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)