| Overall Statistics |
|
Total Trades 402 Average Win 0.71% Average Loss -0.66% Compounding Annual Return 9.604% Drawdown 21.000% Expectancy 0.640 Net Profit 150.284% Sharpe Ratio 0.872 Probabilistic Sharpe Ratio 28.369% Loss Rate 21% Win Rate 79% Profit-Loss Ratio 1.06 Alpha 0.041 Beta 0.276 Annual Standard Deviation 0.077 Annual Variance 0.006 Information Ratio -0.228 Tracking Error 0.124 Treynor Ratio 0.245 Total Fees $966.31 Estimated Strategy Capacity $1700000.00 Lowest Capacity Asset BIL TT1EBZ21QWKL Portfolio Turnover 1.92% |
from AlgorithmImports import *
import numpy as np
class MyAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2012, 12, 31)
self.SetEndDate(2022, 12, 31)
self.SetCash(100000)
self.canary = self.AddEquity("TIP", Resolution.Minute).Symbol
self.cash = self.AddEquity("BIL", Resolution.Minute).Symbol
self.ief = self.AddEquity("IEF", Resolution.Minute).Symbol
self.offensive_tickers = ['SPY', 'IWM', 'VWO', 'VEA', 'VNQ', 'DBC', 'IEF', 'TLT']
self.offensive_assets = [self.AddEquity(symbol, Resolution.Minute).Symbol for symbol in self.offensive_tickers]
self.month = -1
self.SetWarmUp(252)
self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", 1), self.Rebalance)
def momentum(self, asset):
# Get historical price data for the asset
history = self.History(asset, 365, Resolution.Daily)
# Check if history is empty
if history.empty or len(history) < 252:
return 0.0
prices = history['close']
# Calculate total returns for each period
returns_1m = (prices[-1] / prices[-21]) - 1
returns_3m = (prices[-1] / prices[-63]) - 1
returns_6m = (prices[-1] / prices[-126]) - 1
returns_12m = (prices[-1] / prices[-252]) - 1
# Calculate average momentum
momentum = np.mean([returns_1m, returns_3m, returns_6m, returns_12m])
return momentum
def Rebalance(self):
canary_momentum = self.momentum(self.canary)
offensive_assets = self.offensive_assets
momentums = [self.momentum(asset) for asset in offensive_assets]
cash_momentum = self.momentum(self.cash)
ief_momentum = self.momentum(self.ief)
sorted_assets = [x for _, x in sorted(zip(momentums, offensive_assets), reverse=True)]
# self.Debug(f"{self.Time.strftime('%Y-%m-%d')}");
# self.Debug(f"TIPS momentum {canary_momentum}");
if canary_momentum < 0:
# self.Debug(f"Going DEFENSIVE on {self.Time.strftime('%Y-%m-%d')}");
if ief_momentum > cash_momentum:
self.SetHoldings(self.ief, 1, True)
else:
self.SetHoldings(self.cash, 1, True)
else:
top_four_assets = sorted_assets[:4]
rest_assets = sorted_assets[4:]
# there's probably a more efficient way to do this to avoid more transactions than needed
for asset in rest_assets:
self.Liquidate(asset)
self.Liquidate(self.ief)
self.Liquidate(self.cash)
count_neg_momentum = 0
for asset in top_four_assets:
momentum = self.momentum(asset)
self.Debug(f"{asset} momentum {momentum} {self.Time.strftime('%Y-%m-%d')}");
if momentum >= 0:
self.SetHoldings(asset, 0.25)
else:
count_neg_momentum = count_neg_momentum + 1
if ief_momentum > cash_momentum:
self.SetHoldings(self.ief, 0.25 * count_neg_momentum)
else:
self.SetHoldings(self.cash, 0.25 * count_neg_momentum)