Table of Contents
Trading is a challenging endeavor, with many full-time traders struggling to outperform the broader market on an annual basis. This led me to wonder if there might be a simple yet effective trading strategy that could consistently beat the market.
In Taiwan’s stock market, there is an interesting phenomenon: when certain stocks experience significant gains, there’s often a debate about whether to jump in, with some fearing they might get trapped at a high point while others are anxious about missing out on the opportunity.
To address this, I developed a straightforward stock selection strategy, combining it with momentum analysis over different time periods. I conducted backtesting to evaluate the performance of this strategy, providing readers with insights and a potential reference.
In the stock selection process, we aim to avoid choosing stocks with low trading volume that can be easily manipulated by large investors or institutions, leading to significant volatility. To mitigate this risk, I selected the top 200 companies by market capitalization at the time, ensuring that the chosen stocks have higher liquidity and market depth.
After selecting the stock pool, we calculate the closing prices of each stock over the past quarter (with the time period adjustable based on specific needs) to assess their mid-term momentum. We then choose the stocks with the strongest momentum. This stock selection method helps capture the trends of strong performers in the market.
• On the first trading day of each month, buy the stock with the highest momentum from the previous period, and sell it at the end of the month.
• This article provides customizable take-profit and stop-loss code, allowing readers to adjust and use it according to their needs.
This strategy is designed to simulate investing without the need for constant monitoring and without being influenced by intraday price fluctuations. At the end of each month, we calculate the momentum from the previous period, and then on the first trading day of the new month, we buy the stock with the highest momentum one minute before the market closes. We sell it one minute before the market closes on the last trading day of the month. Additionally, the strategy includes a slippage function to simulate the impact of transaction costs due to price friction when placing orders. This design is both simple and effective, making it suitable for investors who prefer not to engage in frequent trading.
This article uses the macOS 14.6.1 operating system, with Visual Studio Code (VSCode) as the primary editor for analysis and writing.
# Load commonly used packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Set environment variables (TEJ key)
import tejapi
import os
os.environ['TEJAPI_BASE'] = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = 'your_key'
When selecting the time point for backtesting, the top 200 companies by market capitalization at that time are chosen as the stock pool (to avoid look-ahead bias), ensuring that the selected stocks have higher liquidity and market depth. During this step, you can review the stock pool to check for any stocks with undesirable characteristics and make adjustments as needed.
pool = get_universe(start = '2018-01-01',
end = '2018-08-01',
mkt_bd_e = ['TSE'], # Listed Stocks
stktp_e = 'Common Stock'
)
import TejToolAPI
cap_data = TejToolAPI.get_history_data(start = '2018-01-03',
end = '2018-01-03',
ticker = pool,
columns = ['Market_Cap_Dollars'])
top_200_cap = cap_data.sort_values(by='Market_Cap_Dollars', ascending=False).head(200)['coid'].tolist()
top_200_cap
The data period ranges from June 1, 2018, to August 15, 2024. During this period, the price and volume data for the 200 selected stocks are imported, along with the Taiwan Weighted Stock Index (IR0001) as a performance benchmark for comparison.
# Set the backtesting period and start time; 'start' is set to '2018-06-01' to ensure that momentum can be calculated for 2019-01-01
# (The start time is determined based on the required momentum calculation length, and the backtesting period is from 2019/01/01 to 2024/08/01)
start = '2018-06-01'
end = '2024-08-15'
# Set environment variables to specify the backtesting time range and the selected stock pool
os.environ['mdate'] = start + ' ' + end
os.environ['ticker'] = ' '.join(top_200_cap) + ' ' + 'IR0001'
# Execute data retrieval
!zipline ingest -b tquant
CustomFactor
FunctionWith the price and volume data available, we can use the CustomFactor
function to construct the momentum indicator we need. This function calculates the momentum for each stock over a specified time period (window_length
), using changes in closing prices to determine the stock’s momentum.
In this code, Momentum()
inherits from CustomFactor
and defines the logic for calculating momentum. The window_length
parameter specifies the length of the calculation period (for example, 63 days), and momentum is calculated by measuring the change between the initial and final closing prices within this period. The results are then output, allowing us to easily calculate the momentum of each stock over the specified period.
Pipeline()
offers users the ability to process quantitative indicators and price-volume data for multiple securities quickly. In this case, we use it to handle the following tasks:
CustomFactor
function.The initialize()
function is used to define the daily trading environment before trading begins. In this example, we set up the following within initialize()
:
The handle_data()
function is a crucial component in constructing the trading strategy, as it is called on each trading day during the backtest. The primary tasks of this function include setting up the trading strategy, executing orders, and recording trading information.
As mentioned earlier in the article, our strategy only needs to determine whether the current day is the start or end of the month based on the date. The stock to be purchased is then decided based on the momentum value at that time.
Strategy Summary:
• Entry Condition: Enter the market on the first trading day of each month by buying the stock with the highest momentum.
• Exit Condition: Exit the market by selling the held stock on the last trading day of each month. Additionally, the stock can be sold earlier based on custom-defined take-profit or stop-loss conditions.
Use run_algorithm()
to execute the momentum strategy outlined above. The dataset used is tquant
, with an initial capital of 100,000 units. During execution, the output results
will include daily performance and trade details.
Before generating plots, set the font to avoid any font errors :
# Set font to avoid font errors
plt.rcParams['font.family'] = 'Arial Unicode MS'
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
# Execute backtest
results = run_algorithm(
start = start_dt,
end = end_dt,
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e7,
handle_data = handle_data
)
results
from pyfolio.utils import extract_rets_pos_txn_from_zipline
import pyfolio as pf
# Extract returns, positions & transactions from the results table
returns, positions, transactions = extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results.benchmark_return # 取出 benchmark 的報酬率
# Plot all charts provided by Pyfolio
pf.tears.create_full_tear_sheet(returns=returns,
positions=positions,
transactions=transactions,
benchmark_rets=benchmark_rets
)
From the data above, it can be observed that this strategy achieves an annualized return of 64.018%, but it also comes with a high volatility of 63.715%. This indicates that while the strategy has the potential to generate significant returns, it also carries considerable risk. The Sharpe ratio is 1.09, suggesting that although the risk-adjusted return is positive, it is not particularly outstanding. However, the Sortino ratio of 1.7 shows that the strategy is relatively effective in managing downside risk. Therefore, this strategy is suitable for investors seeking high returns and who can tolerate substantial volatility. It may not be ideal for more conservative investors.
In the drawdown ratio chart, it’s evident that this strategy exhibits significant volatility, frequently experiencing large drawdowns. The most substantial drawdown occurred recently in August 2024, during a sharp decline in the Taiwan stock market, where the strategy’s drawdown reached 50%.
From the annualized return and monthly return charts, we can see that although nearly 40% of the months had negative returns, the average monthly return remains positive. However, the downside of this strategy is its high volatility. The main reason for the capital doubling was the capture of the significant surge in Yang Ming Marine Transport during 2021. I believe this highlights the true value of this strategy: despite experiencing multiple losses, it still achieves substantial capital growth by capturing a few major trending opportunities.
Finally, let’s examine whether this strategy is truly a ‘high-risk strategy.’ This chart compares the cumulative returns of the strategy against the broader market under similar volatility conditions, providing a way to assess the strategy’s relative performance at the same risk level. Before June 2021, the strategy underperformed the market, but once it captured a significant profit opportunity, its performance quickly turned around. Notably, even as the broader market experienced a downtrend in 2022, the strategy remained stable and successfully avoided this decline. Therefore, despite the higher volatility of this strategy, its overall performance advantage still surpasses that of the broader market.
In the previous section, we have presented the results of the strategy without take-profit and stop-loss conditions. Next, we will analyze the results with only take-profit, only stop-loss, and with both take-profit and stop-loss conditions, and examine why the returns vary under these different settings.
From the above chart, it can be observed that when we set a take-profit limit at 30% and exit early, the gains that originally caused the unrestricted strategy’s returns to surge significantly lag behind. As a result, from June 2021 onward, the two trends start to parallel each other, indicating that while setting a take-profit limit reduces some returns, it still maintains a similar trajectory to the unrestricted strategy. However, this chart reveals the initial intention behind setting a take-profit limit: we were concerned that the stock might retrace after a significant rise, reducing the profits already gained. In this case, however, we do not see the benefit of this strategy. In fact, it can be observed that if the take-profit point is set even lower (for example, at 20%), the strategy’s performance shifts from originally outperforming the market significantly to lagging behind it. This suggests that in this strategy, we primarily rely on a few large gains from certain trades to achieve overall impressive returns, rather than accumulating capital growth through multiple small gains. Over-restriction may actually be counterproductive.
What if we set the take-profit point higher? In the range of 30% to 40%, we observe that the return does not significantly increase compared to the 30% threshold—the improvement is limited. However, when we further raise the take-profit point to 50%, the performance actually falls below the levels seen with the 30% and 40% thresholds. This leads me to consider a possible reason: in our stock selection strategy, the characteristics of certain stocks make it difficult for them to achieve monthly performance exceeding 50%. These stocks often experience a pullback or correction once they rise to around 40%. Therefore, setting the take-profit point too high can lead to a decrease in overall performance.
Setting a take-profit does indeed help reduce volatility risk, but as the analysis above shows, the choice of take-profit level significantly impacts performance. While we can identify the optimal take-profit during backtesting, predicting this ‘best take-profit point’ in real trading is not easy. Although risk is controlled, the charts reveal that the unrestricted strategy typically yields higher returns than the strategy with a take-profit. This suggests that even if risk is not effectively controlled, the strategy can still achieve substantial returns by capitalizing on a few significant price surges. Therefore, if I were to implement this strategy in real trading, I might choose not to set a take-profit point, as doing so could potentially miss out on the core value of the strategy—capturing explosive returns from high-volume, strong-performing stocks.
In the similar way, let’s analyze stop-loss.
When setting a stop-loss, we can observe some interesting phenomena. Initially, I set the stop-loss point at 5%, but quickly realized that this setting led to a significant difference from the expected performance. The reason might be that when some high-potential stocks experience substantial gains, they often come with greater volatility. Therefore, setting the stop-loss point too tightly can result in exiting the position prematurely, missing out on further upside. To retain holdings within reasonable volatility, the stop-loss point needs to be set more loosely. From the chart above, the most significant difference between setting a 10% and a 15% stop-loss occurred in early 2024. At that time, the held stock dropped below the 10% stop-loss point but did not exceed 15%. As a result, the 10% stop-loss was triggered, causing a missed opportunity for the subsequent rebound, while the 15% stop-loss successfully retained the stock, capturing the subsequent gains.
Finally, we attempted to apply both take-profit and stop-loss as dual exit conditions. The results show that, compared to using a single exit condition, this approach effectively reduces overall volatility, making the asset curve smoother. Despite this, the strategy still managed to achieve a performance that outperformed the broader market. Therefore, for investors with a lower risk tolerance, this dual exit strategy might be a worthwhile option to consider, as it controls risk while still retaining decent profit potential.
This strategy presents an intriguing question: should one ‘get on board’? I believe each reader will have their own answer. Even though I limited the stock pool to the top 200 companies by market capitalization to reduce the risk of manipulation, the selected stocks with the highest momentum are often those that have experienced significant short-term gains. These stocks may have already reached the peak of their strong performance in the previous quarter, but they could also continue to rise.
In this strategy, the key to achieving high profits is successfully holding stocks with strong upward momentum while bearing high risk. For example, in 2021, the strategy benefited from the explosive rise of Yang Ming Marine Transport; in 2024, despite a drop of more than 10%, the stock rebounded strongly, resulting in considerable gains. During the testing process, we experimented with various conditions, such as take-profit only, stop-loss only, or both. As discussed in the article, interested readers can adjust the code according to the scenarios they wish to test. The core logic of the strategy has been well established, and it can be flexibly adjusted to assess the impact of different settings on the strategy’s performance.
“Taiwan stock market data, TEJ collect it all.”
Subscribe to newsletter