{"id":1312,"date":"2024-08-23T16:00:00","date_gmt":"2024-08-23T16:00:00","guid":{"rendered":"https:\/\/www.tejwin.com\/en\/insights\/momentum-strategy-does-the-trend-remains\/"},"modified":"2024-08-23T16:00:00","modified_gmt":"2024-08-23T16:00:00","slug":"momentum-strategy-does-the-trend-remains","status":"publish","type":"insight","link":"https:\/\/www.tejwin.com\/en\/insights\/momentum-strategy-does-the-trend-remains\/","title":{"rendered":"The Momentum Strategy \u2014 Does the Trend Remain?"},"content":{"rendered":"<figure class=\"wp-block-image aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" alt=\"momentum\" class=\"wp-image-34694\" height=\"768\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/chris-liverani-dBI_My696Rk-unsplash-2.jpg\" style=\"width:657px;height:auto\" width=\"1024\"\/><figcaption class=\"wp-element-caption\">Photo by <a href=\"https:\/\/unsplash.com\/@chrisliverani?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash\" rel=\"noopener\" target=\"_blank\">Chris Liverani<\/a> on <a href=\"https:\/\/unsplash.com\/photos\/turned-on-flat-screen-monitor-dBI_My696Rk?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash\" rel=\"noopener\" target=\"_blank\">Unsplash<\/a><\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Highlight<\/h2>\n<ul class=\"wp-block-list\">\n<li>Article Difficulty: \u2605\u2605\u2606\u2606\u2606<\/li>\n<li>Introduction to the Momentum.<\/li>\n<li>Explain the market capitalization-based stock selection combined with the momentum strategy.<\/li>\n<li>Utilize TQuant Lab to backtest the Momentum Strategy and observe its effectiveness in capturing the momentum factor.<\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">Preface<\/h2>\n<p class=\"wp-block-paragraph\">Trading in the financial markets is a complex task, and even professional investors often find it challenging to outperform the market consistently. In Taiwan\u2019s equity market, investors often face the dilemma of whether to ride rising trends or avoid purchasing at peak prices. \u00a0<\/p>\n<p class=\"wp-block-paragraph\">This article presents a structured momentum strategy designed to identify and capture stocks with strong upward trends. Through detailed backtesting, we evaluate the strategy\u2019s performance, providing a valuable reference for quantitative researchers and practitioners.\u00a0<\/p>\n<h2 class=\"wp-block-heading\">Selecting Stocks and Calculating Momentum Across Periods<\/h2>\n<h3 class=\"wp-block-heading\">Stock Selection Criteria<\/h3>\n<p class=\"wp-block-paragraph\">In the stock selection process, we aim to avoid securities with low trading volume that are prone to sharp price fluctuations due to manipulation by large investors or institutional players. To mitigate such risks, we construct our stock universe by selecting the top 200 companies by market capitalization each year, ensuring that the chosen stocks possess high liquidity and sufficient market depth..\u00a0<\/p>\n<h3 class=\"wp-block-heading\">Momentum Calculation Across Timeframes\u00a0<\/h3>\n<p class=\"wp-block-paragraph\">After establishing the stock universe, we evaluate each individual stock by calculating its closing prices over the past month\u2014a period that can be adjusted based on specific needs\u2014to assess its medium- to long-term momentum. From this analysis, we select the top five stocks exhibiting the strongest momentum. This stock selection strategy helps capture prevailing trends among market leaders.<\/p>\n<h3 class=\"wp-block-heading\">Trading Logic<\/h3>\n<p class=\"wp-block-paragraph\">On the first trading day of each month, we buy the top five momentum stocks identified from the previous period and sell those held in the prior portfolio that no longer meet the selection criteria. If a stock remains in the top five for two consecutive periods, it continues to be held without being sold. Each of the five positions is rebalanced using <code>order_target_percent<\/code> to allocate 20% of the portfolio\u2019s value to each stock.<\/p>\n<p class=\"wp-block-paragraph\">The strategy is designed to simulate an investment approach that does not require constant monitoring and is unaffected by intraday price volatility. Momentum rankings are calculated at the end of each month, and trades are executed one minute before the close on the first trading day of the following month. To reflect the real-world costs of trading, the strategy incorporates a slippage function to simulate price friction when placing orders. This approach is both simple and effective, making it suitable for investors who prefer a low-maintenance strategy.\u00a0<\/p>\n<h3 class=\"wp-block-heading\">Import Packages<\/h3>\n<pre class=\"wp-block-code\"><code># \u8f09\u5165\u5e38\u7528\u5957\u4ef6\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\uff09\nimport tejapi\nimport os\nos.environ['TEJAPI_BASE'] = 'https:\/\/api.tej.com.tw'\nos.environ['TEJAPI_KEY'] = 'your_key'\n\n\nfrom zipline.sources.TEJ_Api_Data import get_universe\nfrom zipline.data import bundles\nfrom zipline.sources.TEJ_Api_Data import get_Benchmark_Return\nfrom zipline.pipeline.mixins import SingleInputMixin\nfrom zipline.pipeline.data import TWEquityPricing\nfrom zipline.pipeline.factors import CustomFactor\nfrom zipline.pipeline import Pipeline\nfrom zipline.TQresearch.tej_pipeline import run_pipeline\nfrom zipline import run_algorithm\n<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Stock Pool and Ingest data<\/h3>\n<pre class=\"wp-block-code\"><code>pool = get_universe(start = '2018-01-01',\n                    end = '2025-04-10',\n                    mkt_bd_e = ['TSE'],  \n                    stktp_e = 'Common Stock'\n)\n\nstart = '2018-06-01'\nend = '2025-04-10'\n\n\nos.environ['mdate'] = start + ' ' + end\nos.environ['ticker'] = ' '.join(pool) + ' ' + 'IR0001'\n\n!zipline ingest -b tquant<\/code><\/pre>\n<h3 class=\"wp-block-heading\">CustomFactor Function<\/h3>\n<p class=\"wp-block-paragraph\">With price and volume data in place, we can use the <code>CustomFactor<\/code> function to construct our desired momentum indicator. This function calculates the momentum of each stock by analyzing the change in closing prices over a specified time window (<code>window_length<\/code>) for each individual security.<\/p>\n<pre class=\"wp-block-code\"><code>from zipline.pipeline.filters import StaticAssets\n\nclass Momentum(CustomFactor):\n    inputs = [TWEquityPricing.close]\n   \n    window_length = 20\n    \n    def compute(self, today, assets, out, close):\n        \n        return_rate = (((close[-1] - close[0]) \/ close[0]) * 100).round(5)\n        out[:] = return_rate<\/code><\/pre>\n<p class=\"wp-block-paragraph\">In this code snippet, <code>Momentum()<\/code> inherits from <code>CustomFactor<\/code> and defines the logic for calculating momentum. The <code>window_length<\/code> parameter specifies the length of the lookback period (e.g., 20 days), and momentum is computed based on the change between the first and last closing prices within that period. This approach enables us to easily calculate the momentum of each stock over a defined time frame.<\/p>\n<h3 class=\"wp-block-heading\">Pipeline<\/h3>\n<p class=\"wp-block-paragraph\">The <code>Pipeline()<\/code> function offers users an efficient way to process quantitative indicators and price-volume data across multiple securities. In this case, we use it to:<\/p>\n<ul class=\"wp-block-list\">\n<li>Import daily closing prices<\/li>\n<li>Integrate the user-defined <code>CustomFactor<\/code> function for momentum calculation<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">This setup streamlines the data preparation process, enabling scalable and systematic analysis across a broad universe of stocks.<\/p>\n<pre class=\"wp-block-code\"><code>start = '2019-01-01'\nend = '2025-04-10'\nstart_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')\nbundle = bundles.load('tquant')\nbenchmark_asset = bundle.asset_finder.lookup_symbol('IR0001', as_of_date=None)\n\ndef make_pipeline():\n    \n    mom = Momentum()\n    \n    curr_price = TWEquityPricing.close.latest\n    \n    return Pipeline(\n        columns={\n            'curr_price': curr_price,\n            'Momentum': mom\n        },\n        \n        screen=~StaticAssets([benchmark_asset])\n    )\n\nmy_pipeline = run_pipeline(make_pipeline(), start_dt, end_dt)\ndates = my_pipeline.index.get_level_values(0).unique()\nmy_pipeline<\/code><\/pre>\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" alt=\"momentum\" class=\"wp-image-34367\" height=\"404\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/image-669.png\" style=\"width:839px;height:auto\" width=\"617\"\/><\/figure>\n<h3 class=\"wp-block-heading\">Initialize<\/h3>\n<p class=\"wp-block-paragraph\">The <code>initialize()<\/code> function is used to define the daily trading environment before the start of the backtest. In this example, we configure the following:<\/p>\n<ul class=\"wp-block-list\">\n<li>Set a fixed bid-ask spread for slippage modeling, without applying any volume constraints<\/li>\n<li>Define transaction costs<\/li>\n<li>Use the return of the weighted stock index (IR0001) as the performance benchmark<\/li>\n<li>Integrate the previously constructed <code>Pipeline<\/code> for momentum calculation into the trading algorithm<\/li>\n<li>Initialize <code>context.last_rebalance_day<\/code> to record the date of the last portfolio rebalance<\/li>\n<li>At the beginning of each year, reselect the top 200 stocks by market capitalization before applying the momentum filter<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">This setup ensures that the trading strategy operates under realistic market conditions and dynamically updates its universe and portfolio allocations based on well-defined criteria.<\/p>\n<pre class=\"wp-block-code\"><code>from zipline.finance import slippage, commission\nfrom zipline.api import *\nfrom zipline.api import set_slippage, set_commission, set_benchmark, attach_pipeline, order, order_target, symbol, pipeline_output, record\ndef initialize(context):\n    context.holding_stock    = False\n    context.target           = None\n    context.targets = []\n    context.last_rebalance_day = None\n\n    context.stock_buy_price  = 0\n    context.rebalance_period    = 20 \n\n\n\n    set_slippage(slippage.FixedBasisPointsSlippage(basis_points=50.0, volume_limit=1))\n    set_commission(commission.PerShare(cost=0.001425 + 0.003\/2))\n    attach_pipeline(make_pipeline(), 'mystrats')\n    set_benchmark(symbol('IR0001'))\n\n    \n    context.top200_by_year = {}\n    for Y in years:\n        \n        sessions = calendar.sessions_in_range(\n            pd.Timestamp(f\"{Y-1}-01-01\", tz=tz),\n            pd.Timestamp(f\"{Y-1}-12-31\", tz=tz),\n        )\n        last_session = sessions[-1]  \n\n        dt_str = last_session.strftime('%Y-%m-%d')\n        cap = TejToolAPI.get_history_data(\n            start   = dt_str,\n            end     = dt_str,\n            ticker  = pool,\n            columns = ['Market_Cap_Dollars']\n        )\n        #print(f\"Year: {Y}, Date: {dt_str}\")\n        coids = (\n            cap.sort_values('Market_Cap_Dollars', ascending=False)\n               .head(200)['coid']\n               .tolist()\n        )\n        \n        context.top200_by_year[Y] = [symbol(c) for c in coids]<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Handle_data<\/h3>\n<p class=\"wp-block-paragraph\">The <code>handle_data()<\/code> function plays a crucial role in constructing the trading strategy, as it is called on each trading day during the backtest. Its primary responsibilities include defining the trading logic, executing orders, and recording trade-related information.<\/p>\n<p class=\"wp-block-paragraph\">As mentioned at the beginning of the article, this strategy only requires checking whether the current date marks the beginning or end of the month, and making buy or sell decisions based on the momentum rankings at that time.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Strategy Summary:<\/strong><\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Entry Condition:<\/strong> Enter positions on the first trading day of each month by selecting the top five stocks with the highest momentum (adjustable based on specific needs).<\/li>\n<li><strong>Exit and Holding Conditions:<\/strong> Also on the first trading day of each month, sell stocks that no longer appear in the current top five momentum rankings and continue holding those that remain selected. Additionally, the strategy can incorporate custom stop-loss or take-profit rules for early exits.<\/li>\n<\/ul>\n<pre class=\"wp-block-code\"><code>def handle_data(context, data):\n    \n    out_dir = pipeline_output('mystrats')\n    current = context.get_datetime().normalize()\n    y, m, d = current.year, current.month, current.day\n\n    \n    mdays     = dates[(dates.year == y) &amp; (dates.month == m)]\n    first_day = mdays.min().day\n\n    \n    if d == first_day:\n        \n        universe = context.top200_by_year.get(y, [])\n        \n        today_df = out_dir.loc[out_dir.index.isin(universe)]\n        if today_df.empty:\n            return\n\n        \n        top5 = today_df['Momentum'].nlargest(5).index.tolist()\n\n        \n        prev    = context.targets\n        to_drop = set(prev) - set(top5)\n        for asset in to_drop:\n            order_target_percent(asset, 0)\n            print(f\"Drop {asset}\")\n\n        \n        weight = 1.0 \/ len(top5)\n        for asset in top5:\n            order_target_percent(asset, weight)\n            print(f\"Rebalance {asset} to {weight:.0%}\")\n\n        \n        context.targets = top5\n\ndef analyze(context, perf):\n    pass<\/code><\/pre>\n<p class=\"wp-block-paragraph\">If you wish to customize the rebalancing frequency, you can replace the original <code>handle_data()<\/code> function with the version below. Additionally, modify <code>context.rebalance_period<\/code> in the <code>initialize()<\/code> function to specify the desired number of days between each rebalance.<\/p>\n<pre class=\"wp-block-code\"><code>def handle_data(context, data):\n    \n    out_dir = pipeline_output('mystrats')\n    current = context.get_datetime().normalize()\n    \n    \n    if context.last_rebalance_day is None:\n        context.last_rebalance_day = current\n\n    \n    days_since = (current - context.last_rebalance_day).days\n    if days_since &lt; context.rebalance_period:\n        return\n\n    context.last_rebalance_day = current\n\n    y        = current.year\n    universe = context.top200_by_year.get(y, [])\n    #  \u76f4\u63a5\u7528\u55ae\u5c64 index \u7be9\u9078\n    today_df = out_dir.loc[out_dir.index.isin(universe)]\n    if today_df.empty:\n        return\n\n    top5 = today_df['Momentum'].nlargest(5).index.tolist()\n\n    prev    = context.targets\n    to_drop = set(prev) - set(top5)\n    for asset in to_drop:\n        order_target_percent(asset, 0)\n        print(f\"Drop {asset}\")\n\n    weight = 1.0 \/ len(top5)\n    for asset in top5:\n        order_target_percent(asset, weight)\n        print(f\"Rebalance {asset} to {weight:.0%}\")\n\n    context.targets = top5<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Backtesting the Momentum Strategy<\/h2>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" alt=\"momentum\" class=\"wp-image-34433\" height=\"522\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/image-674.png\" width=\"1024\"\/><\/figure>\n<p class=\"wp-block-paragraph\">Below is a summary of the core backtest logic:\u00a0<\/p>\n<ul class=\"wp-block-list\">\n<li>Stock Universe: Top 200 companies by market capitalization at the time.\u00a0<\/li>\n<li>Data Source:<a data-id=\"https:\/\/www.tejwin.com\/en\/databank-solution\/market-data\/\" data-type=\"link\" href=\"\/en\/databank-solution\/market-data\/\"> TEJ market data<\/a>, including stock prices and volumes.\u00a0<\/li>\n<li>Entry\/Exit Points: First and last trading days of each month.\u00a0<\/li>\n<li>Performance Evaluation: Pyfolio package used to generate a comprehensive performance report.\u00a0<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Performance Overview\u00a0<\/p>\n<ul class=\"wp-block-list\">\n<li>Annualized Return: 28.26%\u00a0<\/li>\n<li>Volatility: 37.25%\u00a0<\/li>\n<li>Sharpe Ratio: 0.86<\/li>\n<li>Sortino Ratio: 1.24<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Although the strategy presents significant profit potential, it also entails considerable risk. The strategy successfully captured major opportunities, such as the 2021 rally in Yang Ming Marine Transport, which contributed substantially to the overall performance. \u00a0<\/p>\n<h3 class=\"wp-block-heading\">\u00a0<\/h3>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" alt=\"momentum\" class=\"wp-image-34439\" height=\"698\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/image-677.png\" width=\"1024\"\/><\/figure>\n<p class=\"wp-block-paragraph\">\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<p class=\"wp-block-paragraph\">This strategy presents a fascinating question: should you \u201cget on board\u201d? Readers likely have their own answers to this. Even though the stock universe has been limited to the top 200 companies by market capitalization\u2014an effort to reduce the risk of price manipulation\u2014the stocks selected as the top five momentum performers are often those that have experienced sharp gains in the recent past. Such stocks may be nearing the end of their rally, or they may continue to ride their upward momentum.<\/p>\n<p class=\"wp-block-paragraph\">The key to achieving significant profits with this strategy lies in taking on higher risk while successfully capturing stocks with strong upward momentum. For instance, in 2021, the strategy benefited from the explosive rally of Yang Ming Marine Transport Corporation. In 2024, despite a drawdown of over 10%, the stock rebounded strongly, yielding considerable returns. However, during the tariff war of 2025, its performance lagged the broader market, highlighting the inherent volatility of such trades.<\/p>\n<p class=\"wp-block-paragraph\">For readers interested in further exploration, the strategy can be customized to reflect different market conditions. By adjusting the code to include stop-loss and take-profit mechanisms, you can test how such modifications affect performance. The core logic of the strategy is already in place\u2014what remains is the flexibility to adapt it to suit various investment scenarios and validate its robustness under different conditions.<\/p>\n<p class=\"has-text-align-center has-large-font-size has-ek-indent wp-block-paragraph\" style=\"--ek-indent:20px\"><mark class=\"has-inline-color has-vivid-cyan-blue-color\" style=\"background-color:rgba(0, 0, 0, 0)\"><strong><em>\u201c<mark class=\"has-inline-color has-vivid-cyan-blue-color\" style=\"background-color:rgba(0, 0, 0, 0)\"><strong><em>Taiwan stock market data, TEJ collect it all.\u201d<\/em><\/strong><\/mark><\/em><\/strong><\/mark><\/p>\n<p class=\"wp-block-paragraph\">For researchers and investors looking to refine their trading strategies, access to high-quality data is paramount. TEJ\u2019s comprehensive databases provide essential insights and historical performance data, facilitating the development and backtesting of such quantitative strategies. By leveraging these resources, users can better navigate the complexities of market dynamics and optimize their trading decisions.\u00a0<\/p>\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button has-custom-width wp-block-button__width-100 has-custom-font-size is-style-fill\" style=\"font-size:21px\"><a class=\"wp-block-button__link has-background wp-element-button\" href=\"\/en\/databank-solution\/market-data\/\" style=\"border-radius:9px;background:linear-gradient(135deg,rgb(238,217,130) 2%,rgb(83,198,160) 50%,rgb(51,106,181) 100%)\"><strong>Learn More About the High-Quality Investment Database by TEJ! <br \/>Construct Trading Strategies With Market Data<\/strong><\/a><\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">\n<h2 class=\"wp-block-heading\">Extended Reading<\/h2>\n<ul class=\"wp-block-list\">\n<li><a href=\"\/en\/?post_type=insight&amp;p=26933\" rel=\"noreferrer noopener\" target=\"_blank\">Verifying LSTM Stock Price Prediction Effectiveness Using TQuant Lab (Part 2)<\/a><\/li>\n<li><a href=\"\/en\/insight\/loss-aversion-strategy\/\" rel=\"noreferrer noopener\" target=\"_blank\">TQuant Lab Loss Aversion Strategy\u200a\u2014\u200aAverage True Range<\/a><\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">\n<p class=\"wp-block-paragraph\">\n","protected":false},"excerpt":{"rendered":"<p>The article delves into the performance of momentum strategy under different take-profit and stop-loss conditions, analyzing how to balance risk and reward in a highly volatile market using actual backtesting data.<\/p>\n","protected":false},"featured_media":1311,"template":"","tags":[50,65,70,71],"insight_category":[13],"class_list":["post-1312","insight","type-insight","status-publish","has-post-thumbnail","hentry","tag-market-data","tag-python","tag-tej-api","tag-quantitative-strategy","insight_category-quant-research"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight\/1312","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight"}],"about":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/types\/insight"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/media\/1311"}],"wp:attachment":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/media?parent=1312"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/tags?post=1312"},{"taxonomy":"insight_category","embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight_category?post=1312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}