| Overall Statistics |
|
Total Trades 489 Average Win 0.54% Average Loss -0.78% Compounding Annual Return -0.013% Drawdown 52.100% Expectancy 0.010 Net Profit -0.065% Sharpe Ratio 0.107 Probabilistic Sharpe Ratio 0.816% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 0.70 Alpha 0.005 Beta 0.843 Annual Standard Deviation 0.211 Annual Variance 0.044 Information Ratio 0.012 Tracking Error 0.114 Treynor Ratio 0.027 Total Fees $2473.47 Estimated Strategy Capacity $2500000.00 Lowest Capacity Asset SHV TP8J6Z7L419H |
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.LOW
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)