Overall Statistics |
Total Trades 625 Average Win 1.84% Average Loss -1.75% Compounding Annual Return 25.869% Drawdown 32.500% Expectancy 0.415 Net Profit 865.359% Sharpe Ratio 0.894 Probabilistic Sharpe Ratio 32.882% Loss Rate 31% Win Rate 69% Profit-Loss Ratio 1.05 Alpha 0.15 Beta 0.422 Annual Standard Deviation 0.202 Annual Variance 0.041 Information Ratio 0.52 Tracking Error 0.21 Treynor Ratio 0.428 Total Fees $5808.53 Estimated Strategy Capacity $66000000.00 Lowest Capacity Asset UNH R735QTJ8XC9X Portfolio Turnover 7.75% |
from AlgorithmImports import * class BBandsAlgorithm(QCAlgorithm): def Initialize(self) -> None: # backtest settings self.SetCash(100000) self.SetStartDate(2014, 1, 1) self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash) self.BBstd = float(self.GetParameter("stdBB")) #4 self.BBvalue = float(self.GetParameter("BB")) #17.6 self.ROCvalue = float(self.GetParameter("ROC")) #24.8 # Initialize an empty dictionary to store last trade times (new addition) self.lastTradeTimes = {} # settings self.EnableAutomaticIndicatorWarmUp = True self.Settings.FreePortfolioValuePercentage = 0.05 self.UniverseSettings.Resolution = Resolution.Daily # ETF universe self.etf = self.AddEquity("SPY", self.UniverseSettings.Resolution).Symbol self.AddUniverseSelection( ETFConstituentsUniverseSelectionModel(self.etf, self.UniverseSettings, self.ETFConstituentsFilter)) # Alternative investments self.alternatives = { 'UUP': self.AddEquity('UUP', self.UniverseSettings.Resolution).Symbol, 'TLT': self.AddEquity('TLT', self.UniverseSettings.Resolution).Symbol, 'GLD': self.AddEquity('GLD', self.UniverseSettings.Resolution).Symbol } self.SetBenchmark(self.etf) self.symbolData = {} self.universe = [] self.buy_prices = {} def OnData(self, data: Slice) -> None: # liquidate assets that we should not trade for symbol in [x.Key for x in self.Portfolio if x.Value.Invested]: if symbol not in self.universe or not self.Securities[symbol].IsTradable: self.Liquidate(symbol) # Calculate the ROC for all symbols and select the max ROC symbol roc_values = {symbol: self.symbolData[symbol].roc.Current.Value for symbol in self.universe if symbol in self.symbolData} if roc_values: max_roc_symbol = max(roc_values, key=roc_values.get) max_roc = roc_values[max_roc_symbol] else: max_roc_symbol = None max_roc = None # Check if all ROCs are negative and liquidate/reallocate if necessary if all(value < 0 for value in roc_values.values()): self.Liquidate() self.Reallocate() return # Implement the Buy Conditions if max_roc_symbol and self.CanInvest() and max_roc > 0: price = self.Securities[max_roc_symbol].Price symbolData = self.symbolData[max_roc_symbol] if symbolData.bb.MiddleBand.Current.Value < price < symbolData.bb.UpperBand.Current.Value: self.SetHoldings(max_roc_symbol, 1) self.buy_prices[max_roc_symbol] = price self.lastTradeTimes[ max_roc_symbol] = self.Time # Update the last trade time when a new holding is set (new addition) # Implement the Sell Conditions for symbol in self.Portfolio.Keys: price = self.Securities[symbol].Price if self.Portfolio[symbol].Invested: # Profit taking if price >= 1.05 * self.buy_prices.get(symbol, 0): self.Liquidate(symbol) continue # Stop Loss if price <= 0.95 * self.buy_prices.get(symbol, self.Portfolio[symbol].AveragePrice): self.Liquidate(symbol) # self.stop_loss_triggered = True continue # Time-based Exit if symbol in self.lastTradeTimes: # Check if the last trade time exists (new addition) time_invested = (self.Time - self.lastTradeTimes[ symbol]).days # Use the stored last trade time (modified line) if time_invested > 10: self.Liquidate(symbol) continue def ETFConstituentsFilter(self, constituents: List[ETFConstituentData]) -> List[Symbol]: # validate in-data if constituents is None: return Universe.Unchanged selected = [c for c in constituents if c.Weight] sorted_selected = sorted([c for c in selected], key=lambda c: c.Weight, reverse=True)[:(int(self.GetParameter("selected")))] #20 self.universe = [c.Symbol for c in sorted_selected] return self.universe def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: # validate in-data if changes is None: return for security in changes.AddedSecurities: self.symbolData[security.Symbol] = SymbolData(self, security.Symbol) for security in changes.RemovedSecurities: self.Liquidate(security.Symbol) symbolData = self.symbolData.pop(security.Symbol, None) if symbolData: symbolData.dispose() def CanInvest(self) -> bool: return sum(1 for x in self.Portfolio if x.Value.Invested) < 10 def Reallocate(self) -> None: for symbol, security in self.alternatives.items(): self.SetHoldings(security, 1 / len(self.alternatives)) def OnOrderEvent(self, orderEvent: OrderEvent) -> None: if orderEvent.Status == OrderStatus.Filled: self.Debug(str(orderEvent)) class SymbolData(object): def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol self.stdBB = float(algorithm.BBstd) self.BBi = int(algorithm.BBvalue) self.ROCi = int(algorithm.ROCvalue) # Assuming 'BB' and 'ROC' are methods provided by QuantConnect's QCAlgorithm class self.bb = algorithm.BB(symbol, self.BBi, self.stdBB, MovingAverageType.Simple, Resolution.Daily) self.roc = algorithm.ROC(symbol, self.ROCi, Resolution.Daily) def dispose(self): # deregister indicators and remove consolidator pass