| Overall Statistics |
|
Total Orders 5085 Average Win 1.66% Average Loss -0.57% Compounding Annual Return 394.029% Drawdown 34.600% Expectancy 1.166 Start Equity 10000 End Equity 87618797418.24 Net Profit 876187874.182% Sharpe Ratio 5.961 Sortino Ratio 7.24 Probabilistic Sharpe Ratio 100% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 2.94 Alpha 2.122 Beta 0.985 Annual Standard Deviation 0.369 Annual Variance 0.136 Information Ratio 6.247 Tracking Error 0.34 Treynor Ratio 2.233 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset TNA U7EC123NWZTX Portfolio Turnover 61.02% Drawdown Recovery 330 |
from AlgorithmImports import *
import numpy as np
import json
from datetime import datetime, timedelta
# WARNING: This is an example to illustrate a "flaw" I'm seeing in a lot of QuantConnect published algorithms.
# That is, the use of Resolution.DAILY when algorithm leverages trade within last N minutes of market closing.
# Why is this a problem? When the equity symbol is subscribed on a daily basis,
# backtesting will fill the price at yesterday's closing price. When you backtest this algorithm, you'll see
# that 2025/4/18 3:55pm (ET) will fill the price on the close price of 2025/4/17.
class TrendBarbellMetaAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2015, 1, 1)
self.set_end_date(2025, 1, 1) # ignored in live trading
self.set_cash(10000)
self.settings.rebalance_portfolio_on_insight_changes = False
self.set_brokerage_model(BrokerageName.CHARLES_SCHWAB, AccountType.MARGIN)
# ── 1. THE REGIME ARBITER ─────────────────────────────────────────────
self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol
self.inverse_spy = self.add_equity("SH", Resolution.MINUTE).symbol
self.spy_sma_200 = self.sma(self.spy, 200, Resolution.DAILY)
self.spy_ema = self.ema(self.spy, 2, Resolution.DAILY)
self.spy_roc_3 = self.roc(self.spy, 3, Resolution.DAILY)
# ── 2. MICRO SETUP ────────────────────────────────────────────────────
self.vwap_ind = self.vwap(self.spy)
self.imbalance_sma = SimpleMovingAverage(5)
# Note: This is the standard deviation of the closing price, not the VWAP itself.
self.price_std = self.std(self.spy, 390, Resolution.MINUTE)
self.trades_today = 0
self.micro_entry_price = 0.0
self.micro_highest_price = 0.0
self.micro_lowest_price = 99999.0
self._last_state_save = datetime.min
# ── OPENING RANGE BREAKOUT (ORB) ─────────────────────────────────────
self._or_high = 0.0
self._or_low = 99999.0
self._or_complete = False
self._orb_trade = False
# Unique ObjectStore key per deployment
self._deploy = self.get_parameter("deployment_id") or "paper"
self._state_key = "barbell_state" if self._deploy == "live" else f"barbell_state_{self._deploy}"
# ── 3. MACRO BASKET SETUP ─────────────────────────────────────────────
self.tqqq = self.add_equity("TQQQ", Resolution.DAILY).symbol
self.soxl = self.add_equity("SOXL", Resolution.DAILY).symbol
self.tna = self.add_equity("TNA", Resolution.DAILY).symbol
self.bear_tickers = ["GLD", "TLT", "TMF", "USO", "UUP", "FXY"]
self.bear_symbols = {t: self.add_equity(t, Resolution.DAILY).symbol for t in self.bear_tickers}
self.bear_momentum = {
t: self.momp(self.bear_symbols[t], 21, Resolution.DAILY)
for t in self.bear_tickers
}
self.last_bear_rebalance = datetime(2000, 1, 1)
self.schedule.on(self.date_rules.every_day(self.spy), self.time_rules.midnight, self.reset_daily_limits)
self.schedule.on(self.date_rules.every_day(self.spy), self.time_rules.after_market_open(self.spy, 5), self.macro_rebalance)
# self.schedule.on(self.date_rules.every_day(self.spy), self.time_rules.before_market_close(self.spy, 5), self.macro_rebalance)
self.set_warm_up(210, Resolution.DAILY)
def reset_daily_limits(self):
self.trades_today = 0
if not self.portfolio[self.spy].is_long and not self.portfolio[self.inverse_spy].invested:
self.micro_entry_price = 0.0
self.micro_highest_price = 0.0
self.micro_lowest_price = 99999.0
self._or_high = 0.0
self._or_low = 99999.0
self._or_complete = False
self._orb_trade = False
self._save_state()
def on_warmup_finished(self):
today = self.time.date()
self.trades_today = sum(
1 for o in self.transactions.get_orders()
if o.time.date() == today and o.status == OrderStatus.FILLED
)
spy_price = self.securities[self.spy].price
if self.portfolio[self.spy].is_long:
self.micro_entry_price = self.portfolio[self.spy].average_price
self.micro_highest_price = spy_price
elif self.portfolio[self.inverse_spy].invested:
self.micro_entry_price = self.portfolio[self.inverse_spy].average_price
self.micro_lowest_price = spy_price
self._load_state()
self.debug(f"Indicator Status - SMA: {self.spy_sma_200.is_ready}, EMA: {self.spy_ema.is_ready}")
def _save_state(self):
state = {
"last_bear_rebalance": self.last_bear_rebalance.isoformat(),
"state_date": self.time.date().isoformat(),
"trades_today": self.trades_today,
"micro_entry_price": self.micro_entry_price,
"micro_highest_price": self.micro_highest_price,
"micro_lowest_price": self.micro_lowest_price,
}
self.object_store.save(self._state_key, json.dumps(state))
self._last_state_save = self.time
def _load_state(self):
if not self.object_store.contains_key(self._state_key):
self.debug("No state found, starting fresh.")
return
try:
state = json.loads(self.object_store.read(self._state_key))
self.debug(f"Loading state: {state}")
self.last_bear_rebalance = datetime.fromisoformat(state["last_bear_rebalance"])
saved_date = datetime.strptime(state["state_date"], "%Y-%m-%d").date()
if saved_date == self.time.date():
self.trades_today = max(self.trades_today, state.get("trades_today", 0))
if self.portfolio[self.spy].is_long:
self.micro_entry_price = self.portfolio[self.spy].average_price
self.micro_highest_price = max(self.micro_highest_price, state.get("micro_highest_price", 0))
elif self.portfolio[self.inverse_spy].invested:
self.micro_entry_price = self.portfolio[self.inverse_spy].average_price
self.micro_lowest_price = min(self.micro_lowest_price, state.get("micro_lowest_price", 99999))
except Exception as e:
self.log(f"State restore failed, using safe defaults: {e}")
def get_top_momentum_bear_assets(self, n=3):
scored = {}
for t in self.bear_tickers:
mom = self.bear_momentum[t]
if mom.is_ready:
scored[t] = mom.current.value
if not scored:
return list(self.bear_symbols.keys())[:n]
ranked = sorted(scored.keys(), key=lambda t: scored[t], reverse=True)
return ranked[:n]
def macro_rebalance(self):
self.debug(f"Macro rebalance triggered at {self.time}")
if self.is_warming_up or not self.spy_sma_200.is_ready or not self.spy_ema.is_ready:
return
spy_price = self.securities[self.spy].close
sma200 = self.spy_sma_200.current.value
ema_val = self.spy_ema.current.value
if spy_price > ema_val and spy_price > sma200:
self.debug("Regime: Bullish")
# FIX: Clear micro positions to free up margin for the leveraged basket
if self.portfolio[self.spy].invested:
self.liquidate(self.spy, "Bull: Liquidating Micro Long to free margin")
if self.portfolio[self.inverse_spy].invested:
self.liquidate(self.inverse_spy, "Bull: Liquidating Micro Short to free margin")
for t in self.bear_tickers:
s = self.bear_symbols[t]
if self.portfolio[s].invested:
self.liquidate(s, "Bull: Liquidating Protective Basket")
tna_available = self.securities[self.tna].price > 0
total_val = max(self.portfolio.total_portfolio_value, 1)
if tna_available:
tqqq_w = abs(self.portfolio[self.tqqq].holdings_value) / total_val
soxl_w = abs(self.portfolio[self.soxl].holdings_value) / total_val
tna_w = abs(self.portfolio[self.tna].holdings_value) / total_val
if abs(tqqq_w - 0.30) > 0.05 or abs(soxl_w - 0.60) > 0.05 or abs(tna_w - 0.10) > 0.05:
self.set_holdings(self.tqqq, 0.30)
self.set_holdings(self.soxl, 0.60)
self.set_holdings(self.tna, 0.10)
else:
tqqq_w = abs(self.portfolio[self.tqqq].holdings_value) / total_val
soxl_w = abs(self.portfolio[self.soxl].holdings_value) / total_val
if abs(tqqq_w - 0.33) > 0.05 or abs(soxl_w - 0.67) > 0.05:
self.set_holdings(self.tqqq, 0.33)
self.set_holdings(self.soxl, 0.67)
elif spy_price > sma200:
self.debug("Regime: Weakening Bull")
for t in self.bear_tickers:
s = self.bear_symbols[t]
if self.portfolio[s].invested:
mom = self.bear_momentum[t]
if not mom.is_ready or mom.current.value <= 0:
self.liquidate(s, "Weakening Bull: Clearing Negative Momentum Asset")
if self.portfolio[self.tqqq].invested: self.liquidate(self.tqqq, "Weakening Bull: Exiting TQQQ")
if self.portfolio[self.soxl].invested: self.liquidate(self.soxl, "Weakening Bull: Exiting SOXL")
if self.portfolio[self.tna].invested: self.liquidate(self.tna, "Weakening Bull: Exiting TNA")
else:
self.debug("Regime: Bear market")
if self.portfolio[self.tqqq].invested: self.liquidate(self.tqqq, "Bear Market: Liquidating TQQQ")
if self.portfolio[self.soxl].invested: self.liquidate(self.soxl, "Bear Market: Liquidating SOXL")
if self.portfolio[self.tna].invested: self.liquidate(self.tna, "Bear Market: Liquidating TNA")
days_since_rebalance = (self.time - self.last_bear_rebalance).days
if days_since_rebalance >= 30:
top_assets = self.get_top_momentum_bear_assets(n=3)
self.last_bear_rebalance = self.time
self._save_state()
total = self.portfolio.total_portfolio_value
micro_weight = (abs(self.portfolio[self.spy].holdings_value) +
abs(self.portfolio[self.inverse_spy].holdings_value)) / total if total > 0 else 0.0
per_asset = max(0.0, 1.0 - micro_weight) / len(top_assets)
for t in self.bear_tickers:
s = self.bear_symbols[t]
if t not in top_assets and self.portfolio[s].invested:
self.liquidate(s, "Bear: Rotating Out Low Momentum Asset")
for t in top_assets:
s = self.bear_symbols[t]
current_w = abs(self.portfolio[s].holdings_value) / max(total, 1)
if abs(current_w - per_asset) > 0.05:
self.set_holdings(s, per_asset)
def check_micro_stops(self, price):
# FIX: Evaluated unconditionally so stops trigger regardless of regime shifts
if self.portfolio[self.spy].is_long:
self.micro_highest_price = max(self.micro_highest_price, price)
if price < self.micro_highest_price * 0.99 or price < self.micro_entry_price * 0.98:
self.liquidate(self.spy, "Sniper Stop Loss Triggered (Long)")
self._save_state()
elif self.portfolio[self.inverse_spy].invested:
self.micro_lowest_price = min(self.micro_lowest_price, price)
if price > self.micro_lowest_price * 1.01 or price > self.micro_entry_price * 1.02:
self.liquidate(self.inverse_spy, "Sniper Stop Loss Triggered (SH)")
self._save_state()
def on_data(self, data: Slice):
if self.is_warming_up or not self.spy_sma_200.is_ready or not self.spy_ema.is_ready:
return
# Safely extract quotes
quote = data.quote_bars.get(self.spy) if data.quote_bars.contains_key(self.spy) else None
if quote:
tot = quote.last_bid_size + quote.last_ask_size
if tot > 0:
self.imbalance_sma.update(self.time, (quote.last_bid_size - quote.last_ask_size) / tot)
if not data.bars.contains_key(self.spy): return
spy_price = data.bars[self.spy].close
# Check stops immediately, independently of regime
self.check_micro_stops(spy_price)
sma200_val = self.spy_sma_200.current.value
ema_val = self.spy_ema.current.value
in_non_bull = spy_price < sma200_val or (spy_price < ema_val and spy_price >= sma200_val)
# ── OPENING RANGE BREAKOUT (ORB) ─────────────────────────────────────
if self.time.hour == 9 and self.time.minute >= 30:
self._or_high = max(self._or_high, spy_price)
self._or_low = min(self._or_low, spy_price)
elif self.time.hour == 10 and not self._or_complete:
self._or_complete = True
if (in_non_bull and self._or_complete and
self.time.hour == 10 and self.time.minute <= 30 and
not self._orb_trade and self.trades_today < 2 and
not self.portfolio[self.spy].is_long and
not self.portfolio[self.inverse_spy].invested and
self.imbalance_sma.is_ready):
or_range = self._or_high - self._or_low
imbalance = self.imbalance_sma.current.value
if or_range > 0 and or_range / max(self._or_low, 1) > 0.0005:
if spy_price < self._or_low * 0.999 and imbalance < -0.08:
self.set_holdings(self.inverse_spy, 0.20)
self.micro_entry_price = self.micro_lowest_price = spy_price
self._orb_trade = True
self.trades_today += 1
self._save_state()
if in_non_bull:
self.run_micro_execution(spy_price)
# FIX: Only save state frequently during Live deployments to prevent backtest I/O throttling
if self._deploy == "live" and (self.time - self._last_state_save).total_seconds() >= 300:
self._save_state()
def run_micro_execution(self, price):
if not self.vwap_ind.is_ready or not self.price_std.is_ready: return
# FIX: Removed the 15:00 restriction so regular trades trigger all day
if self.trades_today >= 3:
return
vwap_val = self.vwap_ind.current.value
std_dev = self.price_std.current.value if self.price_std.current.value > 0 else 1.0
imbalance = self.imbalance_sma.current.value
strong_bull = price > (vwap_val + std_dev) and imbalance > 0.15
roc3_bearish = self.spy_roc_3.is_ready and self.spy_roc_3.current.value < 0
strong_bear = price < (vwap_val - std_dev) and imbalance < -0.15 and roc3_bearish
target_size = 0.50 if abs(imbalance) < 0.30 else 0.95
spy_long = self.portfolio[self.spy].is_long
sh_invested = self.portfolio[self.inverse_spy].invested
if not spy_long and not sh_invested:
if strong_bull:
self.set_holdings(self.spy, target_size)
self.micro_entry_price = self.micro_highest_price = price
self.trades_today += 1
self._save_state()
elif strong_bear:
self.set_holdings(self.inverse_spy, target_size)
self.micro_entry_price = self.micro_lowest_price = price
self.trades_today += 1
self._save_state()
else:
if spy_long and strong_bear:
self.liquidate(self.spy, "Flipping Bear: Liquidating SPY Long")
self.set_holdings(self.inverse_spy, target_size)
self.micro_entry_price = self.micro_lowest_price = price
self.trades_today += 1
self._save_state()
elif sh_invested and strong_bull:
self.liquidate(self.inverse_spy, "Flipping Bull: Liquidating SH")
self.set_holdings(self.spy, target_size)
self.micro_entry_price = self.micro_highest_price = price
self.trades_today += 1
self._save_state()
def on_order_event(self, orderEvent):
# We only care when an order actually fills
if orderEvent.status == OrderStatus.FILLED:
symbol = orderEvent.symbol
# Check if it's one of your daily macro assets
if symbol == self.tqqq or symbol == self.soxl:
fill_price = orderEvent.fill_price
fill_time = self.time
engine_price = self.securities[symbol].price
self.log(f"--- FAKE FILL CHECK ---")
self.log(f"Fill Time: {fill_time}")
self.log(f"Asset: {symbol}")
self.log(f"Fill Price: ${fill_price}")
self.log(f"Engine's Current Price: ${engine_price}")
self.log(f"-----------------------")