| 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)