| Overall Statistics |
|
Total Orders 646 Average Win 0.82% Average Loss -0.18% Compounding Annual Return 21.779% Drawdown 32.800% Expectancy 2.149 Start Equity 10000 End Equity 29040.94 Net Profit 190.409% Sharpe Ratio 0.872 Sortino Ratio 1.09 Probabilistic Sharpe Ratio 57.923% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 4.50 Alpha 0.062 Beta 0.726 Annual Standard Deviation 0.136 Annual Variance 0.018 Information Ratio 0.423 Tracking Error 0.098 Treynor Ratio 0.163 Total Fees $524.00 Estimated Strategy Capacity $590000000.00 Lowest Capacity Asset UBER X4DDRW1HKLT1 Portfolio Turnover 0.73% Drawdown Recovery 546 |
"""
1-day cross-sectional reversal, top-100 US large-cap.
Mechanism: each month-end, pick the 10 stocks with the LOWEST current cross-
sectional rank of daily low (= relatively most-beaten-down today within universe).
Mcap-weight within the 10 picks, max 30% per name. Rebalance via MarketOnOpen.
Universe: top-100 by mcap, has_fundamental_data, price > $5, mcap > $5B,
listed on NYS/NAS/ASE, blacklist GME/AMC.
Account: IBKR-India CASH (no margin, no leverage). $10K starting capital.
Backtested:
IS 2010-2020: CAGR 20.8% / DD 18.3% / Sharpe 1.02
OOS 2021-2025: CAGR 24.0% / DD 32.2% / Sharpe 0.89 (single-touch consumed)
16Y 2010-2025 cash $10K: CAGR 17.7% / DD 25.9% / Sharpe 0.91 / WinRate 68%
vs SPY: ~+5pp CAGR with better DD, ~+0.25 Sharpe edge.
"""
from AlgorithmImports import *
from collections import defaultdict
import numpy as np
class TopMcapUniverse(FundamentalUniverseSelectionModel):
def __init__(self,algo,top_n=100,blacklist=None):
self.algo=algo; self.top_n=top_n; self.blacklist=set(blacklist or [])
super().__init__(self._select)
def _select(self,f_arr):
e=[]
for f in f_arr:
if not f.has_fundamental_data: continue
if f.symbol.Value in self.blacklist: continue
if f.company_reference.primary_exchange_id not in ("NYS","NAS","ASE"): continue
if f.price is None or f.price<=5: continue
if f.market_cap is None or f.market_cap<5_000_000_000: continue
e.append(f)
e.sort(key=lambda x: x.market_cap, reverse=True)
return [s.symbol for s in e[:self.top_n]]
class Alpha101_4_MCapWt(QCAlgorithm):
TOP_N=100; TOP_K=10; MAX_W=0.30; WIN=9
def Initialize(self):
self.SetStartDate(2021,1,1); self.SetEndDate(2026,12,31); self.SetCash(10_000)
self.SetBrokerageModel(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.CASH)
self.Settings.MinimumOrderMarginPortfolioPercentage=0.0; self.SetBenchmark("SPY")
self.UniverseSettings.Resolution=Resolution.Daily
self.UniverseSettings.DataNormalizationMode=DataNormalizationMode.TOTAL_RETURN
self.SetUniverseSelection(TopMcapUniverse(self,self.TOP_N,blacklist={"GME","AMC"}))
self.symbols=set(); self._pending=None
self.SetWarmUp(self.WIN+5)
self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.BeforeMarketClose("SPY",5), self.Rebalance)
def OnSecuritiesChanged(self,changes):
for sec in changes.AddedSecurities:
sec.SetFeeModel(InteractiveBrokersFeeModel()); sec.SetSlippageModel(ConstantSlippageModel(0.001))
self.symbols.add(sec.Symbol)
for sec in changes.RemovedSecurities: self.symbols.discard(sec.Symbol)
def OnData(self, data):
if self._pending is None or self.IsWarmingUp: return
targets=self._pending; self._pending=None
equity=self.Portfolio.TotalPortfolioValue
for pos in list(self.Portfolio.Values):
if pos.Invested and pos.Symbol not in targets and pos.Quantity!=0:
self.MarketOnOpenOrder(pos.Symbol,-pos.Quantity)
for sym,w in targets.items():
if w<=0 or not self.Securities.ContainsKey(sym): continue
price=self.Securities[sym].Price
if price<=0: continue
tq=int(equity*w/price)
cur=self.Portfolio[sym].Quantity if self.Portfolio.ContainsKey(sym) else 0
d=tq-cur
if d!=0: self.MarketOnOpenOrder(sym,d)
def Rebalance(self):
if self.IsWarmingUp or len(self.symbols)<20: return
hist=self.History(list(self.symbols),self.WIN+2,Resolution.Daily)
if hist.empty: return
lows=hist["low"].unstack(level=0)
cs_rank = lows.rank(axis=1, pct=True)
ts_rank = cs_rank.rolling(self.WIN).apply(lambda x: x.iloc[-1]/len(x) if len(x)>0 else 0).iloc[-1]
score = -ts_rank
score = score.dropna()
if score.empty: return
top=score.nlargest(self.TOP_K).index.tolist()
if not top: return
# MCap-weight the picks
mcaps={}
for s in top:
f=self.Securities[s].Fundamentals
if f is None or f.MarketCap is None: continue
mcaps[s]=float(f.MarketCap)
if not mcaps: return
total=sum(mcaps.values())
target={s: min(self.MAX_W, mcaps[s]/total) for s in mcaps}
sw=sum(target.values())
if sw>0:
target={s:v/sw for s,v in target.items()}
self._pending=target