【Application】Trend-Following Strategy: A Trading Method Used by Fund Managers

TEJ 台灣經濟新報
TEJ-API Financial Data Analysis
7 min readJun 14, 2024
Photo by riis riiiis on Unsplash

Summary

  • Difficulty: ★★★☆☆
  • Focus: Exploring the relationship between trend-following strategies and expected stock returns.
  • Implementation: Designing and backtesting trend following trading strategies using the TQuant Lab platform to assess risk and performance.

Introduction

Over the past two decades, trend following has been a successful trading strategy used by many fund managers, professional traders, and global macro hedge funds to profit in global futures markets. Applying trend-following strategies to stocks has recently gained significant attention, with a surge in published research and articles. Increasingly, the literature suggests that the trend effects observed in futures markets can similarly apply to stock markets.
Michael W. Covel, the founder of the renowned trend-following company TrendFollowing.com, presents a unique perspective. He states, “In capital markets, investors often seek to establish ‘causal relationships’ in price movements, believing it provides a sense of security to their strategies. However, trend following takes a different path. It doesn’t attempt to predict market directions, but instead, it places a strong emphasis on discipline, precise trading rules, and strict risk control to navigate market volatility.”
Additionally, Cole Wilcox and Eric Crittenden (2005) outlined the conditions for implementing a trend-following strategy in the U.S. stock market:

  • Investment Scope: Includes the 100 most liquid U.S. stocks with prices not less than $5.
  • Entry Condition: An entry signal is generated if today’s closing price equals or exceeds the highest closing price in the stock’s history.
  • Exit Condition: Uses a 10-period Average True Range (ATR) as a trailing stop signal. The stock is exited when its price falls below the current price minus the ATR.

Based on these principles, this article designs a trend-following strategy suitable for investors’ reference in the Taiwanese stock market.

Trend-Following Strategy

  • Investment Scope: Invest in the most liquid Taiwanese stocks with prices not less than NT$10.
  • Entry Condition: If today’s closing price exceeds the previous year’s highest closing price, an entry signal is generated.
  • Exit Condition: This strategy uses a 10-day Average True Range (ATR) as a trailing stop signal. The stock is exited when its price falls below the current price minus the ATR.
  • Position Management: Investors hold all stocks that meet the entry criteria and have not triggered a stop-loss. The portfolio is equally weighted and rebalanced daily.
  • Transaction Costs: TQuant Lab’s Taiwan fee model.

Note: To increase entry opportunities, the entry condition of “highest closing price in history” is modified to “highest closing price in the past year.” Readers may adjust this condition as they see fit.

Editing Environment and Module Requirements

This article uses macOS and Jupyter Notebook as the editor.

Data Import

import os
import tejapi
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
tej_key = 'your key'
api_base = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = tej_key
os.environ['TEJAPI_BASE'] = api_base
start = '2019-04-01'
end = '2024-04-01'

Utilize the get_universe function to obtain the stock pool

from zipline.sources.TEJ_Api_Data import get_universe
pool = get_universe(start, end, mkt = ['TWSE', 'OTC'], stktp_e=['Common Stock-Foreign', 'Common Stock'])
tickers = ' '.join(pool)
start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')
os.environ['ticker'] = ''.join(tickers) +' IR0001'
os.environ['mdate'] = start+' '+end
!zipline ingest -b tquant

The data spans from April 1, 2019, to April 1, 2024. We filter all common stocks listed on the Taiwanese stock market as the stock pool and include the Taiwan Weighted Index (IR0001) for market comparison. Finally, we ingest the bundle.

Note: To access all common stocks listed on the market, data for 1,890 stocks needs to be retrieved. The large volume of data may result in local memory insufficiency or significant API key usage. Please use it with caution.

Creating Pipeline Function and Custom Factor

Custom Factor Function

The CustomFactor allows users to design customized factors as needed. In this case study, we use it to handle:

  • Average True Range (ATR)
  • Highest Price in the Past Year (N_Year_Highest)

Pipeline() provides users with the capability to quickly process quantitative indicators and price-volume data for multiple stocks. In this case study, we use it to handle:

  • Stocks trading above their highest price in the past year, priced over NT$10, and ranked in the top ten for trading volume (vol_rank)
  • Current day’s closing price (curr_price)
  • Stop-loss point (atr)
  • Highest price in the past year (highest_price)
from zipline.pipeline import Pipeline
from zipline.TQresearch.tej_pipeline import run_pipeline
def make_pipeline():
volume = TWEquityPricing.volume.latest
curr_price = TWEquityPricing.close.latest
highest_price = N_Year_Highest(inputs = [TWEquityPricing.close])

sample = curr_price.__ge__(highest_price) # find stocks that >= highest price
vol_rank = volume.rank(mask = sample & curr_price.__ge__(10)) # rank the price that >= 10 and stocks that >= highest price

ATR = AverageTrueRange(inputs = [TWEquityPricing.high,
TWEquityPricing.low,
TWEquityPricing.close])
return Pipeline(
columns = {
'vol_rank':vol_rank,
'curr_price': curr_price,
'highest_price': highest_price,
'atr': ATR.ATR,
'sample': sample,
'volume':volume
},
screen = ~StaticAssets([benchmark_asset])
)
my_pipeline = run_pipeline(make_pipeline(), start_dt, end_dt)
my_pipeline
Pipeline Information

Creating Initialize Function

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

  • Slippage costs
  • Taiwan stock market fee model
  • Taiwan Weighted Index (IR0001) as the benchmark index
  • Incorporation of strategy factors designed in the Pipeline into the trading process
  • Setting the context.stop_loss variable to record stop-loss points during backtesting
from zipline.finance import slippage, commission
from zipline.api import *

def initialize(context):
set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.01))
set_commission(commission.Custom_TW_Commission(min_trade_cost = 20, discount = 1.0, tax = 0.003))
attach_pipeline(make_pipeline(), 'mystrats')
set_benchmark(symbol('IR0001'))
context.stop_loss = {}

Creating handle_data Function

The handle_data() function is a crucial part of constructing the strategy. It is called daily after the backtest starts and is primarily responsible for setting the trading strategy, placing orders, and recording trading information.

For detailed trading rules for this strategy, please refer to trend_following.ipynb.

def handle_data(context, data):
context.df = pipeline_output('mystrats')
space = 10 - len(context.portfolio.positions)
open_orders = get_open_orders()

if space != 0:
context.latest_target = pipeline_output('mystrats').query("sample == 1")
context.latest_target['vol_rank'] = pd.to_numeric(context.latest_target['vol_rank'], errors='coerce')
context.latest_target = context.latest_target.nlargest(space, 'vol_rank')

for symbol, atr in zip(context.latest_target.index, context.latest_target['atr']):

if symbol not in context.portfolio.positions and symbol not in open_orders and len(context.portfolio.positions) < 10 and atr is not None:

order_target_percent(symbol, 1 / 10 * 0.95)
context.stop_loss[f'{symbol}'] = data.current(symbol, 'close') - atr * 1.25

Executing the Trend-Following Strategy

Use run_algorithm() to execute the above-set strategy, setting the trading period from start_dt (2019-04-16) to end_dt (2024-04-01), using the tquant dataset, with an initial capital of one million NT dollars. The output results includes the daily performance and transaction details.

Cumulative Return
from zipline import run_algorithm
start = '2019-04-16'
end = '2024-04-01'
start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')

results = run_algorithm(
start = start_dt,
end = end_dt,
initialize = initialize,
bundle = 'tquant',
analyze = analyze,
capital_base = 1e6,
handle_data = handle_data
)

Performance Evaluation Using Pyfolio

import pyfolio as pf 
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results['benchmark_return']
pf.tears.create_full_tear_sheet(returns=returns,
positions=positions,
transactions=transactions,
benchmark_rets=benchmark_rets
)
Backtest Performance Comparison with Market

The momentum factor strategy achieved an annualized return of approximately 31.1% over these 57 months, with a cumulative return of 266.536%. Although there was a slight underperformance compared to the market in the initial stages, overall profitability was superior to the market. The beta value of 0.79 indicates that the strategy is less sensitive to market fluctuations, reflecting the core concept of trend following, which does not focus on overall market volatility or predict market directions. However, due to the strategy’s focus on stock price movements, it targets stocks breaking new highs, which intuitively results in higher volatility, reaching 26.1%. Although high, this volatility primarily trends upward, as indicated by the Sortino Ratio of 1.17, an ideal figure (a lower Sortino Ratio means higher negative returns per unit risk).

Apart from the bearish year of 2022, all other trading years showed positive returns.

The chart of long and short position holdings indicates that, under the strategy’s setup with a maximum of 10 stocks, the number of holdings mostly stayed around 10, with a minimum of 4.

Due to daily rebalancing, the turnover rate was occasionally high, warranting attention to transaction fees.

Conclusion

This strategy applies the research of Cole Wilcox and Eric Crittenden (2005) to verify the profitability of trend following in the Taiwanese stock market, yielding the following results:

  • Advantages: Excellent profitability, intuitive and simple entry and exit conditions.
  • Disadvantages: Daily rebalancing results in a higher turnover rate, requiring additional attention to transaction fees.

Investors are welcome to refer to this strategy. We will continue introducing various indicators constructed using the TEJ database and backtest their performance. Readers interested in various trading backtests are encouraged to consider TQuant Lab‘s related packages to build their trading strategies using high-quality databases.

Disclaimer: This strategy is for reference only and does not represent any product or investment advice.

Source Code

Extended Reading

About

--

--

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

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