Daily Momentum in Chinese Equities
Log in to collectAcademic paper
Daily Momentum and New Investors in an Emerging Stock Market
Zhenyu Gao; Wenxi Jiang; Wei Xiong
- HKChinese University of Hong Kong
- ?The Chinese University of Hong Kong (CUHK) - CUHK Business School
- Yale University
- ?CUHK Business School, The Chinese University of Hong Kong
- Shenzhen Stock Exchange
- National Bureau of Economic Research
- Princeton University
- ?National Bureau of Economic Research (NBER)
- ?Princeton University - Department of Economics
Strategy in a nutshell
Universe: Chinese stocks (Shanghai and Shenzhen exchanges), excluding daily limit-hitting stocks. Sort stocks by prior one-day returns and construct a long-short portfolio: long top decile, short bottom decile. Portfolios are value-weighted and rebalanced daily.
Economic rationale
Daily momentum in China is driven by trading behavior of new investors. Noise trading from these investors creates short-term mispricing, explaining the daily price momentum effect and highlighting its role in emerging market dynamics.
Backtest performance
Annualised return57.96%
Volatility31.03%
Beta-0.051
Sharpe ratio1.81
Win rate50%
Full Python code
from AlgorithmImports import *
from typing import Dict, List
import data_tools
# endregion
class DailyMomentuminChineseEquities(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1) # Chinese data starts in 2015
self.SetCash(100000)
self.leverage:int = 5
self.quantile:int = 10
self.period:int = 2 + 1
self.data:Dict[Symbol, SymbolData] = {}
self.top_size_symbol_count:int = 300
ticker_file_str:str = self.Download('data.quantpedia.com/backtesting_data/equity/chinese_stocks/large_cap_500.csv')
tickers:List[str] = ticker_file_str.split('\r\n')[:self.top_size_symbol_count]
for t in tickers:
data = self.AddData(data_tools.ChineseStocks, t, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(self.leverage)
stock_symbol:Symbol = data.Symbol
self.data[stock_symbol] = data_tools.SymbolData(data.Symbol, self.period)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
def OnData(self, data: Slice) -> None:
price_last_update_date:Dict[Symbol, datetime.date] = data_tools.ChineseStocks.get_last_update_date()
asset_returns:Dict[Symbol, float] = {}
# store daily data
for symbol, symbol_data in self.data.items():
if symbol in data and data[symbol]:
price_data:dict[str, str] = data[symbol].GetProperty('price_data')
# valid price data
if data[symbol].Value != 0 and price_data:
close_price:float = data[symbol].Value
symbol_data.update_price(close_price)
mc:float = float(price_data['marketValue'])
symbol_data.update_market_cap(mc)
# calculate momentum
if symbol_data.is_ready() and self.Time.date() < price_last_update_date[symbol]:
asset_returns[symbol] = symbol_data.get_momentum()
weight:Dict[Symbol, float] = {}
# sort and divide to quantiles
if len(asset_returns) >= self.quantile:
sorted_stocks:List[Symbol] = sorted(asset_returns, key=asset_returns.get , reverse=True)
quantile:int = int(len(sorted_stocks) / self.quantile)
long:List[Symbol] = sorted_stocks[:quantile]
short:List[Symbol] = sorted_stocks[-quantile:]
# calculate weights based on market cap
for i, portfolio in enumerate([long, short]):
mc_sum:float = sum(list(map(lambda symbol: self.data[symbol].get_market_cap(), portfolio)))
for symbol in portfolio:
weight[symbol] = ((-1)**i) * self.data[symbol].get_market_cap() / mc_sum
# trade execution
invested:List[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in weight:
self.Liquidate(symbol)
for price_symbol, weight in weight.items():
if price_symbol in data and data[price_symbol]:
self.SetHoldings(price_symbol, weight)