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)