| Overall Statistics |
|
Total Orders 34 Average Win 17.93% Average Loss -1.05% Compounding Annual Return 26.329% Drawdown 26.000% Expectancy 8.550 Start Equity 100000 End Equity 321475.70 Net Profit 221.476% Sharpe Ratio 0.757 Sortino Ratio 0.854 Probabilistic Sharpe Ratio 36.095% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 17.04 Alpha 0.129 Beta 0.609 Annual Standard Deviation 0.215 Annual Variance 0.046 Information Ratio 0.522 Tracking Error 0.204 Treynor Ratio 0.267 Total Fees $391.49 Estimated Strategy Capacity $140000000.00 Lowest Capacity Asset XLE RGRPZX100F39 Portfolio Turnover 1.48% Drawdown Recovery 429 |
# region imports
from AlgorithmImports import *
# endregion
class MomentumETFRotationLean(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(self.end_date - timedelta(5 * 365))
self.set_cash(100_000)
self._universe = ["SPY", "QQQ", "IWM", "XLK", "XLV", "XLE", "XLF", "XLI", "XLB", "XLU", "GLD", "VNQ"]
self._safe_haven = "AGG"
self._mom_long = 252
for ticker in self._universe + [self._safe_haven]:
self.add_equity(ticker, Resolution.DAILY)
self.set_warm_up(280, Resolution.DAILY)
self.schedule.on(self.date_rules.month_start(self._universe[0]), self.time_rules.at(8, 0), self._rebalance)
def on_warmup_finished(self) -> None:
self._rebalance()
def _momentum_score(self, symbol: Symbol) -> float:
history = self.history(symbol, self._mom_long + 1, Resolution.DAILY)
if history.empty or len(history) < self._mom_long:
return float("-inf")
closes = history["close"]
price_long = closes.iloc[0]
if price_long <= 0:
return float("-inf")
# Skip the most recent month so the score captures 12-1 month momentum.
return (closes.iloc[-22] / price_long) - 1.0
def _rebalance(self) -> None:
if self.is_warming_up:
return
scores = {ticker: self._momentum_score(self.symbol(ticker)) for ticker in self._universe}
top_ticker, top_score = max(scores.items(), key=lambda item: item[1])
target = top_ticker if top_score > 0 else self._safe_haven
for holding in self.portfolio.values():
if holding.invested and holding.symbol.value != target:
self.liquidate(holding.symbol)
self.set_holdings(target, 1.0)