| Overall Statistics |
|
Total Trades 41 Average Win 3.21% Average Loss -1.61% Compounding Annual Return 22.679% Drawdown 10.100% Expectancy 0.651 Net Profit 20.612% Sharpe Ratio 1.185 Probabilistic Sharpe Ratio 54.717% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 2.00 Alpha 0.167 Beta 0.057 Annual Standard Deviation 0.137 Annual Variance 0.019 Information Ratio 1.034 Tracking Error 0.237 Treynor Ratio 2.866 Total Fees $268.02 Estimated Strategy Capacity $150000.00 Lowest Capacity Asset USDU VMIMJSS4X2SL Portfolio Turnover 4.87% |
from AlgorithmImports import *
from AlgorithmImports import *
from datetime import timedelta
class MyAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 1, 1)
self.SetEndDate(2022, 12, 1)
self.SetCash(100000)
self.signals = ["VIXM", "BND", "BIL", "TLT", "SPY"]
self.risk_on_no_inflation = ["TECL", "TQQQ", "UPRO", "TMF"]
self.risk_on_falling_rates = ["UPRO", "TMF"]
self.risk_off_market_crash = ["SHY"]
self.risk_off_rising_rates = ["USDU", "QID", "TBF"]
self.refined_risk_off = ["IEI", "GLD", "TIP", "BSV"]
for symbol in self.signals + self.risk_on_no_inflation + self.risk_on_falling_rates + self.risk_off_market_crash + self.risk_off_rising_rates + self.refined_risk_off:
self.AddEquity(symbol)
self.current_condition = None
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("SPY", 10), self.CheckConditions)
def OnData(self, data):
pass
def CheckConditions(self):
history = self.History(self.signals + self.risk_on_no_inflation + self.risk_on_falling_rates + self.risk_off_market_crash + self.risk_off_rising_rates + self.refined_risk_off, 60, Resolution.Daily)
if history.empty:
return
last_prices = history['close'].unstack(level=0).iloc[-1]
prev_prices = history['close'].unstack(level=0).iloc[-60]
vixm_momentum = self.CalculateMomentum(history['close']['VIXM'], 40)
if vixm_momentum > 0.69:
new_condition = "RiskOffMarketCrash"
else:
bnd_return_60 = self.CalculateReturn(last_prices['BND'], prev_prices['BND'], 60)
bil_return_60 = self.CalculateReturn(last_prices['BIL'], prev_prices['BIL'], 60)
if bnd_return_60 > bil_return_60:
new_condition = "RiskOnNormal"
elif bnd_return_60 <= bil_return_60 and self.CheckTLTCondition(last_prices, prev_prices):
new_condition = "RiskOffRisingRates"
else:
spy_drawdown = self.CalculateMaxDrawdown(history['close']['SPY'], 5)
if spy_drawdown < 0.05:
new_condition = "RiskOnFallingRates"
else:
new_condition = "RefinedRiskOff"
if new_condition != self.current_condition:
self.current_condition = new_condition
self.Rebalance(self.current_condition, history, last_prices)
def CheckTLTCondition(self, last_prices, prev_prices):
tlt_return_20 = self.CalculateCumulativeReturn(last_prices['TLT'], prev_prices['TLT'], 20)
bil_return_20 = self.CalculateCumulativeReturn(last_prices['BIL'], prev_prices['BIL'], 20)
return tlt_return_20 < bil_return_20
def Rebalance(self, condition, history, last_prices):
self.Log(f"Rebalancing for condition: {condition}")
self.Liquidate()
if condition == "RiskOffMarketCrash":
self.SetHoldings("SHY", 1)
elif condition == "RiskOnNormal":
self.RiskOnNormal(history, last_prices)
elif condition == "RiskOffRisingRates":
self.RiskOffRisingRates(history)
elif condition == "RiskOnFallingRates":
self.SetHoldings("UPRO", 0.55)
self.SetHoldings("TMF", 0.45)
elif condition == "RefinedRiskOff":
self.RefinedRiskOff()
def RiskOnNormal(self, history, last_prices):
relative_strength = {asset: self.CalculateMomentum(history['close'][asset], 10) for asset in self.risk_on_no_inflation}
top_assets = sorted(relative_strength, key=lambda x: relative_strength[x], reverse=True)[:3]
weight = 0.33
for asset in top_assets:
self.SetHoldings(asset, weight)
def RiskOffRisingRates(self, history):
self.SetHoldings("USDU", 0.5)
qid_tbf_momentum = {asset: self.CalculateMomentum(history['close'][asset], 20) for asset in self.risk_off_rising_rates}
top_asset = max(qid_tbf_momentum, key=qid_tbf_momentum.get)
self.SetHoldings(top_asset, 0.5)
def RefinedRiskOff(self):
weight = 0.25
for asset in self.refined_risk_off:
self.SetHoldings(asset, weight)
def CalculateReturn(self, current_price, previous_price, days):
return (current_price - previous_price) / previous_price
def CalculateCumulativeReturn(self, current_price, previous_price, days):
return (current_price / previous_price) - 1
def CalculateMaxDrawdown(self, prices, days):
max_drawdown = 0
for i in range(1, len(prices) - days + 1):
drawdown = (prices[i:i+days].min() - prices[i-1]) / prices[i-1]
max_drawdown = min(max_drawdown, drawdown)
return max_drawdown
def CalculateMomentum(self, prices, days):
return prices.pct_change(periods=days).mean()