【Application】TQuant Lab Price Momentum Factor Strategy: The Market Favors the Strong

TEJ 台灣經濟新報
TEJ-API Financial Data Analysis
8 min readMar 15, 2024
Momentum Factor Strategy
Photo by Behnam Norouzi on Unsplash

Highlight

  • Article Difficulty: ★★★☆☆
  • We are exploring the relationship between price momentum and expected stock returns.
  • We are developing and backtesting Price Momentum Factor Strategy strategies using the TQuant Lab backtesting platform to assess risks and performance.
  • Adapted from The Relationship between Price Momentum and Expected Stock Returns.

Introduction

Previous research on price momentum has found a positive correlation between the 26-week price deviation rate and future stock returns in the U.S. stock market. Jegadeesh and Titman identified a momentum effect in the U.S. stock market — buying stocks with the best past 6–12 months returns while simultaneously selling stocks with the worst past 6–12 months yielded significant excess returns economically and statistically. Subsequent studies by many scholars have also found that momentum effects are prevalent in international stock markets and across different asset classes.

In recent years, regulatory efforts to enhance information disclosure in the stock market, coupled with gradual relaxations in the volatility of stock market fluctuations, have rapidly increased the efficiency of the Taiwan stock market. Therefore, it is worth further examining the relationship between price momentum and expected stock returns in the Taiwanese stock market. To explore this relationship, academia, and industry have developed numerous indicators to measure stock momentum. In addition to indicators based on past cumulative returns, there are also indicators utilizing volatility-adjusted cumulative stock returns, moving average divergences, CAPM residuals’ cumulative values, Etc. This article will use our previous research findings:

  • Univariate analysis shows that the JTMOM3 formed over three months and the MAD momentum variable based on moving average divergences exhibit the most economically and statistically significant predictive ability for stock expected returns.
  • Multivariate analysis shows that even after controlling for one-year beta values, price-to-book ratios, market capitalization, and 12-month momentum characteristic variables, the relationship between JTMOM3 and MAD momentum variables and stock expected returns remains significantly positive. This indicates their predictive and explanatory power for cross-sectional stock expected returns.
Momentum Factor Strategy

Hence, this article will primarily utilize JTMOM3 and MAD momentum variables for backtesting analysis.
For more detailed information, please refer to The Relationship between Price Momentum and Expected Stock Returns.

Momentum Factor Strategy

  • Calculate the average stock returns over the past three months.
  • Calculate the Moving Average Divergence (MAD) as the exit condition for the Golden Cross.
  • Divide trading into recording months (first three months) and trading months (fourth month) using a four-month cycle:

Record momentum for each stock over the past three months during the recording month and execute orders during the trading month.

  • On the “last day” of the trading month, buy the top 5% performing stocks in terms of momentum from the stock pool and simultaneously short the bottom 5% performing stocks. (With a one-month gap between momentum formation periods to avoid the impact of bid-ask spreads and short-term reversal effects.)
  • Precise stock data from the recording month and start a new cycle.
  • Exit conditions in the recording month (exit uniformly in the recording month):

Exit long positions when the short-term MAD > long-term MAD.

Exit short positions when the short-term MAD < long-term MAD.

Editing Environment and Module Requirements

This article uses MacOS and Jupyter Notebook as the editor.

Data Importation

The data period ranges from 2018–02–01 to 2023–12–31. Forty stocks are randomly selected, and the Taiwan Weighted Index (IX0001) is included as the market benchmark.

Creating the Pipeline Function

Creating Custom Factor Function

The Custom Factor allows users to design their desired customized factors. In this case, we will use it to handle:
A 3-month momentum factor (Momentum, details can be found in Custom Factors).

from zipline.pipeline.filters import StaticAssets
class Momentum(CustomFactor):
inputs = [TWEquityPricing.close]
window_length = 63 # finding past n days' return

def compute(self, today, assets, out, close):

monthly_return_1 = ((close[21-1]-close[0])/close[0])*100
monthly_return_2 = ((close[42-1]-close[20])/close[0])*100
monthly_return_3 = ((close[63-1]-close[41])/close[0])*100
formation_return = (((monthly_return_1)+(monthly_return_2)+(monthly_return_3))/3).round(5) # 3 months average return
out[:] = formation_return
from numpy import average, abs as np_abs
from zipline.pipeline.factors.basic import _ExponentialWeightedFactor, exponential_weights
class WeightedMovingAbsDev(_ExponentialWeightedFactor):
def compute(self, today, assets, out, data, decay_rate):
weights = exponential_weights(len(data), decay_rate = 1)
mean = average(data, axis=0, weights=weights)
abs_deviation = average(np_abs(data - mean), axis=0, weights=weights)
out[:] = abs_deviation

The Pipeline() function allows users to quickly process quantitative indicators and price-volume data for multiple targets. In this case, we will use it to handle:

  • Calculate the MAD indicator as an exit condition (built-in factor: WeightedMovingAbsDev; details can be found in Pipeline built-in factors).
  • The window_length MAD is set to 21 days for the short term and 120 days for the long term.
  • When High is True, it indicates the top 8 performing stocks in terms of returns, whereas when Low is True, it indicates the bottom eight performing stocks. (Users can choose to trade only two stocks per 5% of the 40 stocks, but expanding the stock pool is recommended; otherwise, the number of traded stocks will be too small. Here, we will calculate using eight stocks each.)
start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')
bundle = bundles.load('tquant')
benchmark_asset = bundle.asset_finder.lookup_symbol('IX0001',as_of_date = None)
def make_pipeline():
mom = Momentum()
mad_short = WeightedMovingAbsDev(inputs = [TWEquityPricing.close], window_length = 21, decay_rate = 1)
mad_long = WeightedMovingAbsDev(inputs = [TWEquityPricing.close], window_length = 120, decay_rate = 1)
curr_price = TWEquityPricing.close.latest

return Pipeline(
columns = {
'curr_price': curr_price,
'Momentum': mom,
'High': mom.top(8),
'Low': mom.bottom(8),
'MAD_short': mad_short,
'MAD_long': mad_long,
},
screen = ~StaticAssets([benchmark_asset])
)
my_pipeline = run_pipeline(make_pipeline(), start_dt, end_dt)
my_pipeline.tail(20)
Momentum Factor Strategy

Establishing the initialize Function

The initialize() Function is used to define the daily trading environment before the start of trading. In this example, we set:

  • Slippage cost
  • Commission fees
  • Returns index (IX0001) as the benchmark index
  • Integrating the momentum factor strategy designed using Pipeline into the trading process

Creating the handle_data Function

The handle_data() function is critical for constructing the price momentum factor strategy. It is called daily after the start of backtesting and is mainly responsible for setting trading strategies, placing orders, and recording trading information.

For detailed trading rules of this strategy, please refer to Momentum Factor.ipynb

# record month -> record 3 months' momentum
if (context.day_count == 1) and (High == True) and (len(context.high_list) < 45):
context.high_list.append(i)
if (context.day_count == 1) and (Low == True) and (len(context.low_list) < 45):
context.low_list.append(i)

# trade month (Long, Short) -> first day in trade month, context.high_list and context.low_list have values
if (context.day_count == 0) and (i in context.high_list) and (cash_position > curr_price * 2000):
order(i, 2000)
buy = True
record(buy = buy)
context.high_list = [x for x in context.high_list if x != i]
elif (context.day_count == 0) and (i in context.low_list) and (cash_position >= 0):
order(i, -2000)
buy = True
record(buy = buy)
context.low_list = [x for x in context.low_list if x != i]

# Exiting the position (clearing the position from the previous trading month on the 21st day of the recording month) -> MAD_short > MAD_long or MAD_short > MAD_long
if (21 <= context.day_count <= 63) and (MAD_short > MAD_long) and (stock_position > 0) and (i in context.history_high):
order_target(i, 0)
sell = True
record(sell = sell)
context.history_high = [x for x in context.history_high if x != i]
elif (21 <= context.day_count <= 63) and (MAD_short < MAD_long) and (i in context.history_low):
order_target(i, 0)
sell = True
record(sell = sell)
context.history_low = [x for x in context.history_low if x != i]

Executing the Trading Strategy

Run_algorithm() is used to execute the momentum factor strategy as configured. The trading period is set from start_dt (2018-02-01) to end_dt (2023-12-31), using the dataset tquant, with initial capital of ten million dollars. The output results represent daily performance and trading details.

Momentum Factor Strategy

Performance Evaluation Using Pyfolio

import pyfolio as pf
from pyfolio.utils import extract_rets_pos_txn_from_zipline
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results.benchmark_return
# Creating a Full Tear Sheet
pf.create_full_tear_sheet(returns, positions = positions, transactions = transactions,
benchmark_rets = benchmark_rets,
round_trips=False)
Momentum Factor Strategy

We can see that the momentum factor strategy achieved an annualized return of approximately 16.56% over these 68 months, with a cumulative return of 140.25%. The profit performance is quite impressive. However, relative to the strategy’s characteristics, there may be a lag in entry. For example, we cannot immediately follow up on stocks with good performance this month; instead, we must wait for some time to buy in. Therefore, missing the entry point may be the reason for the performance discrepancy between Q1 and Q2 of 20 years compared to the benchmark. However, this lagging effect may also bring more excess returns to the strategy. For example, when the market sentiment in a particular sector improves, the strategy has already bought in due to its previous good momentum, equivalent to early positioning. Therefore, it is speculated that such significant outperformance occurred in the second half of 21 and 23 years. However, regarding the lagging issue, we can complement it with indicators such as RSI (TQuant Lab RSI moving average strategy) or volatility-related indicators, which can be further explored.

Momentum Factor Strategy
Momentum Factor Strategy

The total amount of long positions is more significant than that of short positions, but there are more trading stocks in short positions.

Conclusion

This strategy applies the conclusion of The Relationship between Price Momentum and Expected Stock Returns. and simulates backtesting based on the concept of momentum factor: “Stocks with good past performance are likely to perform well in the future, and vice versa,” verifying the profitability of “the strong get stronger, and the weak get weaker” in the Taiwan stock market. Investors are welcome to refer to it. In the future, we will continue introducing various indicators using the TEJ database and backtesting their performance. Therefore, readers interested in various trading backtesting scenarios are welcome to purchase relevant solutions from TQuant Lab to construct their trading strategies using high-quality databases.

This is a gentle reminder that this strategy is for reference only and does not represent advice on commodities or investments.

[TQuant Lab] Solving Your Quantitative Finance Pain Points Comprehensively providing all the tools needed for trading backtesting.

Register and Start Your Trial

GitHub Source Code

Extended Reading

相關連結

--

--

TEJ 台灣經濟新報
TEJ-API Financial Data Analysis

TEJ 為台灣本土第一大財經資訊公司,成立於 1990 年,提供金融市場基本分析所需資訊,以及信用風險、法遵科技、資產評價、量化分析及 ESG 等解決方案及顧問服務。鑒於財務金融領域日趨多元與複雜,TEJ 結合實務與學術界的精英人才,致力於開發機器學習、人工智慧 AI 及自然語言處理 NLP 等新技術,持續提供創新服務