Overall Statistics |
Total Trades 1245 Average Win 0.65% Average Loss -0.29% Compounding Annual Return 12.201% Drawdown 29.300% Expectancy 0.869 Net Profit 338.692% Sharpe Ratio 0.584 Probabilistic Sharpe Ratio 3.097% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 2.26 Alpha 0.002 Beta 1.024 Annual Standard Deviation 0.168 Annual Variance 0.028 Information Ratio 0.048 Tracking Error 0.078 Treynor Ratio 0.096 Total Fees $5037.23 Estimated Strategy Capacity $24000000.00 Lowest Capacity Asset SSO TJNNZWL5I4IT |
from AlgorithmImports import * import numpy as np class SymbolIndicatorData(): def __init__(self, algorithm, symbol) -> None: self.symbol = symbol self.algorithm = algorithm self.spyRollingWindow = RollingWindow[float](21*3) self.ema_21_vol = None self.sma_63_vol = None self.spyEma21VolRollingWindow = RollingWindow[float](21*6) self.spySma63VolRollingWindow = RollingWindow[float](21*6) warmUpData = self.algorithm.History[TradeBar]( symbol, 21*6 + 21*3 +10, Resolution.Daily ) for bar in warmUpData: self.spyRollingWindow.Add(bar.Close) if self.spyRollingWindow.IsReady: l = list(self.spyRollingWindow) self.ema_21_vol = np.std(l[:21]) self.sma_63_vol = np.std(l[:21*3]) self.spyEma21VolRollingWindow.Add(self.ema_21_vol) self.spySma63VolRollingWindow.Add(self.sma_63_vol) def Update(self, close): self.spyRollingWindow.Add(close) if self.spyRollingWindow.IsReady: l = list(self.spyRollingWindow) self.ema_21_vol = np.std(l[:21]) self.sma_63_vol = np.std(l[:21*3]) self.spyEma21VolRollingWindow.Add(self.ema_21_vol) self.spySma63VolRollingWindow.Add(self.sma_63_vol) def Reset(self): self.spyRollingWindow.Reset() self.ema_21_vol = None self.sma_63_vol = None self.spyEma21VolRollingWindow.Reset() self.spyEma21VolRollingWindow.Reset() @property def IsReady(self): if self.spyEma21VolRollingWindow.IsReady and self.spySma63VolRollingWindow.IsReady: return True return False @property def IsHighVol(self): if not self.IsReady: return 0 if self.ema_21_vol > np.mean(list(self.spyEma21VolRollingWindow)) and self.sma_63_vol > np.mean(list(self.spySma63VolRollingWindow)): return 1 elif self.ema_21_vol < np.mean(list(self.spyEma21VolRollingWindow)) and self.sma_63_vol < np.mean(list(self.spySma63VolRollingWindow)): return -1 return 0
#region imports from AlgorithmImports import * from enum import Enum from SymbolData import SymbolIndicatorData ''' 1. At every point in time, you can allocate multiplier M of the cushion to the risky assets. Thus, as the cushion decreases, you are reducing the risky asset allocation. If the cushion goes to zero (the value of the portfolio hits the floor), your allocation to the risky assets is the multiplier M times zero. This means you are allocating nothing to the portfolio's risky portion, and 100% is invested in the safe component of your portfolio. 2. Because of this, it is recommended to set the multiplier as a function of the maximum potential loss within a given trading interval. ''' # Benchmark BENCHMARK = 'SPY' # Risky asset # RISKY_ASSET = 'SPY' RISKY_ASSET = 'SSO' # 2x SPY ETF # Risk free asset RISKFREE_ASSET = 'SHV' # short term Treasury ETF # RISKFREE_ASSET = 'SHY' # short term Treasury ETF' # RISKFREE_ASSET = 'TLT' # long term Treasury ETF' # RISKFREE_ASSET = 'IEF' # long term Treasury ETF' class CppiMode(Enum): BEHCNMARK = 0 BASIC = 1 NEW_HIGH = 2 # TIPP LEARNING = 3 class MultiplierMode(Enum): FIXED_MULTIPLIER = 0 DYNAMIC_MULTIPLIER = 1 class BacktestTimePeriod(Enum): ALL = 1 LOW = 2 HIGH = 3 class OvernightTradeAlgorithm(QCAlgorithm): def Initialize(self): self._init_cash = 100000 self.SetCash(self._init_cash) #Set Strategy Cash # self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage) # T+2 ########################################## # Symbol management self.benchmark = self.AddEquity(BENCHMARK, Resolution.Daily).Symbol self.spy = self.AddEquity(RISKY_ASSET, Resolution.Daily).Symbol self.riskfree = self.AddEquity(RISKFREE_ASSET, Resolution.Daily).Symbol ########################################## # CPPI setup self.cppi_mode = CppiMode.LEARNING self.mutiplier_mdoe = MultiplierMode.DYNAMIC_MULTIPLIER self.scenario = BacktestTimePeriod.ALL self.max_asset = self._init_cash self.FloorPercentage = 0.8 self.Floor = self.FloorPercentage * self._init_cash self.Cushion = self._init_cash - self.Floor self.Multiplier = 3 self.UpdateUpperBound = 1.1 self.learning_alpha = 0.2 # By percentage % of total asset self.E = (self.Cushion * self.Multiplier) / self._init_cash self.B = (self._init_cash - self.Cushion * self.Multiplier) / self._init_cash if self.scenario == BacktestTimePeriod.ALL: self.SetStartDate(2010, 1, 1) #Set Start Date self.SetEndDate(2022, 11, 1) #Set End Date elif self.scenario == BacktestTimePeriod.LOW: self.SetStartDate(2007, 1, 1) #Set Start Date self.SetEndDate(2012, 1, 1) #Set End Date elif self.scenario == BacktestTimePeriod.HIGH: self.SetStartDate(2015, 1, 1) #Set Start Date self.SetEndDate(2019, 1, 1) #Set End Date # Construct indicators if self.mutiplier_mdoe == MultiplierMode.DYNAMIC_MULTIPLIER: self.dynamicMultiplierIndicator = SymbolIndicatorData(self, self.spy) ########################################## # Scheduling self.Schedule.On( self.DateRules.WeekStart(self.benchmark), self.TimeRules.At(6, 30), self.UpdateParameters ) # Place the order/SetHolding before market open, then order price will be decided by the market open price self.Schedule.On( self.DateRules.WeekStart(self.benchmark), self.TimeRules.At(8, 30), self.EveryDayAfterMarketOpen ) ########################################## # Charting self._benchmark_queue = [] self._perf_start = None self._my_chart = Chart('AvailCash') self._my_chart.AddSeries(Series("Cash", SeriesType.Line, 0)) self.AddChart(self._my_chart) self._my_chart2 = Chart('BnE') self._my_chart2.AddSeries(Series("B", SeriesType.Line, 0)) self._my_chart2.AddSeries(Series("E", SeriesType.Line, 0)) self.AddChart(self._my_chart2) self._my_chart3 = Chart('Multiplier') self._my_chart3.AddSeries(Series("Multi", SeriesType.Line, 0)) self.AddChart(self._my_chart3) def UpdateParameters(self): if self.mutiplier_mdoe == MultiplierMode.DYNAMIC_MULTIPLIER: if self.dynamicMultiplierIndicator.IsReady: if self.dynamicMultiplierIndicator.IsHighVol > 0: self.Multiplier = 2 elif self.dynamicMultiplierIndicator.IsHighVol == 0: self.Multiplier = 3 elif self.dynamicMultiplierIndicator.IsHighVol < 0: self.Multiplier = 4 else: self.Debug(f'The indicator is not ready') self.Multiplier = 3 elif self.mutiplier_mdoe == MultiplierMode.FIXED_MULTIPLIER: pass cur_total_asset = self.Portfolio.TotalPortfolioValue if self.cppi_mode == CppiMode.BASIC: pass elif self.cppi_mode == CppiMode.NEW_HIGH: self.max_asset = cur_total_asset if (cur_total_asset > self.max_asset * self.UpdateUpperBound) else self.max_asset self.Floor = self.max_asset * self.FloorPercentage elif self.cppi_mode == CppiMode.LEARNING: diff = 0 # if abs(cur_total_asset - self.max_asset * self.UpdateUpperBound) > (self.max_asset * (self.UpdateUpperBound - 1)): if abs(cur_total_asset - self.max_asset) > (self.max_asset * (self.UpdateUpperBound - 1)): diff = (cur_total_asset - self.max_asset) self.max_asset = self.max_asset + diff * self.learning_alpha self.Floor = self.max_asset * self.FloorPercentage self.Cushion = cur_total_asset - self.Floor self.E = (self.Cushion * self.Multiplier) / self.max_asset self.E = min(1, self.E) self.E = max(0, self.E) self.B = 1 - self.E def EveryDayAfterMarketOpen(self): if self.cppi_mode == CppiMode.BEHCNMARK: self.SetHoldings(self.spy, 1) self.SetHoldings(self.riskfree, 0) else: self.SetHoldings(self.spy, self.E) self.SetHoldings(self.riskfree, self.B) def OnData(self, data): if len(data.Bars) <= 0: self.Debug(f'{self.Time}: Data is empty') return if self.mutiplier_mdoe == MultiplierMode.DYNAMIC_MULTIPLIER: if data.ContainsKey(self.spy): self.dynamicMultiplierIndicator.Update( data[self.spy].Close ) else: self.Debug(f'[{self.Time}]: {self.spy} is not found in the slice data!!!!') def OnEndOfDay(self, symbol): self.Plot('AvailCash', 'Cash', self.Portfolio.Cash) self.Plot('AvailCash', 'Portfolio', self.Portfolio.TotalPortfolioValue) self.Plot('BnE', 'B', self.B) self.Plot('BnE', 'E', self.E) self.Plot('Multiplier', 'Multi', self.Multiplier) # Plot the benchmark return hist = self.History(self.benchmark, 2, Resolution.Daily)['close'].unstack(level= 0).dropna() self._benchmark_queue.append(hist[self.benchmark].iloc[-1]) if self._perf_start is None: self._perf_start = hist[self.benchmark].iloc[0] spy_perf = self._benchmark_queue[-1] / self._perf_start * self._init_cash self.Plot('Strategy Equity', self.benchmark, spy_perf)