| Overall Statistics |
|
Total Orders 142 Average Win 0.10% Average Loss -0.70% Compounding Annual Return 42.223% Drawdown 16.800% Expectancy -0.135 Start Equity 1000000 End Equity 1304710.14 Net Profit 30.471% Sharpe Ratio 1.077 Sortino Ratio 1.106 Probabilistic Sharpe Ratio 55.639% Loss Rate 24% Win Rate 76% Profit-Loss Ratio 0.14 Alpha 0.214 Beta 0.532 Annual Standard Deviation 0.24 Annual Variance 0.057 Information Ratio 0.746 Tracking Error 0.235 Treynor Ratio 0.485 Total Fees $960.21 Estimated Strategy Capacity $3400000.00 Lowest Capacity Asset BIL TT1EBZ21QWKL Portfolio Turnover 3.84% Drawdown Recovery 183 |
# region imports
from AlgorithmImports import *
import numpy as np
# endregion
class InstitutionalTQQQComposite(QCAlgorithm):
def initialize(self):
# 1. Setup: 5 Years (Captures 2020 Crash, 2022 Bear, 2023 Bull)
self.set_start_date(2019, 1, 1)
self.set_end_date(2024, 6, 1)
self.set_cash(100000)
self.settings.rebalance_portfolio_on_insight_changes = False
# 2. The Engine: TQQQ (3x Nasdaq)
self.tqqq = self.add_equity("TQQQ", Resolution.MINUTE).symbol
# 3. The Safe Haven: BIL (T-Bills)
# Instead of 0% Cash, we earn ~5% interest when out of the market
self.safe = self.add_equity("BIL", Resolution.MINUTE).symbol
# 4. The Canary Signals (Intermarket Analysis)
# XLI (Industrials) vs XLU (Utilities) -> Economic Cycle
self.xli = self.add_equity("XLI", Resolution.DAILY).symbol
self.xlu = self.add_equity("XLU", Resolution.DAILY).symbol
# 5. Volatility Controls
self.vol_lookback = 20 # 1 Month Volatility Check
self.target_vol = 0.40 # Target 40% Annualized Volatility (Aggressive but safe)
# 6. Logic Parameters
self.lookback = 60 # 3 Months trend comparison
self.wait_days = 5 # "Cool off" period
self.out_day_counter = 0
self.be_in = False
# 7. Schedule: Trade 10 mins before close to capture daily data
self.schedule.on(self.date_rules.every_day(self.tqqq),
self.time_rules.before_market_close(self.tqqq, 10),
self.rebalance)
self.set_warm_up(100)
def on_data(self, data):
pass
def rebalance(self):
if self.is_warming_up: return
# --- PART 1: REGIME SIGNAL (The Light Switch) ---
history = self.history([self.xli, self.xlu], self.lookback, Resolution.DAILY)
if history.empty: return
# Get closing prices
try:
xli_closes = history.loc[self.xli].close
xlu_closes = history.loc[self.xlu].close
except:
return # Data missing
if len(xli_closes) < self.lookback or len(xlu_closes) < self.lookback: return
# Calculate Returns (Momentum)
xli_perf = (xli_closes[-1] / xli_closes[0]) - 1
xlu_perf = (xlu_closes[-1] / xlu_closes[0]) - 1
# Logic: If Industrials > Utilities, Economy is Healthy
if xli_perf > xlu_perf:
self.out_day_counter = 0
self.be_in = True
else:
self.out_day_counter += 1
if self.out_day_counter >= self.wait_days:
self.be_in = False
# --- PART 2: VOLATILITY TARGETING (The Sizing) ---
# If we are "In", how much do we buy?
target_weight = 0.0
if self.be_in:
# Calculate TQQQ's recent volatility (Standard Deviation)
tqqq_hist = self.history(self.tqqq, self.vol_lookback + 1, Resolution.DAILY)
if not tqqq_hist.empty:
# Calculate daily returns
returns = tqqq_hist.close.pct_change().dropna()
# Calculate Annualized Volatility (StdDev * Sqrt(252))
current_vol = np.std(returns) * np.sqrt(252)
# Volatility Scaling Math:
# If Target is 40% and Current is 20%, we go >100% (or capped at 1.0)
# If Target is 40% and Current is 80%, we go 50% Size.
if current_vol > 0:
vol_weight = self.target_vol / current_vol
else:
vol_weight = 1.0
# Cap leverage at 1.0 (No margin on top of TQQQ)
target_weight = min(1.0, vol_weight)
self.plot("Data", "TQQQ Vol", current_vol)
self.plot("Data", "Target Weight", target_weight)
else:
# If we are OUT, weight is 0
target_weight = 0.0
# --- PART 3: EXECUTION ---
# 1. Invest calculated weight into TQQQ
self.set_holdings(self.tqqq, target_weight)
# 2. Invest the REST into BIL (Safe Yield)
# E.g., if TQQQ is 0.6, BIL is 0.4. If TQQQ is 0, BIL is 1.0.
# This ensures our "Cash" is actually earning 5% interest
safe_weight = 1.0 - target_weight
self.set_holdings(self.safe, safe_weight)
if self.be_in:
self.plot("State", "Regime", 1)
else:
self.plot("State", "Regime", 0)