| Overall Statistics |
|
Total Orders 98 Average Win 14.20% Average Loss -1.06% Compounding Annual Return 40.575% Drawdown 77.800% Expectancy 11.593 Start Equity 100000 End Equity 23773974.76 Net Profit 23673.975% Sharpe Ratio 0.853 Sortino Ratio 0.909 Probabilistic Sharpe Ratio 12.559% Loss Rate 12% Win Rate 88% Profit-Loss Ratio 13.39 Alpha 0.139 Beta 2.74 Annual Standard Deviation 0.43 Annual Variance 0.185 Information Ratio 0.927 Tracking Error 0.306 Treynor Ratio 0.134 Total Fees $14427.02 Estimated Strategy Capacity $0 Lowest Capacity Asset TQQQ UK280CGTCB51 Portfolio Turnover 0.23% Drawdown Recovery 933 |
# region imports
from AlgorithmImports import *
# endregion
"""
9-Sig Strategy for TQQQ
Based on Kelly Letter's 9-Sig Strategy
A rules-based, quarterly rebalancing strategy that uses value averaging to buy low
and sell high, targeting 9% quarterly growth in TQQQ value.
Core Rules:
- Start with 60/40 allocation (60% TQQQ, 40% bonds)
- Rebalance quarterly against a 9% growth target (signal line)
- Sell excess when above target, buy more when below target
- Skip 2 sell signals after a 30%+ drop to stay invested during recovery
- No intra-quarter trading - discipline over emotion
For more information, visit: https://kellyletter.com
"""
class NineSigTQQQStrategy(QCAlgorithm):
"""9-Sig Strategy for TQQQ with quarterly rebalancing based on 9% growth target."""
def initialize(self):
self.set_start_date(2010, 3, 1)
self.set_cash(100000)
# Add securities
self._tqqq = self.add_equity("TQQQ", Resolution.DAILY).symbol
self._bonds = self.add_equity("AGG", Resolution.DAILY).symbol
# Strategy parameters
self._quarterly_growth_rate = 0.09 # 9% quarterly growth target
self._initial_tqqq_allocation = 0.60
self._initial_bond_allocation = 0.40
# State variables
self._signal_line = 0 # Target TQQQ value
self._is_initialized = False
self._peak_tqqq_value = 0 # Track peak for 30% drop detection
self._sell_signals_to_skip = 0 # Skip sell signals after 30%+ drop
self._last_rebalance_date = None
# Schedule quarterly rebalancing at start of each quarter
self.schedule.on(self.date_rules.month_start("TQQQ"),
self.time_rules.after_market_open("TQQQ", 30),
self._check_and_rebalance)
# Warmup for price data
self.set_warmup(1)
self.settings.free_portfolio_value_percentage = 0.01
def _check_and_rebalance(self):
"""Check if it's a quarter start and rebalance if needed."""
if self.is_warming_up:
return
# Initialize on first call
if not self._is_initialized:
self._initialize_positions()
return
current_month = self.time.month
# Only rebalance at the start of quarters (Jan, Apr, Jul, Oct)
if current_month not in [1, 4, 7, 10]:
return
# Prevent double-rebalancing in the same quarter
if self._last_rebalance_date is not None:
if self._last_rebalance_date.year == self.time.year and self._last_rebalance_date.month == self.time.month:
return
self._rebalance()
def _rebalance(self):
"""Quarterly rebalancing based on 9% growth target."""
# Get current TQQQ value
current_tqqq_value = self.portfolio[self._tqqq].holdings_value
if current_tqqq_value <= 0:
self.log("No TQQQ holdings, skipping rebalance")
return
# Check for 30%+ drop from peak
self._peak_tqqq_value = max(self._peak_tqqq_value, current_tqqq_value)
drawdown = (self._peak_tqqq_value - current_tqqq_value) / self._peak_tqqq_value
if drawdown >= 0.30 and self._sell_signals_to_skip == 0:
self._sell_signals_to_skip = 2
self.log(f"30%+ drawdown detected. Will skip next 2 sell signals.")
# Rebalancing logic
difference = current_tqqq_value - self._signal_line
if difference > 0:
# TQQQ above signal line - sell excess
if self._sell_signals_to_skip > 0:
self._sell_signals_to_skip -= 1
self.log(f"Skipping sell signal ({self._sell_signals_to_skip} remaining). Current: ${current_tqqq_value:.2f}, Target: ${self._signal_line:.2f}")
else:
# Sell TQQQ, buy bonds
self._sell_tqqq_buy_bonds(difference)
else:
# TQQQ below signal line - buy more
self._buy_tqqq_from_bonds(abs(difference))
# Reset signal line for next quarter (9% growth)
self._signal_line = self.portfolio[self._tqqq].holdings_value * (1 + self._quarterly_growth_rate)
self._last_rebalance_date = self.time
self.log(f"Rebalanced. TQQQ: ${self.portfolio[self._tqqq].holdings_value:.2f}, Bonds: ${self.portfolio[self._bonds].holdings_value:.2f}, New Signal: ${self._signal_line:.2f}")
def _initialize_positions(self):
"""Initial 60/40 allocation."""
total_value = self.portfolio.total_portfolio_value
self.set_holdings(self._tqqq, self._initial_tqqq_allocation)
self.set_holdings(self._bonds, self._initial_bond_allocation)
# Set initial signal line
self._signal_line = self.portfolio[self._tqqq].holdings_value * (1 + self._quarterly_growth_rate)
self._peak_tqqq_value = self.portfolio[self._tqqq].holdings_value
self._is_initialized = True
self._last_rebalance_date = self.time
self.log(f"Initialized: TQQQ ${self.portfolio[self._tqqq].holdings_value:.2f}, Bonds ${self.portfolio[self._bonds].holdings_value:.2f}, Signal ${self._signal_line:.2f}")
def _sell_tqqq_buy_bonds(self, amount: float):
"""Sell TQQQ excess and buy bonds."""
if amount <= 0:
return
tqqq_price = self.securities[self._tqqq].price
bonds_price = self.securities[self._bonds].price
if tqqq_price <= 0 or bonds_price <= 0:
return
# Sell TQQQ
tqqq_shares_to_sell = int(amount / tqqq_price)
if tqqq_shares_to_sell > 0:
self.market_order(self._tqqq, -tqqq_shares_to_sell)
# Buy bonds with the proceeds
bond_shares_to_buy = int(amount / bonds_price)
if bond_shares_to_buy > 0:
self.market_order(self._bonds, bond_shares_to_buy)
self.log(f"Sold {tqqq_shares_to_sell} TQQQ, bought {bond_shares_to_buy} AGG (${amount:.2f})")
def _buy_tqqq_from_bonds(self, amount: float):
"""Buy TQQQ using bond allocation."""
if amount <= 0:
return
bonds_value = self.portfolio[self._bonds].holdings_value
if bonds_value <= 0:
self.log("No bonds available, temporary buy-and-hold")
return
# Use bond allocation to buy TQQQ
amount_to_use = min(amount, bonds_value)
bonds_price = self.securities[self._bonds].price
tqqq_price = self.securities[self._tqqq].price
if bonds_price <= 0 or tqqq_price <= 0:
return
# Sell bonds
bond_shares_to_sell = int(amount_to_use / bonds_price)
if bond_shares_to_sell > 0:
self.market_order(self._bonds, -bond_shares_to_sell)
# Buy TQQQ with the amount
tqqq_shares_to_buy = int(amount_to_use / tqqq_price)
if tqqq_shares_to_buy > 0:
self.market_order(self._tqqq, tqqq_shares_to_buy)
self.log(f"Bought {tqqq_shares_to_buy} TQQQ shares (${amount_to_use:.2f}) from bonds")