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