| Overall Statistics |
|
Total Orders 1265 Average Win 1.08% Average Loss -1.23% Compounding Annual Return 15.059% Drawdown 44.600% Expectancy 0.147 Start Equity 100000 End Equity 240430.24 Net Profit 140.430% Sharpe Ratio 0.463 Sortino Ratio 0.488 Probabilistic Sharpe Ratio 9.763% Loss Rate 39% Win Rate 61% Profit-Loss Ratio 0.88 Alpha 0.001 Beta 1.151 Annual Standard Deviation 0.207 Annual Variance 0.043 Information Ratio 0.179 Tracking Error 0.075 Treynor Ratio 0.083 Total Fees $2204.94 Estimated Strategy Capacity $0 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 24.27% |
#region imports
from AlgorithmImports import *
#endregion
#https://www.quantconnect.com/tutorials/strategy-library/the-dynamic-breakout-ii-strategy
from datetime import datetime
import decimal
import numpy as np
class DynamicBreakoutAlgorithm(QCAlgorithm):
# INITIALIZATION
def Initialize(self):
self.SetStartDate(2019,1,15)
self.SetEndDate(2025,4,15)
self._cash=100000
self.SetCash(self._cash)
# 2. UNIVERSE SELECTION MODEL
self.syl = []
self.tickers=["SPY", "USMV" , "QUAL", "DGRO", "DVY", "MTUM", "VLUE", "EFAV", "SIZE", "INTF", "IQLT", "LRGF"]
for ticker in self.tickers:
self.AddEquity(ticker, Resolution.Daily, Market.USA)
for ticker in self.tickers:
self.syl.append(self.AddEquity(ticker, Resolution.Daily, Market.USA).Symbol)
# 3. PARAMETERS
self.numd = 10
self.ceiling = 25
self.floor = 10
self.numdays = 30
# Initialize portfolio list with initial portfolio value to avoid empty sequence error
self._portfolio = [self._cash] # Start with initial cash value
self.counter = 0
self.buypoint = None
self.sellpoint= None
self.longLiqPoint = None
self.shortLiqPoint = None
self.yesterdayclose= None
# 4. EXECUTION MODEL
self.Schedule.On(self.DateRules.EveryDay(self.syl[0]), self.TimeRules.AfterMarketOpen(self.syl[0], 0), self.SetCombinedSignal)
# 5. BENCHMARKING FOR PLOTTING
self.spy = self.syl[0]
self.reference = self.History(self.spy, 1, Resolution.Daily)['close']
self._initialValue = self.reference.iloc[0]
self.SetWarmup(45)
def SetCombinedSignal(self):
# 6. RISK MANAGEMENT MODEL
if self.counter % 5 == 0 and len(self._portfolio) > 0: # Check if _portfolio has values before using max()
if self.Portfolio.TotalPortfolioValue < 0.95*max(self._portfolio):
if self.counter % 2 == 0:
if len(self._portfolio) > 0 and self.Portfolio.TotalPortfolioValue > 1.05*min(self._portfolio):
self.Liquidate()
self.SetHoldings("SPY", 2)
else:
self.Liquidate()
self.SetHoldings("SPY", -1)
else:
for s in range(len(self.tickers)):
self.Bolband = self.BB(self.syl[s], self.numd, 0.25, MovingAverageType.Exponential, Resolution.Daily)
self._s = s
self.SetMiniSignal()
def SetMiniSignal(self):
close = self.History(self.syl[self._s], 31, Resolution.Daily)["close"]
self.todayvol = np.std(close[1:31])
self.yesterdayvol = np.std(close[0:30])
self.deltavol = (self.todayvol - self.yesterdayvol) / self.todayvol
self.numd = int(round(self.numd * (1 + self.deltavol)))
if self.numd > self.ceiling:
self.numd = self.ceiling
elif self.numd < self.floor:
self.numd = self.floor
self.high = self.History(self.syl[self._s], self.numd, Resolution.Daily)['high']
self.low = self.History(self.syl[self._s], self.numd, Resolution.Daily)['low']
historyclose = self.History(self.syl[self._s], self.numdays, Resolution.Daily)['close']
historyopen = self.History(self.syl[self._s], self.numdays, Resolution.Daily)['open']
self.longLiqPoint = np.mean(historyclose)
self.shortLiqPoint = np.mean(historyopen)
self.buypoint = max(self.high)
self.sellpoint = min(self.low)
self.Debug(f" Basic Information: {self.todayvol}")
self.Debug(f" Buy Point: {self.buypoint }")
self.Debug(f" Price : {self.Securities[self.syl[self._s]].Close }")
self.Debug(f" Sell Point : {self.sellpoint }")
holdings = self.Portfolio[self.syl[self._s]].Quantity
self.Debug(f" Quantity: {self.Portfolio[self.syl[self._s]].Quantity}" )
historyclose = self.History(self.syl[self._s], self.numd, Resolution.Daily)['close']
self.yesterdayclose = historyclose.iloc[-1]
# 4. ALPHA MODEL AND PORTFOLIO CONSTRUCTION MODEL
if self.yesterdayclose > self.Bolband.UpperBand.Current.Value and self.Securities[self.syl[self._s]].Close >= self.buypoint:
self.SetHoldings(self.syl[self._s], 2*(1/(len(self.tickers))))
elif self.yesterdayclose < self.Bolband.LowerBand.Current.Value and self.Securities[self.syl[self._s]].Close <= self.sellpoint:
self.SetHoldings(self.syl[self._s], -2*(1/(len(self.tickers))))
else:
self.SetHoldings(self.syl[0], 1)
# 6. RISK MANANGEMENT MODEL
if holdings > 0 and self.Portfolio[self.syl[self._s]].Price <= 0.95*self.longLiqPoint:
self.Liquidate(self.syl[self._s])
#pass
elif holdings < 0 and self.Portfolio[self.syl[self._s]].Price >= 1.05*self.shortLiqPoint:
self.Liquidate(self.syl[self._s])
#pass
def OnData(self, data):
self.counter += 1
self._portfolio.append(self.Portfolio.TotalPortfolioValue)
self.Plot("Relative Performance", "Benchmark", self._cash*self.Securities["SPY"].Close/self._initialValue)
self.Plot("Relative Performance", "Portfolio Value", self.Portfolio.TotalPortfolioValue)