| Overall Statistics |
|
Total Orders 405 Average Win 0.95% Average Loss -1.25% Compounding Annual Return 24.203% Drawdown 16.500% Expectancy 0.363 Start Equity 100000 End Equity 295691.10 Net Profit 195.691% Sharpe Ratio 1.02 Sortino Ratio 1.17 Probabilistic Sharpe Ratio 74.118% Loss Rate 22% Win Rate 78% Profit-Loss Ratio 0.76 Alpha 0.109 Beta 0.306 Annual Standard Deviation 0.126 Annual Variance 0.016 Information Ratio 0.413 Tracking Error 0.154 Treynor Ratio 0.421 Total Fees $1626.62 Estimated Strategy Capacity $9800000.00 Lowest Capacity Asset DBC TFVSB03UY0DH Portfolio Turnover 6.85% Drawdown Recovery 199 |
from AlgorithmImports import *
from datetime import timedelta
class WeeklyVaaMomentumScoreRotation(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(self.end_date - timedelta(5 * 365))
self.set_cash(100000)
self.settings.seed_initial_prices = True
self.settings.free_portfolio_value_percentage = 0.03
self._target_total_weight = 0.95
self._risk_tickers = ["QQQ", "XLK", "XLE", "DBC", "GLD"]
self._defensive_tickers = ["GLD", "BIL"]
self._momentum_periods = [21, 63, 126, 252]
self._momentum_weights = [12.0, 4.0, 2.0, 1.0]
self._tickers = ["SPY", "QQQ", "XLK", "XLE", "DBC", "GLD", "BIL"]
self._symbols_by_ticker = {}
self._momentum_by_ticker = {}
for ticker in self._tickers:
symbol = self.add_equity(ticker, Resolution.DAILY).symbol
self._symbols_by_ticker[ticker] = symbol
self._momentum_by_ticker[ticker] = []
for period in self._momentum_periods:
self._momentum_by_ticker[ticker].append(self.rocp(symbol, period))
self.set_warm_up(max(self._momentum_periods) + 20, Resolution.DAILY)
self.schedule.on(
self.date_rules.week_start(self._symbols_by_ticker["SPY"]),
self.time_rules.at(8, 0),
self._rebalance,
)
def on_warmup_finished(self) -> None:
self._rebalance()
def _rebalance(self) -> None:
if self.is_warming_up:
return
if not self._indicators_are_ready():
self.set_holdings(self._symbols_by_ticker["BIL"], self._target_total_weight)
return
selected = self._select_tickers()
weight = self._target_total_weight / len(selected)
targets = []
for ticker in selected:
symbol = self._symbols_by_ticker[ticker]
targets.append(PortfolioTarget(symbol, weight))
self.set_holdings(targets, liquidate_existing_holdings=True)
def _indicators_are_ready(self) -> bool:
for ticker in self._tickers:
for indicator in self._momentum_by_ticker[ticker]:
if not indicator.is_ready:
return False
return True
def _select_tickers(self) -> list[str]:
if self._risk_regime_is_on():
selected = self._rank_tickers(self._risk_tickers, 2, require_positive=True)
else:
selected = self._rank_tickers(self._defensive_tickers, 1, require_positive=True)
if len(selected) == 0:
selected = self._rank_tickers(self._defensive_tickers, 1, require_positive=False)
if len(selected) == 0:
return ["BIL"]
return selected
def _risk_regime_is_on(self) -> bool:
return self._momentum_score("SPY") > 0
def _rank_tickers(self, tickers: list[str], count: int, require_positive: bool) -> list[str]:
ranked = []
for ticker in tickers:
score = self._momentum_score(ticker)
if score > 0 or not require_positive:
ranked.append((ticker, score))
ranked.sort(key=lambda item: item[1], reverse=True)
selected = []
for item in ranked[:count]:
selected.append(item[0])
return selected
def _momentum_score(self, ticker: str) -> float:
score = 0.0
indicators = self._momentum_by_ticker[ticker]
for index in range(len(indicators)):
score += self._momentum_weights[index] * indicators[index].current.value
return score