Quant BuffetRelax, Not Over Thinking

Lunar Monthly Effect in Chinese Stocks

Log in to collect

Academic paper

Strategy in a nutshell

The strategy invests in Chinese A-share stocks by going long on six major market indices at the start of Lunar January. Positions are value-weighted and held for the full lunar month (28 days) without rebalancing. The approach aims to capture short-term market gains associated with the Lunar New Year period.

Economic rationale

The Lunar January effect in Chinese markets is driven by seasonal patterns in trading volumes, buy proportions, and daily returns, especially in smaller firms. Significant macro events, such as the National People’s Congress and the Chinese People’s Political Consultative Conference (LIANGHUI), also positively impact stock performance. Traditional explanations like tax-loss selling or window dressing do not account for this effect in China.

Backtest performance

Annualised return4.46%
Volatility5.08%
Beta0.05
Sharpe ratio0.88
Sortino ratio-0.091
Win rate68%

Full Python code

from AlgorithmImports import *
from data_tools import CustomFeeModel, GregorianLunarDates, ChinaIndexData
# endregion

class LunarMonthlyEffectInChineseStocks(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

self.leverage:int = 5

self.liquidate_period:int = 28
self.liquidate_on_date_flag:bool = False
self.open_date:Union[datetime.date, None] = None
self.liquidate_date:Union[datetime.date, None] = None

self.fxi_flag:bool = True

data = self.AddEquity('FXI', Resolution.Daily) if self.fxi_flag else self.AddData(ChinaIndexData, 'HANG_SENG', Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(self.leverage)
self.china_market:Symbol = data.Symbol

self.gregorian_dates:Symbol = self.AddData(GregorianLunarDates, 'gregorian_lunar_dates', Resolution.Daily).Symbol

def OnData(self, data: Slice):
if self.gregorian_dates in data and data[self.gregorian_dates]:
    date:str = data[self.gregorian_dates].GetProperty('gregorian_date_end_first_lunar_month')
    self.liquidate_date = datetime.strptime(date, '%d.%m.%Y').date()
    self.open_date = self.Time.date()

    if self.Time.year != 2005: # data error prevention
        if self.Securities[self.china_market].Price != 0 and self.Securities[self.china_market].IsTradable:
            self.SetHoldings(self.china_market, 1)

if self.Portfolio.Invested:
    if self.liquidate_on_date_flag and self.Time.date() >= self.liquidate_date:
        self.Liquidate()

    elif not self.liquidate_on_date_flag and (self.Time.date() - self.open_date).days >= self.liquidate_period:
        self.Liquidate()