| Overall Statistics |
|
Total Orders 4359 Average Win 0.28% Average Loss -0.27% Compounding Annual Return 27.894% Drawdown 21.900% Expectancy 0.210 Start Equity 100000 End Equity 342332.73 Net Profit 242.333% Sharpe Ratio 0.898 Sortino Ratio 1.098 Probabilistic Sharpe Ratio 50.731% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.04 Alpha 0.136 Beta 0.606 Annual Standard Deviation 0.188 Annual Variance 0.035 Information Ratio 0.647 Tracking Error 0.176 Treynor Ratio 0.279 Total Fees $5202.75 Estimated Strategy Capacity $3900000.00 Lowest Capacity Asset GRMN S0DPIYB0VD5X Portfolio Turnover 17.25% Drawdown Recovery 290 |
from AlgorithmImports import *
from datetime import timedelta
class FractalMomentumCascade(QCAlgorithm):
"""
Fractal momentum cascade — multi-timeframe momentum coherence.
COMPLETELY INVENTED SIGNAL:
Momentum Coherence Score (MCS) — requires momentum to be positive
across THREE timeframes simultaneously:
- Short: ROC(10) — 2-week momentum
- Medium: ROC(21) — monthly momentum
- Long: ROC(63) — quarterly momentum
MCS = (ROC10 × ROC21 × ROC63) ^ (1/3)
Gate: all three ROC > 0, Sharpe(126) > 0, price > SMA(200).
Ranking: MCS / volatility.
Universe: top-300 liquid $2B+ caps, PE > 0, ROE > 0, net margin > 0.
Regime: SPY above 200-day SMA.
Sizing: inverse-vol weighted top-25 at 2x leverage.
Execution: biweekly rebalance Monday 31 min after open.
"""
COARSE_SIZE = 800
FINE_SIZE = 300
MIN_PRICE = 10.0
MIN_DOLLAR_VOL = 10e6
MIN_MARKET_CAP = 2e9
ROC_SHORT = 10
ROC_MED = 21
ROC_LONG = 63
SMA_PERIOD = 200
SHARPE_PERIOD = 126
VOL_PERIOD = 21
TOP_N = 25
REGIME_PERIOD = 200
MAX_GROSS = 2.0
MAX_SINGLE = 0.20
def initialize(self):
self.set_start_date(self.end_date - timedelta(5 * 365))
self.set_cash(100_000)
self.settings.minimum_order_margin_portfolio_percentage = 0
self.settings.automatic_indicator_warm_up = True
self.settings.seed_initial_prices = True
self.set_warm_up(timedelta(days=400))
self._spy = self.add_equity("SPY")
self._spy_sma = self.sma(
self._spy, self.REGIME_PERIOD, Resolution.DAILY
)
self._safe_haven = self.add_equity("GLD")
self.universe_settings.resolution = Resolution.MINUTE
self.universe_settings.schedule.on(
self.date_rules.month_start(self._spy)
)
self._universe = self.add_universe(self._fundamental_filter)
def on_warmup_finished(self):
time_rule = self.time_rules.after_market_open(self._spy, 31)
self.schedule.on(
self.date_rules.week_start(self._spy),
time_rule,
self._weekly_check,
)
if self.live_mode:
self._weekly_check()
else:
self.schedule.on(
self.date_rules.today, time_rule,
lambda: self._weekly_check(True),
)
# ── universe ─────────────────────────────────────────────
def _fundamental_filter(self, fundamental):
pool = [
f for f in fundamental
if (f.has_fundamental_data and
f.price > self.MIN_PRICE and
f.dollar_volume > self.MIN_DOLLAR_VOL and
f.market_cap >= self.MIN_MARKET_CAP and
f.valuation_ratios.pe_ratio > 0 and
f.operation_ratios.roe.value > 0 and
f.operation_ratios.net_margin.value > 0)
]
pool = sorted(pool, key=lambda f: f.dollar_volume)[-self.COARSE_SIZE:]
pool = sorted(pool, key=lambda f: f.market_cap)[-self.FINE_SIZE:]
return [f.symbol for f in pool]
# ── security lifecycle ───────────────────────────────────
def on_securities_changed(self, changes):
for sec in changes.added_securities:
if sec == self._spy:
continue
sec.sma = self.sma(sec, self.SMA_PERIOD, Resolution.DAILY)
sec.sharpe = self.sr(
sec, self.SHARPE_PERIOD, 0.0, Resolution.DAILY
)
sec.roc_s = self.roc(sec, self.ROC_SHORT, Resolution.DAILY)
sec.roc_m = self.roc(sec, self.ROC_MED, Resolution.DAILY)
sec.roc_l = self.roc(sec, self.ROC_LONG, Resolution.DAILY)
roc1 = self.roc(sec, 1, Resolution.DAILY)
sec.volatility = IndicatorExtensions.of(
StandardDeviation(self.VOL_PERIOD), roc1
)
roc1.reset()
for bar in self.history[TradeBar](
sec, self.VOL_PERIOD + 1, Resolution.DAILY
):
roc1.update(bar)
# ── biweekly rebalance ───────────────────────────────────
def _weekly_check(self, skip_week_check=False):
if self.is_warming_up or not self._spy_sma.is_ready:
return
if self._spy.price < self._spy_sma.current.value:
self.set_holdings(self._safe_haven, 1, True, tag="bear_regime")
return
if not skip_week_check and self.time.isocalendar()[1] % 2 != 0:
return
securities = [self.securities[sym] for sym in self._universe.selected]
# filter eligible securities — all gates applied here
eligible = [
s for s in securities
if (s != self._spy and
s.sma.is_ready and s.sharpe.is_ready and
s.roc_s.is_ready and s.roc_m.is_ready and
s.roc_l.is_ready and s.volatility.is_ready and
s.price and s.sma.current.value and
s.volatility.current.value and
s.price > s.sma.current.value and
s.sharpe.current.value > 0 and
s.roc_s.current.value > 0 and
s.roc_m.current.value > 0 and
s.roc_l.current.value > 0)
]
def _mcs_score(s):
mcs = (s.roc_s.current.value * s.roc_m.current.value
* s.roc_l.current.value) ** (1.0 / 3.0)
return mcs / s.volatility.current.value
top = sorted(eligible, key=_mcs_score, reverse=True)[:self.TOP_N]
if not top:
return
# inverse-volatility weighting
inv_vols = {s: 1.0 / s.volatility.current.value for s in top}
total_inv = sum(inv_vols.values())
weights = {
s: min((iv / total_inv) * self.MAX_GROSS, self.MAX_SINGLE)
for s, iv in inv_vols.items()
}
total_w = sum(weights.values())
if total_w > self.MAX_GROSS:
scale = self.MAX_GROSS / total_w
weights = {s: w * scale for s, w in weights.items()}
targets = [PortfolioTarget(s, w) for s, w in weights.items()]
self.set_holdings(targets, True)