| Overall Statistics |
|
Total Orders 5863 Average Win 0.47% Average Loss -0.29% Compounding Annual Return 156.432% Drawdown 26.000% Expectancy 0.581 Start Equity 100000 End Equity 11116745.12 Net Profit 11016.745% Sharpe Ratio 2.879 Sortino Ratio 3.746 Probabilistic Sharpe Ratio 99.852% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.62 Alpha 0.915 Beta 1.135 Annual Standard Deviation 0.346 Annual Variance 0.12 Information Ratio 3.015 Tracking Error 0.307 Treynor Ratio 0.877 Total Fees $89091.89 Estimated Strategy Capacity $22000000.00 Lowest Capacity Asset TYP U8JOSZGR4OKL Portfolio Turnover 17.28% Drawdown Recovery 103 |
"""
Deploy Book 75/25 — Crash-Guarded LETF Rotation + Sector-Neutral Momentum
==========================================================================
Single-algorithm deployment of a 2-sleeve long-only US equity book:
* 75% capital: crash-guarded 4-way leveraged-ETF rotation ensemble
(T11 + T10 + S3 + S2, each 25% of the 75% bucket,
+ a QQQ velocity crash guard that de-levers to 50%
gross when QQQ trailing 10-day return drops below -10%)
— this is the "Multi-Model Tactical ETF Rotation 504
with Velocity Crash Guard" engine
* 25% capital: sector-neutral large-cap trend momentum
(monthly rebalance, top-10 by multi-horizon momentum,
EMA-189 trend filter, ADX-35 cap, Fibonacci-band
ceiling sizing, breadth-based risk-off with 180-day
hard timeout)
— this is the "Sector-Neutral Large-Cap Trend Momentum
(Fixed)" engine with FIX 1-6 applied
Combined weights are aggregated daily and executed via MarketOnOpenOrder
for clean next-open fills (no look-ahead). Same code as the two standalone
publications; this file is the unified deploy artifact.
Account type: cash (IBKR), T+1 settlement modelled correctly.
Resolution: Daily.
Verified deploy numbers (friction-realistic, IBKR cash, daily-rebalanced
weighted blend of per-sleeve equity curves):
IS 2010-2020 : 29% CAGR / 34% DD / Sharpe 1.00
OOS 2021-2026: 70% CAGR / 20% DD / Sharpe 1.66 (Sharpe 2.30 on margin)
Walk-forward : 7/8 two-year windows positive, median 49% CAGR
"""
from AlgorithmImports import *
from collections import defaultdict, deque
import numpy as np
# ============================================================================
# 476 universe selector (sector-neutral top-by-market-cap large caps)
# ============================================================================
class SectorTopUniverse(FundamentalUniverseSelectionModel):
def __init__(self, algo, blacklist=None):
self.algo = algo
self.blacklist = set(blacklist or [])
super().__init__(self._select)
def _select(self, fundamentals):
buckets = defaultdict(list)
for f in fundamentals:
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
sector = f.asset_classification.morningstar_sector_code
if sector is None:
continue
buckets[sector].append(f)
symbols = []
for _, stocks in buckets.items():
stocks.sort(key=lambda x: x.market_cap, reverse=True)
symbols.extend(s.symbol for s in stocks[:100])
return symbols
# ============================================================================
# Unified deploy book
# ============================================================================
class DeployBook_75_25(QCAlgorithm):
# -------- Capital split between sleeves --------
W_504CG = 0.75
W_476 = 0.25
# -------- 504cg constants (QuadEnsemble) --------
QUARTER = 0.25 # each sub-sleeve gets 25% of the 504cg bucket
SVIX_LIVE = datetime(2022, 3, 30)
UVIX_LIVE = datetime(2022, 3, 30)
_T10_LATE = {"KMLM", "LABU", "QQQE", "VOOG", "VOOV"}
_T11_LATE = {"KMLM"}
CRASH_ENTRY = -0.10 # QQQ 10-day return -> de-lever
CRASH_EXIT = -0.04 # recover above -> resume
CRASH_LOOKBACK = 10
CRASH_GROSS = 0.50 # 50% deployed when in crash (rest cash)
# -------- 476 constants --------
LOOKBACKS = [21, 63, 126, 189, 252]
STOCK_COUNT = 10
MAX_WEIGHT_476 = 0.20
BAND_LEN = 189
HIST_LEN = 126
ADX_LIMIT = 35
ADX_PERIOD = 14
BOTTOM_LEVELS = {0, 1, 2, 3, 4}
RISK_OFF_THRESHOLD = 0.45
def Initialize(self):
self.SetStartDate(2021, 1, 1)
self.SetEndDate(2026, 1, 1)
self.SetCash(100_000)
self.SetBrokerageModel(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.SetBenchmark("SPY")
res = Resolution.Daily
# -------- 504cg manual universe --------
all_tickers = [
"TQQQ", "TECL", "SOXL", "SQQQ", "UVXY", "SVIX", "SVXY",
"XLK", "KMLM", "TLT",
"QQQE", "VTV", "VOX", "VOOG", "VOOV", "XLP", "XLY", "FAS",
"SPXL", "LABU",
"SPY", "IOO", "VTV", "XLF",
"TECS", "SOXS",
"QQQ", "PSQ", "QLD", "BTAL", "BIL", "AGG", "SH", "BND", "IEF",
"BSV",
"SMH", "UVIX",
]
seen = set()
unique = [t for t in all_tickers if not (t in seen or seen.add(t))]
self._syms = {t: self.AddEquity(t, res).Symbol for t in unique}
self._504cg_universe = set(self._syms.values())
# -------- 504cg indicators --------
def rsi10(t): return self.RSI(self._syms[t], 10, MovingAverageType.Wilders, res)
self._t10_rsi = {t: rsi10(t) for t in [
"QQQE", "VTV", "VOX", "TECL", "VOOG", "VOOV", "XLP",
"TQQQ", "XLY", "FAS", "SPY",
"SOXL", "SPXL", "LABU", "XLK", "KMLM",
]}
self._t11_rsi10 = {t: rsi10(t) for t in [
"SPY", "IOO", "TQQQ", "VTV", "XLF",
"XLK", "KMLM", "PSQ", "BND", "QQQ", "IEF",
]}
def rsi20(t): return self.RSI(self._syms[t], 20, MovingAverageType.Wilders, res)
self._t11_rsi20 = {t: rsi20(t) for t in ["TLT", "PSQ", "AGG"]}
self._t11_rsi60_sh = self.RSI(self._syms["SH"], 60, MovingAverageType.Wilders, res)
self._t11_spy_sma200 = self.SMA(self._syms["SPY"], 200, res)
self._t11_tqqq_sma20 = self.SMA(self._syms["TQQQ"], 20, res)
self._t11_kmlm_sma20 = self.SMA(self._syms["KMLM"], 20, res)
self._s2_tqqq_sma200 = self.SMA(self._syms["TQQQ"], 200, res)
self._s2_tqqq_sma20 = self.SMA(self._syms["TQQQ"], 20, res)
self._s2_tqqq_rsi10 = rsi10("TQQQ")
self._s2_soxl_rsi10 = rsi10("SOXL")
self._s2_sqqq_rsi10 = rsi10("SQQQ")
self._s2_bsv_rsi10 = rsi10("BSV")
self._s3_spy_sma202 = self.SMA(self._syms["SPY"], 202, res)
self._s3_qqq_sma202 = self.SMA(self._syms["QQQ"], 202, res)
self._s3_smh_sma202 = self.SMA(self._syms["SMH"], 202, res)
self._s3_soxl_sma202 = self.SMA(self._syms["SOXL"], 202, res)
def rsi8(t): return self.RSI(self._syms[t], 8, MovingAverageType.Wilders, res)
def rsi15(t): return self.RSI(self._syms[t], 15, MovingAverageType.Wilders, res)
self._s3_rsi_qqq8 = rsi8("QQQ")
self._s3_rsi_smh8 = rsi8("SMH")
self._s3_rsi_spy15 = rsi15("SPY")
self._s3_rsi_qqq15 = rsi15("QQQ")
self._s3_rsi_smh15 = rsi15("SMH")
self._s3_rsi_soxl15 = rsi15("SOXL")
# -------- 504cg state --------
self._qqq_window = RollingWindow[float](self.CRASH_LOOKBACK + 1)
self._in_crash = False
self._504cg_targets = {} # current 504cg target weight dict (within 504cg's 75% bucket)
self._504cg_last_label = ""
# -------- 476 universe + state --------
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.TOTAL_RETURN
self.SetUniverseSelection(SectorTopUniverse(self, blacklist={"GME", "AMC"}))
self._476_symbols = set()
self._476_targets = {} # current 476 target weight dict (within 476's 25% bucket)
# 476 per-symbol state
self.ma = {}
self.adx = {}
self.close_win = {}
self.stretch_ema = {}
self.band_hist = {}
self.stretch_win = {}
# 476 breadth state
self.current_band_idx = {}
self.allow_universe = True
self.max_stress_level = 0.0
self.was_risk_off = False
self.risk_off_date = None
# Combined execution
self._pending_weights = None
self.SetWarmUp(300)
# 476 monthly rebalance
self.Schedule.On(
self.DateRules.MonthEnd("SPY"),
self.TimeRules.BeforeMarketClose("SPY", 5),
self.Rebalance_476
)
# ========================================================================
# OnSecuritiesChanged — route 504cg ETFs vs 476 stocks
# ========================================================================
def OnSecuritiesChanged(self, changes):
for sec in changes.AddedSecurities:
sec.SetFeeModel(InteractiveBrokersFeeModel())
sec.SetSlippageModel(ConstantSlippageModel(0.001))
s = sec.Symbol
# 504cg ETFs are pre-added in Initialize — already handled
if s in self._504cg_universe:
continue
# New 476 universe stock — set up its indicators
self._476_symbols.add(s)
self.ma[s] = self.EMA(s, self.BAND_LEN, Resolution.Daily)
self.adx[s] = self.ADX(s, self.ADX_PERIOD, Resolution.Daily)
self.stretch_ema[s] = self.EMA(s, self.BAND_LEN, Resolution.Daily)
self.close_win[s] = RollingWindow[float](self.BAND_LEN)
self.band_hist[s] = RollingWindow[int](self.HIST_LEN)
self.stretch_win[s] = RollingWindow[float](self.HIST_LEN)
for sec in changes.RemovedSecurities:
s = sec.Symbol
if s in self._504cg_universe:
continue
self._476_symbols.discard(s)
self.ma.pop(s, None)
self.adx.pop(s, None)
self.stretch_ema.pop(s, None)
self.close_win.pop(s, None)
self.band_hist.pop(s, None)
self.current_band_idx.pop(s, None)
self.stretch_win.pop(s, None)
# ========================================================================
# 504cg sleeve readiness checks
# ========================================================================
@property
def _t10_ready(self):
core = [v for k, v in self._t10_rsi.items() if k not in self._T10_LATE]
return not self.IsWarmingUp and all(r.IsReady for r in core)
@property
def _t11_ready(self):
core10 = [v for k, v in self._t11_rsi10.items() if k not in self._T11_LATE]
return (not self.IsWarmingUp
and self._t11_spy_sma200.IsReady
and self._t11_tqqq_sma20.IsReady
and all(r.IsReady for r in core10)
and all(r.IsReady for r in self._t11_rsi20.values())
and self._t11_rsi60_sh.IsReady)
@property
def _s2_ready(self):
return (not self.IsWarmingUp
and self._s2_tqqq_sma200.IsReady
and self._s2_tqqq_sma20.IsReady
and self._s2_tqqq_rsi10.IsReady
and self._s2_soxl_rsi10.IsReady
and self._s2_sqqq_rsi10.IsReady
and self._s2_bsv_rsi10.IsReady)
@property
def _s3_ready(self):
return (not self.IsWarmingUp
and self._s3_spy_sma202.IsReady
and self._s3_qqq_sma202.IsReady
and self._s3_smh_sma202.IsReady
and self._s3_soxl_sma202.IsReady
and self._s3_rsi_qqq8.IsReady
and self._s3_rsi_smh8.IsReady
and self._s3_rsi_spy15.IsReady
and self._s3_rsi_qqq15.IsReady
and self._s3_rsi_smh15.IsReady
and self._s3_rsi_soxl15.IsReady)
# ========================================================================
# 504cg T11 helpers
# ========================================================================
def _t11_bond_baller(self, r10, r20, tqqq_px, tqqq_sma):
if r20["TLT"] > r20["PSQ"]: return "QQQ"
if tqqq_px > tqqq_sma:
if r10["PSQ"] < 35: return "PSQ"
if r20["AGG"] > self._t11_rsi60_sh.Current.Value: return "TQQQ"
return "PSQ"
else:
if r10["IEF"] > r20["PSQ"]: return "PSQ"
return "SQQQ"
def _t11_feaver_bear(self, r10, r20, tqqq_px, tqqq_sma):
hist = self.History(self._syms["QQQ"], 61, Resolution.Daily)
qqq_60d = 0.0
if not hist.empty and len(hist) >= 61:
c = hist["close"].values
qqq_60d = (c[-1] / c[0] - 1) * 100
if qqq_60d < -12:
if r10["BND"] > r10["QQQ"]: return "QLD"
return "BTAL"
if tqqq_px > tqqq_sma:
if r10["PSQ"] < 35: return "PSQ"
if r20["AGG"] > self._t11_rsi60_sh.Current.Value: return "TQQQ"
return "PSQ"
else:
if r10["IEF"] > r20["PSQ"]: return "PSQ"
return "SQQQ"
# ========================================================================
# 504cg sleeve weight methods (each returns dict summing to QUARTER=0.25
# of the 504cg bucket)
# ========================================================================
def _t10_weights(self):
if not self._t10_ready: return {}
r = {t: self._t10_rsi[t].Current.Value for t in self._t10_rsi if self._t10_rsi[t].IsReady}
if (r.get("QQQE",0)>79 or r.get("VTV",0)>79 or r.get("VOX",0)>79
or r.get("TECL",0)>79 or r.get("VOOG",0)>79 or r.get("VOOV",0)>79
or r.get("XLP",0)>75 or r.get("TQQQ",0)>79 or r.get("XLY",0)>80
or r.get("FAS",0)>80 or r.get("SPY",0)>80):
return {self._syms["UVXY"]: self.QUARTER}
if r.get("TQQQ",50) < 30: return {self._syms["TECL"]: self.QUARTER}
if r.get("SOXL",50) < 30: return {self._syms["SOXL"]: self.QUARTER}
if r.get("SPXL",50) < 30: return {self._syms["SPXL"]: self.QUARTER}
if self._t10_rsi["LABU"].IsReady and r["LABU"] < 25:
return {self._syms["LABU"]: self.QUARTER}
vs = "SVIX" if self.Time >= self.SVIX_LIVE else "SVXY"
kmlm_ready = self._t10_rsi["KMLM"].IsReady
xlk_wins = (not kmlm_ready) or (r["XLK"] > r["KMLM"])
if xlk_wins:
return {self._syms["TECL"]: self.QUARTER/3,
self._syms["SOXL"]: self.QUARTER/3,
self._syms[vs]: self.QUARTER/3}
return {self._syms["SQQQ"]: self.QUARTER*0.5,
self._syms["TLT"]: self.QUARTER*0.5}
def _t11_weights(self):
if not self._t11_ready: return {}
r10 = {t: self._t11_rsi10[t].Current.Value for t in self._t11_rsi10 if self._t11_rsi10[t].IsReady}
r20 = {t: self._t11_rsi20[t].Current.Value for t in self._t11_rsi20}
spy_px = self.Securities[self._syms["SPY"]].Close
tqqq_px = self.Securities[self._syms["TQQQ"]].Close
kmlm_px = self.Securities[self._syms["KMLM"]].Close
spy_sma = self._t11_spy_sma200.Current.Value
tqqq_sma = self._t11_tqqq_sma20.Current.Value
kmlm_sma = self._t11_kmlm_sma20.Current.Value
ob79 = (r10["SPY"]>79 or r10["IOO"]>79 or r10["TQQQ"]>79
or r10["VTV"]>79 or r10["XLF"]>79)
if ob79:
ob81 = (r10["SPY"]>81 or r10["IOO"]>81 or r10["TQQQ"]>81
or r10["VTV"]>81 or r10["XLF"]>81)
if ob81:
return {self._syms["UVXY"]: self.QUARTER}
return {self._syms["UVXY"]: self.QUARTER/3,
self._syms["BIL"]: self.QUARTER/3,
self._syms["BTAL"]: self.QUARTER/3}
if r10["TQQQ"] < 30: return {self._syms["TQQQ"]: self.QUARTER}
if r10["SPY"] < 30: return {self._syms["SPXL"]: self.QUARTER}
if spy_px > spy_sma:
kmlm_ready = self._t11_rsi10["KMLM"].IsReady and self._t11_kmlm_sma20.IsReady
if not kmlm_ready or r10["XLK"] > r10["KMLM"]:
return {self._syms["TECL"]: self.QUARTER/3,
self._syms["SOXL"]: self.QUARTER/3,
self._syms["TQQQ"]: self.QUARTER/3}
if kmlm_px < kmlm_sma:
return {self._syms["TECL"]: self.QUARTER/3,
self._syms["SOXL"]: self.QUARTER/3,
self._syms["TQQQ"]: self.QUARTER/3}
return {self._syms["TECS"]: self.QUARTER/3,
self._syms["SOXS"]: self.QUARTER/3,
self._syms["SQQQ"]: self.QUARTER/3}
else:
bb = self._t11_bond_baller(r10, r20, tqqq_px, tqqq_sma)
fb = self._t11_feaver_bear(r10, r20, tqqq_px, tqqq_sma)
w = {}
w[self._syms[bb]] = w.get(self._syms[bb], 0) + self.QUARTER * 0.5
w[self._syms[fb]] = w.get(self._syms[fb], 0) + self.QUARTER * 0.5
return w
def _s2_weights(self):
if not self._s2_ready: return {}
tqqq_price = self.Securities[self._syms["TQQQ"]].Close
tqqq_rsi = self._s2_tqqq_rsi10.Current.Value
soxl_rsi = self._s2_soxl_rsi10.Current.Value
sqqq_rsi = self._s2_sqqq_rsi10.Current.Value
bsv_rsi = self._s2_bsv_rsi10.Current.Value
sma200 = self._s2_tqqq_sma200.Current.Value
sma20 = self._s2_tqqq_sma20.Current.Value
if tqqq_price > sma200:
sym = self._syms["UVXY"] if tqqq_rsi > 79 else self._syms["TQQQ"]
return {sym: self.QUARTER}
if tqqq_rsi < 31: return {self._syms["TECL"]: self.QUARTER}
if soxl_rsi < 30: return {self._syms["SOXL"]: self.QUARTER}
if tqqq_price < sma20:
sym = self._syms["SQQQ"] if sqqq_rsi > bsv_rsi else self._syms["BSV"]
return {sym: self.QUARTER}
return {self._syms["TQQQ"]: self.QUARTER}
def _s3_weights(self):
if not self._s3_ready: return {}
spy_bull = self.Securities[self._syms["SPY"]].Price > self._s3_spy_sma202.Current.Value
qqq_bull = self.Securities[self._syms["QQQ"]].Price > self._s3_qqq_sma202.Current.Value
smh_bull = self.Securities[self._syms["SMH"]].Price > self._s3_smh_sma202.Current.Value
soxl_bull = self.Securities[self._syms["SOXL"]].Price > self._s3_soxl_sma202.Current.Value
bull = (int(spy_bull)+int(qqq_bull)+int(smh_bull)+int(soxl_bull)) >= 3
overbought = (self._s3_rsi_spy15.Current.Value > 72 or
self._s3_rsi_qqq15.Current.Value > 72 or
self._s3_rsi_smh15.Current.Value > 72 or
self._s3_rsi_soxl15.Current.Value > 72)
if bull:
if overbought:
vol = (self._syms["UVIX"]
if (self.Time >= self.UVIX_LIVE
and self.Securities[self._syms["UVIX"]].HasData
and self.Securities[self._syms["UVIX"]].Price > 0)
else self._syms["UVXY"])
return {vol: self.QUARTER}
return {self._syms["TQQQ"]: self.QUARTER*0.5,
self._syms["SOXL"]: self.QUARTER*0.5}
if self._s3_rsi_qqq8.Current.Value < 29 or self._s3_rsi_smh8.Current.Value < 31:
return {self._syms["SOXL"]: self.QUARTER}
return {}
# ========================================================================
# 504cg daily rebalance (called from OnData)
# ========================================================================
def _504cg_rebalance(self):
# Crash guard state machine
if self._qqq_window.IsReady:
qqq_ret = self._qqq_window[0] / self._qqq_window[self.CRASH_LOOKBACK] - 1.0
if not self._in_crash and qqq_ret < self.CRASH_ENTRY:
self._in_crash = True
self.Log(f"[CRASH] {self.Time.date()} QQQ 10d={qqq_ret*100:.1f}% de-lever to {self.CRASH_GROSS:.0%}")
elif self._in_crash and qqq_ret > self.CRASH_EXIT:
self._in_crash = False
self.Log(f"[RECOVER] {self.Time.date()} QQQ 10d={qqq_ret*100:.1f}% resume full")
crash_gross = self.CRASH_GROSS if self._in_crash else 1.0
w10 = self._t10_weights()
w11 = self._t11_weights()
w2 = self._s2_weights()
w3 = self._s3_weights()
def lbl(w): return "+".join(f"{round(wt/self.QUARTER*100):.0f}%{s.Value}"
for s, wt in w.items()) if w else "CASH"
new_label = f"T10={lbl(w10)}|T11={lbl(w11)}|S2={lbl(w2)}|S3={lbl(w3)}|cg={crash_gross}"
if new_label == self._504cg_last_label:
return False # no change
combined = {}
for w in [w10, w11, w2, w3]:
for sym, wt in w.items():
combined[sym] = combined.get(sym, 0.0) + wt * crash_gross
self._504cg_targets = combined
self._504cg_last_label = new_label
return True # changed
# ========================================================================
# 476 monthly rebalance (scheduled)
# ========================================================================
def Rebalance_476(self):
if self.IsWarmingUp:
return
idxs = list(self.current_band_idx.values())
if len(idxs) < 50:
return
bottom_frac = sum(i in self.BOTTOM_LEVELS for i in idxs) / len(idxs)
self.max_stress_level = max(self.max_stress_level, bottom_frac)
if bottom_frac >= self.RISK_OFF_THRESHOLD:
if not self.was_risk_off:
self.risk_off_date = self.Time
self.allow_universe = False
self.was_risk_off = True
elif self.was_risk_off:
denominator = max(self.max_stress_level, 0.10)
improvement = (self.max_stress_level - bottom_frac) / denominator
days_risk_off = (self.Time - self.risk_off_date).days if self.risk_off_date else 0
if improvement >= 0.60 or bottom_frac < 0.15 or days_risk_off > 180:
for s in self._476_symbols:
if s in self.band_hist:
self.band_hist[s] = RollingWindow[int](self.HIST_LEN)
self.allow_universe = True
self.was_risk_off = False
self.max_stress_level = 0.0
self.risk_off_date = None
else:
self.allow_universe = True
if not self.allow_universe:
self._476_targets = {}
self._update_combined()
return
hist = self.History(list(self._476_symbols), max(self.LOOKBACKS) + 1, Resolution.Daily)
if hist.empty:
return
closes = hist["close"].unstack(0)
momentum = {}
for s in self._476_symbols:
if s not in closes:
continue
px = closes[s]
if len(px) < max(self.LOOKBACKS) + 1:
continue
if not self.adx[s].IsReady or self.adx[s].Current.Value > self.ADX_LIMIT:
continue
mom = np.mean([px.iloc[-1] / px.iloc[-lb - 1] - 1 for lb in self.LOOKBACKS])
if not self.ma[s].IsReady:
continue
price = self.Securities[s].Price
ema = self.ma[s].Current.Value
if price <= ema:
continue
fundamentals = self.Securities[s].Fundamentals
if fundamentals is None or fundamentals.MarketCap < 5_000_000_000:
continue
if mom > 0:
momentum[s] = mom
if not momentum:
self._476_targets = {}
self._update_combined()
return
top = sorted(momentum, key=momentum.get, reverse=True)[:self.STOCK_COUNT]
scaled = {}
for s in top:
if not self.ma[s].IsReady or not self.stretch_ema[s].IsReady:
continue
dev = np.std(list(self.close_win[s]))
if dev <= 0:
continue
mid = self.ma[s].Current.Value
lm = self.stretch_ema[s].Current.Value
lm2 = lm / 2.0
lm3 = lm2 * 0.38196601
lm4 = lm * 1.38196601
lm5 = lm * 1.61803399
lm6 = (lm + lm2) / 2.0
bands = [
mid - dev * lm5, mid - dev * lm4, mid - dev * lm,
mid - dev * lm6, mid - dev * lm2, mid - dev * lm3,
mid,
mid + dev * lm3, mid + dev * lm2, mid + dev * lm6,
mid + dev * lm, mid + dev * lm4, mid + dev * lm5
]
price = self.Securities[s].Price
idx = self._band_index(price, bands)
self.band_hist[s].Add(idx)
hist_idx = list(self.band_hist[s])
historical_high = max(hist_idx) if hist_idx else idx
if historical_high <= 0:
scale = 1.0
elif idx >= historical_high:
scale = 0.0
else:
scale = max(0.2, 1.0 - idx / historical_high)
if self.stretch_win[s].IsReady:
stretch_list = list(self.stretch_win[s])
current_stretch = stretch_list[0]
peak_stretch = max(stretch_list)
if idx >= 10 and peak_stretch > 0:
if current_stretch < (peak_stretch * 0.80):
scale = min(scale, 0.2)
scaled[s] = momentum[s] * scale
if not scaled:
self._476_targets = {}
self._update_combined()
return
total_scaled = sum(scaled.values())
raw_weights = {s: v / total_scaled for s, v in scaled.items()}
capped_weights = {s: min(self.MAX_WEIGHT_476, w) for s, w in raw_weights.items()}
current_sum = sum(capped_weights.values())
if current_sum > 0:
self._476_targets = {s: w / current_sum for s, w in capped_weights.items()}
else:
self._476_targets = {}
self._update_combined()
def _band_index(self, price, bands):
for i in range(len(bands) - 1):
if bands[i] <= price < bands[i + 1]:
return i
return len(bands) - 2
# ========================================================================
# Combined weights aggregator + execution
# ========================================================================
def _update_combined(self):
# 504cg targets already include crash_gross scaling and sum to ~1.0
# 476 targets sum to 1.0 (or 0.0 if risk-off)
all_syms = set(self._504cg_targets) | set(self._476_targets)
combined = {}
for s in all_syms:
w = (self.W_504CG * self._504cg_targets.get(s, 0.0)
+ self.W_476 * self._476_targets.get(s, 0.0))
if w > 0:
combined[s] = w
self._pending_weights = combined
def OnData(self, data):
# Feed 504cg crash-guard QQQ window (also during warmup)
qqq_sym = self._syms["QQQ"]
if data.Bars.ContainsKey(qqq_sym):
self._qqq_window.Add(data.Bars[qqq_sym].Close)
if self.IsWarmingUp:
return
# 504cg daily signal update
if self._504cg_rebalance():
self._update_combined()
# 476 breadth-band tracking (per-day per-stock band index)
for s in list(self._476_symbols):
if not data.ContainsKey(s):
continue
bar = data[s]
if bar is None:
continue
close = bar.Close
self.close_win[s].Add(close)
if not self.close_win[s].IsReady or not self.ma[s].IsReady:
continue
dev = np.std(list(self.close_win[s]))
if dev <= 0:
continue
mid = self.ma[s].Current.Value
stretch = abs(close - mid) / dev
self.stretch_ema[s].Update(self.Time, stretch)
self.stretch_win[s].Add(stretch)
bands = [
mid - dev * 1.618, mid - dev * 1.382, mid - dev,
mid - dev * 0.809, mid - dev * 0.5, mid - dev * 0.382,
mid,
mid + dev * 0.382, mid + dev * 0.5, mid + dev * 0.809,
mid + dev, mid + dev * 1.382, mid + dev * 1.618
]
self.current_band_idx[s] = self._band_index(close, bands)
# Drain pending combined weights via MarketOnOpenOrder
if self._pending_weights is not None:
targets = self._pending_weights
self._pending_weights = None
equity = self.Portfolio.TotalPortfolioValue
# Liquidate anything not in target set
for pos in list(self.Portfolio.Values):
if pos.Invested and pos.Symbol not in targets:
if pos.Quantity != 0:
self.MarketOnOpenOrder(pos.Symbol, -pos.Quantity)
# Set target positions
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
target_qty = int(equity * w / price)
cur = self.Portfolio[sym].Quantity if self.Portfolio.ContainsKey(sym) else 0
delta = target_qty - cur
if delta != 0:
self.MarketOnOpenOrder(sym, delta)