Skewness and 52-Week Highs in China
Log in to collectAcademic paper
The Role of Anchoring on Investors' Gambling Preference: Evidence from China
Zhuo Wang; Ziyue Wang; Ke Wu
- Renmin University of China
- ?Renmin University of China - School of Finance
Strategy in a nutshell
The strategy invests in Chinese A-share stocks by calculating each stock’s total skewness and distance from its 52-week high. Stocks are double-sorted into 25 portfolios based on these metrics. The final portfolio goes long low-skewness stocks far from their 52-week high and shorts high-skewness stocks. Portfolios are value-weighted and rebalanced monthly.
Economic rationale
Lottery-like stocks with high skewness tend to underperform when priced far below their 52-week high due to behavioral biases such as anchoring and under-reaction to news. The strategy exploits this predictable mispricing to generate alpha, as confirmed by Fama-MacBeth regressions.
Backtest performance
Annualised return13.08%
Volatility11.49%
Beta0.106
Sharpe ratio1.14
Win rate53%
Full Python code
from AlgorithmImports import *
from data_tools import CustomFeeModel, SymbolData, ChineseStocks
# endregion
class SkewnessAnd52WeekHighsInChina(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
# chinese stock universe
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')
self.tickers:List[str] = ticker_file_str.split('\r\n')[:self.top_size_symbol_count]
self.quantile:int = 10
self.leverage:int = 5
self.max_missing_days:int = 5
self.period:int = 52 * 5 # daily period
self.SetWarmUp(self.period, Resolution.Daily)
self.data:dict[str, SymbolData] = {}
for t in self.tickers:
data = self.AddData(ChineseStocks, t, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(self.leverage)
self.data[data.Symbol] = SymbolData(self.period)
self.recent_month:int = -1
def OnData(self, data: Slice):
curr_date:datetime.date = self.Time.date()
# store daily data
for symbol, symbol_data in self.data.items():
if data.ContainsKey(symbol):
price_data:dict[str, str] = data[symbol].GetProperty('price_data')
# valid price data
if data[symbol].Value != 0. and price_data:
# update price and market cap
close:float = float(data[symbol].Value)
symbol_data.update(curr_date, close)
mc:float = float(price_data['marketValue'])
symbol_data.update_market_cap(mc)
if self.IsWarmingUp: return
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
W52:dict[Symbol, float] = {}
SKEWNESS:dict[Symbol, float] = {}
for symbol, symbol_data in self.data.items():
if not symbol_data.data_still_coming(curr_date, self.max_missing_days):
symbol_data.reset_data()
continue
if symbol_data.is_ready():
W52[symbol] = symbol_data.get_W52_value()
SKEWNESS[symbol] = symbol_data.get_SKEWNESS_value()
symbol_data.reset_monthly_closes()
if len(W52) < self.quantile:
self.Liquidate()
return
# sort by skew and nearness
quantile:int = int(len(W52) / self.quantile)
sorted_W52:list[Symbol] = [x[0] for x in sorted(W52.items(), key=lambda item: item[1])]
sorted_SKEWNESS:list[Symbol] = [x[0] for x in sorted(SKEWNESS.items(), key=lambda item: item[1])]
farthest_W52:list[Symbol] = sorted_W52[:quantile]
lowest_SKEWNESS:list[Symbol] = sorted_SKEWNESS[:quantile]
highest_SKEWNESS:list[Symbol] = sorted_SKEWNESS[-quantile:]
long_leg:list[Symbol] = [symbol for symbol in farthest_W52 if symbol in lowest_SKEWNESS]
short_leg:list[Symbol] = [symbol for symbol in farthest_W52 if symbol in highest_SKEWNESS]
# trade execution
invested:list[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long_leg + short_leg:
self.Liquidate(symbol)
total_long_cap:float = sum(list(map(lambda symbol: self.data[symbol].get_market_cap(), long_leg)))
for symbol in long_leg:
self.SetHoldings(symbol, self.data[symbol].get_market_cap() / total_long_cap)
total_short_cap:float = sum(list(map(lambda symbol: self.data[symbol].get_market_cap(), short_leg)))
for symbol in short_leg:
self.SetHoldings(symbol, -self.data[symbol].get_market_cap() / total_short_cap)