| Overall Statistics |
|
Total Orders 7448 Average Win 0.27% Average Loss -0.15% Compounding Annual Return 90.350% Drawdown 27.300% Expectancy 0.689 Start Equity 100000 End Equity 3420280.22 Net Profit 3320.280% Sharpe Ratio 2.115 Sortino Ratio 2.617 Probabilistic Sharpe Ratio 98.585% Loss Rate 39% Win Rate 61% Profit-Loss Ratio 1.76 Alpha 0.508 Beta 1.037 Annual Standard Deviation 0.275 Annual Variance 0.075 Information Ratio 2.189 Tracking Error 0.233 Treynor Ratio 0.56 Total Fees $21970.87 Estimated Strategy Capacity $39000000.00 Lowest Capacity Asset BTAL UZWBUH9JN52D Portfolio Turnover 9.80% Drawdown Recovery 111 |
# =============================================================================
# Growth-v3 — 3-sleeve deployable book, ONE daily account, fund-of-funds netting.
# =============================================================================
# Sleeves + weights (gross sums to ~1.0 -> never borrows):
# gen263 momentum ............ 34% (CHAMP_gen263_momentum_friction)
# 531 momentum-breadth ....... 33% (S531 / #285 authoritative, BIL cash sweep)
# 529 / 504cg LETF engine .... 33% (S529 QuadEnsemble + QQQ velocity crash guard)
#
# NETTING (fund-of-funds): each sleeve WRITES an in-bucket target dict summing
# <= 1.0 within its bucket. The harness scales each dict by its sleeve weight and
# sums into ONE net weight per symbol:
# net[s] = 0.34*gen263[s] + 0.33*531[s] + 0.33*504cg[s]
# Max gross = 0.34 + 0.33 + 0.33 = 1.00 <= 1.0 (never borrows).
#
# EXECUTION: decision on T-close -> MarketOnOpenOrder -> fills T+1 open ONLY
# (no set_holdings / market_order = no look-ahead). Sells (exits + reductions)
# are submitted before buys. Risk-off / empty branches set that sleeve's dict = {}
# (531 sweeps to its BIL hedge), never a book-wide liquidate.
#
# Derived from Growth-v2 (research/book_swap_variants/GrowthV2.py): the A4
# reversal and kinfo regime sleeves were removed and the book re-weighted to
# 0.34/0.33/0.33. The gen263 + 531 + 504cg sleeves and the netting / MOO
# execution harness are kept VERBATIM.
# =============================================================================
from AlgorithmImports import *
from collections import defaultdict, deque
import numpy as np
import pandas as pd
# ---- shared fundamental universe (VERBATIM; serves gen263 + 531) ----
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
class GrowthV3(QCAlgorithm):
# -------- book weights (sum = 1.0) --------
W_263 = 0.34
W_531 = 0.33
W_504 = 0.33
DEBUG_FILLS = True
# -------- warmup --------
HISTORY_BARS = 1500
# -------- 504cg params (VERBATIM, S529 / S536) --------
QUARTER = 0.25
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
CRASH_EXIT = -0.04
CRASH_LOOKBACK = 10
CRASH_GROSS = 0.50
# -------- shared band params (gen263 + 531) --------
band_len = 189
hist_len = 126
adx_limit = 35
adx_period = 14
BOTTOM_LEVELS = {0, 1, 2, 3, 4}
# -------- gen263 params (VERBATIM) --------
lookbacks_263 = [21, 42, 63, 126, 189]
mom_weights_263 = [0.25, 0.20, 0.20, 0.20, 0.15]
stock_count_base = 10
stock_count_choppy = 5
choppy_threshold = 0.25
max_weight_263 = 0.20
# -------- 531 params (VERBATIM) --------
lookbacks_531 = [21, 63, 126, 189, 252]
stock_count_531 = 10
max_weight_531 = 0.20
# =====================================================================
def Initialize(self):
sy = int(self.get_parameter("start_year", "2010"))
sm = int(self.get_parameter("start_month", "1"))
sd = int(self.get_parameter("start_day", "1"))
ey = int(self.get_parameter("end_year", "2026"))
em = int(self.get_parameter("end_month", "6"))
ed = int(self.get_parameter("end_day", "26"))
self.SetStartDate(sy, sm, sd)
self.SetEndDate(ey, em, ed)
self.SetCash(100_000)
self.SetBrokerageModel(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.SetBenchmark("SPY")
self._n_fills = 0
self._max_moo = 0.0
self._n_moo = 0
self._max_liq = 0.0
self._n_liq = 0
self._n_inv = 0
self._max_gross = 0.0
res = Resolution.Daily
# ---- fixed-ETF universe: 504cg engine + 531 hedge (BIL), each added once ----
etf_504 = [
"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",
]
all_tickers = etf_504 + ["BIL"]
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._syms = self.syms # alias so verbatim 504cg code (self._syms) works
self.hedge_531 = self.syms["BIL"]
self._fixed_etfs = set(self.syms.values())
self._504cg_universe = set(self.syms.values())
# ---- 504cg indicators (VERBATIM S536) ----
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")
self._qqq_window = RollingWindow[float](self.CRASH_LOOKBACK + 1)
self._in_crash = False
self._504cg_targets = {}
self._504cg_last_label = ""
# ---- shared fundamental universe (gen263 + 531) ----
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.TOTAL_RETURN
self.SetUniverseSelection(SectorTopUniverse(self, blacklist={"GME", "AMC"}))
# shared per-stock state (price-derived, read by both momentum sleeves)
self.symbols = set()
self.symbol_to_sector = {}
self.ma = {}
self.adx = {}
self.stretch_max = {}
self.close_win = {}
self.stretch_ema = {}
self.current_band_idx = {}
# per-sleeve band-ceiling history (mutated independently by each sleeve)
self.band_hist_263 = {}
self.band_hist_531 = {}
# gen263 breadth / brake state
self.allow_universe_263 = True
self.was_risk_off_263 = False
self.max_stress_263 = 0.0
self.prev_bottom_frac_263 = None
self.portfolio_peak_263 = 100_000.0
# 531 breadth state
self.allow_universe_531 = True
self.was_risk_off_531 = False
self.max_stress_531 = 0.0
# ---- sleeve target dicts + dispatch flag ----
self.tgt_263 = {}
self.tgt_531 = {}
self._dirty = False
self.SetWarmUp(self.HISTORY_BARS, Resolution.Daily)
# gen263 + 531 monthly decisions at 15:55 -> set target dicts + dirty
self.Schedule.On(self.DateRules.MonthEnd("SPY"),
self.TimeRules.BeforeMarketClose("SPY", 5), self.Rebalance263)
self.Schedule.On(self.DateRules.MonthEnd("SPY"),
self.TimeRules.BeforeMarketClose("SPY", 5), self.Rebalance531)
# 504cg is decided daily in OnData.
# =====================================================================
# 504cg sleeve — readiness (VERBATIM S536)
@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 (VERBATIM S536)
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 sub-sleeve weight methods (each sums 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 {}
def _504cg_rebalance(self):
# Crash guard state machine (QQQ 10-day return gate; asymmetric hysteresis)
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
elif self._in_crash and qqq_ret > self.CRASH_EXIT:
self._in_crash = False
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
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
# =====================================================================
def OnSecuritiesChanged(self, changes):
for sec in changes.AddedSecurities:
s = sec.Symbol
if s in self._fixed_etfs:
# fixed ETFs: leave brokerage-default fee/slippage (sources' behavior)
continue
sec.SetFeeModel(InteractiveBrokersFeeModel())
sec.SetSlippageModel(ConstantSlippageModel(0.001))
self.symbols.add(s)
self.stretch_max[s] = 0.0
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_263[s] = RollingWindow[int](self.hist_len)
self.band_hist_531[s] = RollingWindow[int](self.hist_len)
try:
sector = sec.Fundamentals.AssetClassification.MorningstarSectorCode
if sector is not None and sector != 0:
self.symbol_to_sector[s] = sector
except Exception:
pass
for sec in changes.RemovedSecurities:
s = sec.Symbol
if s in self._fixed_etfs:
continue
self.symbols.discard(s)
self.ma.pop(s, None)
self.adx.pop(s, None)
self.stretch_max.pop(s, None)
self.stretch_ema.pop(s, None)
self.close_win.pop(s, None)
self.band_hist_263.pop(s, None)
self.band_hist_531.pop(s, None)
self.current_band_idx.pop(s, None)
self.symbol_to_sector.pop(s, None)
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
# =====================================================================
def OnData(self, data):
# 504cg crash-guard window (armed during warmup so guard is live at day 0)
qqq_sym = self.syms["QQQ"]
if data.Bars.ContainsKey(qqq_sym):
self._qqq_window.Add(data.Bars[qqq_sym].Close)
# shared per-stock band tracking (fixed Fibonacci bands)
for s in list(self.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)
if stretch > self.stretch_max[s]:
self.stretch_max[s] = 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)
if self.IsWarmingUp:
return
if self._504cg_rebalance():
self._dirty = True
if self._dirty:
self._execute_net()
self._dirty = False
# =====================================================================
# gen263 momentum sleeve (VERBATIM decision logic; writes tgt_263)
def Rebalance263(self):
if self.IsWarmingUp:
return
idxs = list(self.current_band_idx.values())
if len(idxs) < 50:
return
stock_bottom_frac = sum(i in self.BOTTOM_LEVELS for i in idxs) / len(idxs)
sector_bottoms = defaultdict(list)
for s, idx in self.current_band_idx.items():
sector = self.symbol_to_sector.get(s)
if sector is not None:
sector_bottoms[sector].append(idx in self.BOTTOM_LEVELS)
if sector_bottoms:
sector_stress_count = sum(
1 for flags in sector_bottoms.values()
if len(flags) > 0 and sum(flags) / len(flags) > 0.50
)
sector_bottom_frac = sector_stress_count / len(sector_bottoms)
bottom_frac = 0.5 * stock_bottom_frac + 0.5 * sector_bottom_frac
else:
bottom_frac = stock_bottom_frac
stress_roc = 0.0
if self.prev_bottom_frac_263 is not None:
stress_roc = bottom_frac - self.prev_bottom_frac_263
self.prev_bottom_frac_263 = bottom_frac
self.max_stress_263 = max(self.max_stress_263, bottom_frac)
# NOTE: fund-of-funds netting means TotalPortfolioValue is the WHOLE book;
# gen263's equity-DD brake therefore reads book-wide DD (conservative deviation).
current_value = self.Portfolio.TotalPortfolioValue
self.portfolio_peak_263 = max(self.portfolio_peak_263, current_value)
portfolio_dd = (self.portfolio_peak_263 - current_value) / self.portfolio_peak_263 if self.portfolio_peak_263 > 0 else 0.0
if bottom_frac >= 0.45:
self.allow_universe_263 = False
self.was_risk_off_263 = True
elif self.was_risk_off_263:
denominator = max(self.max_stress_263, 0.10)
improvement = (self.max_stress_263 - bottom_frac) / denominator
if improvement >= 0.60 or bottom_frac < 0.15:
for s in self.symbols:
if s in self.band_hist_263:
self.band_hist_263[s] = RollingWindow[int](self.hist_len)
self.allow_universe_263 = True
self.was_risk_off_263 = False
self.max_stress_263 = 0.0
self.prev_bottom_frac_263 = None
self.portfolio_peak_263 = current_value
self.in_recovery = True
self.recovery_months = 0
else:
self.allow_universe_263 = True
if not self.allow_universe_263:
self.tgt_263 = {}
self._dirty = True
return
stock_count = self.stock_count_choppy if bottom_frac >= self.choppy_threshold else self.stock_count_base
hist = self.History(list(self.symbols), max(self.lookbacks_263) + 1, Resolution.Daily)
if hist.empty:
return
closes = hist["close"].unstack(0)
momentum = {}
consistency_map = {}
for s in self.symbols:
if s not in closes:
continue
px = closes[s]
if len(px) < max(self.lookbacks_263) + 1:
continue
if not self.adx[s].IsReady or self.adx[s].Current.Value > self.adx_limit:
continue
lb_returns = [px.iloc[-1] / px.iloc[-lb - 1] - 1 for lb in self.lookbacks_263]
mom = sum(w * r for w, r in zip(self.mom_weights_263, lb_returns))
if not self.ma[s].IsReady:
continue
price = self.Securities[s].Price
ema = self.ma[s].Current.Value
if price <= ema:
continue
if mom > 0:
momentum[s] = mom
n_positive = sum(1 for r in lb_returns if r > 0)
consistency_map[s] = n_positive / len(self.lookbacks_263)
if not momentum:
self.tgt_263 = {}
self._dirty = True
return
top = sorted(momentum, key=momentum.get, reverse=True)[:stock_count]
scaled = {}
vol_20d = {}
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_263[s].Add(idx)
hist_idx = list(self.band_hist_263[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)
current_stretch = self.stretch_ema[s].Current.Value
peak_stretch = self.stretch_max.get(s, 0.0)
if idx >= 10 and peak_stretch > 0:
if current_stretch < (peak_stretch * 0.80):
scale = 0.2
consistency = consistency_map.get(s, 0.6)
consist_factor = 0.6 + 0.4 * consistency
scaled[s] = momentum[s] * scale * consist_factor
px = closes[s]
if len(px) >= 21:
vol_20d[s] = float(px.pct_change().iloc[-20:].std())
else:
vol_20d[s] = 0.015
if not scaled:
self.tgt_263 = {}
self._dirty = True
return
min_stress = 0.15
max_stress = 0.45
target_exposure = float(np.interp(bottom_frac, [min_stress, max_stress], [1.0, 0.0]))
if stress_roc >= 0.15:
target_exposure *= 0.70
if portfolio_dd > 0.07 and stress_roc > 0.03:
dd_brake = max(0.25, 1.0 - (portfolio_dd - 0.07) / 0.22)
target_exposure = min(target_exposure, dd_brake)
target_exposure = float(round(max(0.0, min(1.0, target_exposure)), 2))
total_scaled = sum(scaled.values())
raw_weights = {s: v / total_scaled for s, v in scaled.items()}
capped_weights = {s: min(self.max_weight_263, w) for s, w in raw_weights.items()}
for s in list(capped_weights.keys()):
vol = vol_20d.get(s, 0.015)
if vol > 0:
vol_cap_factor = min(1.0, 0.015 / vol)
capped_weights[s] = min(capped_weights[s], self.max_weight_263 * vol_cap_factor)
current_sum = sum(capped_weights.values())
final_weights = {}
if current_sum > 0:
for s, w in capped_weights.items():
final_weights[s] = (w / current_sum) * target_exposure
self.tgt_263 = {s: w for s, w in final_weights.items() if w > 0.0}
self._dirty = True
# =====================================================================
# 531 momentum-breadth sleeve (VERBATIM decision logic; writes tgt_531 incl. BIL)
def Rebalance531(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_531 = max(self.max_stress_531, bottom_frac)
if bottom_frac >= 0.45:
self.allow_universe_531 = False
self.was_risk_off_531 = True
elif self.was_risk_off_531:
denominator = max(self.max_stress_531, 0.10)
improvement = (self.max_stress_531 - bottom_frac) / denominator
if improvement >= 0.60 or bottom_frac < 0.15:
for s in self.symbols:
if s in self.band_hist_531:
self.band_hist_531[s] = RollingWindow[int](self.hist_len)
self.allow_universe_531 = True
self.was_risk_off_531 = False
self.max_stress_531 = 0.0
else:
self.allow_universe_531 = True
if not self.allow_universe_531:
self.tgt_531 = {self.hedge_531: 1.0}
self._dirty = True
return
hist = self.History(list(self.symbols), max(self.lookbacks_531) + 1, Resolution.Daily)
if hist.empty:
return
closes = hist["close"].unstack(0)
momentum = {}
for s in self.symbols:
if s not in closes:
continue
px = closes[s]
if len(px) < max(self.lookbacks_531) + 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_531])
if not self.ma[s].IsReady:
continue
price = self.Securities[s].Price
ema = self.ma[s].Current.Value
if price <= ema:
continue
if mom > 0:
momentum[s] = mom
if not momentum:
self.tgt_531 = {self.hedge_531: 1.0}
self._dirty = True
return
top = sorted(momentum, key=momentum.get, reverse=True)[:self.stock_count_531]
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_531[s].Add(idx)
hist_idx = list(self.band_hist_531[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)
current_stretch = self.stretch_ema[s].Current.Value
peak_stretch = self.stretch_max.get(s, 0.0)
if idx >= 10 and peak_stretch > 0 and current_stretch < (peak_stretch * 0.80):
scale = 0.2
scaled[s] = (momentum[s] * self.adx[s].Current.Value) * scale
if not scaled:
self.tgt_531 = {self.hedge_531: 1.0}
self._dirty = True
return
min_stress = 0.15
max_stress = 0.45
target_exposure = float(round(np.interp(bottom_frac, [min_stress, max_stress], [1.0, 0.0]), 2))
total_scaled = sum(scaled.values())
raw_weights = {s: v / total_scaled for s, v in scaled.items()}
capped_weights = {s: min(self.max_weight_531, w) for s, w in raw_weights.items()}
current_sum = sum(capped_weights.values())
final_weights = {}
if current_sum > 0:
for s, w in capped_weights.items():
final_weights[s] = (w / current_sum) * target_exposure
# sweep unallocated capital into the BIL yield hedge (within this sleeve's bucket)
hedge_allocation = round(1.0 - target_exposure, 2)
if hedge_allocation > 0:
final_weights[self.hedge_531] = hedge_allocation
self.tgt_531 = final_weights
self._dirty = True
# =====================================================================
# Fund-of-funds netting + T+1 MOO execution (sells before buys)
def _execute_net(self):
net = {}
for sym, w in self.tgt_263.items():
net[sym] = net.get(sym, 0.0) + self.W_263 * w
for sym, w in self.tgt_531.items():
net[sym] = net.get(sym, 0.0) + self.W_531 * w
for sym, w in self._504cg_targets.items():
net[sym] = net.get(sym, 0.0) + self.W_504 * w
net = {s: w for s, w in net.items() if w > 0.0005}
equity = self.Portfolio.TotalPortfolioValue
keep = set(net)
# EXIT de-targeted holdings first (sells before buys)
for kvp in list(self.Portfolio):
sym, h = kvp.Key, kvp.Value
if h.Invested and sym not in keep and h.Quantity != 0:
self.MarketOnOpenOrder(sym, -h.Quantity, tag=f"EXIT {self.Time.date()}")
def cur_w(sym):
return (float(self.Portfolio[sym].HoldingsValue) / equity) if equity > 0 else 0.0
# reductions (negative delta) sort ahead of increases -> sells before buys
for sym, w in sorted(net.items(), key=lambda kv: kv[1] - cur_w(kv[0])):
if not self.Securities.ContainsKey(sym):
continue
price = float(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, tag=f"NET w={w:.3f} {self.Time.date()}")
# =====================================================================
def OnOrderEvent(self, oe):
if not self.DEBUG_FILLS:
return
if oe.Status == OrderStatus.INVALID:
self._n_inv += 1
self.SetRuntimeStatistic("Invalid", str(self._n_inv))
msg = str(oe.Message)[:50] if oe.Message else "?"
self.SetRuntimeStatistic("InvMsg", f"{oe.Symbol.Value}:{msg}")
return
if oe.Status != OrderStatus.FILLED or oe.FillQuantity == 0:
return
sec = self.Securities[oe.Symbol]
fp = float(oe.FillPrice)
o = float(sec.Open)
dev = abs(fp - o) / o if o > 0 else 0.0
try:
od = self.Transactions.GetOrderById(oe.OrderId)
ot = int(od.Type) if od is not None else -1
except Exception:
ot = -1
if ot == 4:
self._max_moo = max(self._max_moo, dev)
self._n_moo += 1
else:
self._max_liq = max(self._max_liq, dev)
self._n_liq += 1
self._n_fills += 1
gross = sum(abs(p.HoldingsValue) for p in self.Portfolio.Values) / self.Portfolio.TotalPortfolioValue
self._max_gross = max(self._max_gross, gross)
self.SetRuntimeStatistic("Fills", str(self._n_fills))
self.SetRuntimeStatistic("MaxVsOpen_MOO_pct", f"{self._max_moo * 100:.4f}")
self.SetRuntimeStatistic("nMOO", str(self._n_moo))
self.SetRuntimeStatistic("nLiq", str(self._n_liq))
self.SetRuntimeStatistic("MaxGross_pct", f"{self._max_gross * 100:.1f}")