Overall Statistics |
Total Orders 577 Average Win 2.62% Average Loss -1.74% Compounding Annual Return 34.492% Drawdown 38.600% Expectancy 0.442 Start Equity 100000000 End Equity 855541613.92 Net Profit 755.542% Sharpe Ratio 1.12 Sortino Ratio 1.025 Probabilistic Sharpe Ratio 63.343% Loss Rate 42% Win Rate 58% Profit-Loss Ratio 1.51 Alpha 0 Beta 0 Annual Standard Deviation 0.206 Annual Variance 0.042 Information Ratio 1.227 Tracking Error 0.206 Treynor Ratio 0 Total Fees $17488051.37 Estimated Strategy Capacity $8100000.00 Lowest Capacity Asset XON R735QTJ8XC9X Portfolio Turnover 17.71% |
from AlgorithmImports import * from symbol_calcs import * from datetime import timedelta import numpy as np class EldersTripleScreenAlpha(QCAlgorithm): def Initialize(self): ''' Initial Algo parameters and QuantConnect methods''' ########### Strategy Params ########### self.SetStartDate(2017, 1, 1) self.SetEndDate(2024, 3, 31) self.SetCash(100000000) # Warmup the algorithm with prior data for backtesting self.SetWarmup(timedelta(30)) # Additional variables for Sharpe Ratio calculation self.dailyReturns = [] self.sharpeRatios = [] self.previousPortfolioValue = self.Portfolio.TotalPortfolioValue ########### Universe Selection ########### self.UniverseSettings.Resolution = Resolution.Hour self.UniverseSettings.Leverage = 2 self.AddUniverse(self._fundamental_selection_function) # Variables for universe selection model self.coarse_count = 10 self.symbols = [] self.data = {} ########### Elders Triple Screen Signals ########### self.ema_period = 20 self.rsi_period = 14 self.macd_fast_period = 12 self.macd_slow_period = 26 self.macd_signal_period = 6 ########### Risk Management Model ########### self.AddRiskManagement(MaximumDrawdownPercentPerSecurity(0.05)) ########### Order Execution Model ########### self.SetExecution(ImmediateExecutionModel()) ########### Portfolio Construction Model ########### self.SetPortfolioConstruction(RiskParityPortfolioConstructionModel(portfolioBias=PortfolioBias.Long)) # Required portfolio free cash value self.Settings.FreePortfolioValuePercentage=0.05 ########### Reality Modeling Parameters ########### self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) self.SetRiskFreeInterestRateModel(InterestRateProvider()) self.Portfolio.MarginCallModel = DefaultMarginCallModel(self.Portfolio, self.DefaultOrderProperties) ########### Scheduler ########### self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.At(11,30,0, TimeZones.NewYork), self.EvaluateIndicators) ########### Object Store ########## self.universe_store = '' self.EMA_store = '' self.RSI_store = '' self.MACD_store = '' ########### Load Existing Universe ########### if self.IsLiveEnvironment(): self.LoadExistingUniverse() # Initialize plotting self.Plot("Sharpe Ratio", "Daily Annualized Sharpe Ratio", 0) def IsLiveEnvironment(self): ''' Check if the algorithm is running in a live environment ''' return self.LiveMode def LoadExistingUniverse(self): ''' Load the existing universe from the live brokerage account holdings ''' self.Debug("Loading existing universe from brokerage account...") # Fetch existing holdings holdings = [x.Symbol for x in self.Portfolio.Values if x.Invested] self.Debug(f"Found {len(holdings)} existing holdings.") # Register indicators for the existing holdings for symbol in holdings: self.data[symbol] = { 'ema': ExponentialMovingAverage(self.ema_period), 'rsi': RelativeStrengthIndex(self.rsi_period, MovingAverageType.Wilders), 'macd': MovingAverageConvergenceDivergence(self.macd_fast_period, self.macd_slow_period, self.macd_signal_period, MovingAverageType.Wilders) } self.RegisterIndicator(symbol, self.data[symbol]['ema'], Resolution.Hour) self.RegisterIndicator(symbol, self.data[symbol]['rsi'], Resolution.Hour) self.RegisterIndicator(symbol, self.data[symbol]['macd'], Resolution.Daily) return holdings def _fundamental_selection_function(self, fundamental: List[Fundamental]) -> List[Symbol]: ''' Fundamental Filter to produce a list of securities within the universe''' filtered = [f for f in fundamental if f.Price > 10 and \ f.HasFundamentalData and \ not np.isnan(f.ValuationRatios.PERatio) and \ f.ValuationRatios.ForwardPERatio > 5 and \ f.MarketCap > 500000] self.Debug(f"Filtered securities: {', '.join([f.Symbol.Value for f in filtered])}") sorted_by_dollar_volume = sorted(filtered, key=lambda f: f.DollarVolume, reverse=True)[:100] self.Debug(f"Top 100 by dollar volume: {', '.join([f.Symbol.Value for f in sorted_by_dollar_volume])}") sorted_by_pe_ratio = sorted(sorted_by_dollar_volume, key=lambda f: f.ValuationRatios.PERatio, reverse=False)[:20] self.Debug(f"Final selected securities: {', '.join([f.Symbol.Value for f in sorted_by_pe_ratio])}") self.Debug(f"Final selected securities: {sorted_by_pe_ratio[:20]}") return [f.Symbol for f in sorted_by_pe_ratio[:20]] def FineFilter(self, fine): ''' Placeholder for further filtering of the universe ''' pass ''' Simpler changes to universe # this event fires whenever we have changes to our universe def OnSecuritiesChanged(self, changes): # liquidate removed securities for security in changes.RemovedSecurities: if security.Invested: self.Liquidate(security.Symbol) self.Debug(f"Liquidated {security.Symbol}") # we want 20% allocation in each security in our universe - This gives 10% allocation for security in changes.AddedSecurities: self.SetHoldings(security.Symbol, 0.1) self.Debug(f"Bought {security.Symbol}") ''' ############### Evaluate Indicators ######################### ''' Elder's Triple Screen Alpha Logic''' def OnSecuritiesChanged(self, changes): ''' Add indicators whenever securities are added to or removed from the universe''' for security in changes.AddedSecurities: symbol = security.Symbol self.data[symbol] = { 'ema': ExponentialMovingAverage(self.ema_period), 'rsi': RelativeStrengthIndex(self.rsi_period, MovingAverageType.Wilders), 'macd': MovingAverageConvergenceDivergence(self.macd_fast_period, self.macd_slow_period, self.macd_signal_period, MovingAverageType.Wilders) } self.RegisterIndicator(symbol, self.data[symbol]['ema'], Resolution.Hour) self.RegisterIndicator(symbol, self.data[symbol]['rsi'], Resolution.Hour) self.RegisterIndicator(symbol, self.data[symbol]['macd'], Resolution.Daily) # Reality Modeling for slippage #security.SetSlippageModel(VolumeShareSlippageModel(0.025, 0.1)) #security.SetSlippageModel(MarketImpactSlippageModel(self)) for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.data: del self.data[symbol] def EvaluateIndicators(self): ''' Evaluate the indicators for the securities ''' # Maybe change this self.data.items() to something else. Rn its not iterating through all the securities like it should for symbol, indicators in self.data.items(): ema = indicators['ema'] rsi = indicators['rsi'] macd = indicators['macd'] if not (ema.IsReady and rsi.IsReady and macd.IsReady): continue ema_value = ema.Current.Value rsi_value = rsi.Current.Value macd_value = macd.Current.Value macd_signal_value = macd.Signal.Current.Value price = self.Securities[symbol].Price direction = InsightDirection.Flat confidence = 0 magnitude = 0 if rsi_value < 30 and macd_value > macd_signal_value: #and price > ema_value: direction = InsightDirection.Up confidence = (70 - rsi_value) / 100 magnitude = macd_value - macd_signal_value # Consider changing this to an AND instead of an or, since both need to be satisfied elif rsi_value > 70 or macd_value < macd_signal_value:# or price < ema_value: direction = InsightDirection.Down confidence = (rsi_value - 30) / 100 magnitude = macd_signal_value - macd_value insight = Insight.Price(symbol, timedelta(days=1), direction, magnitude, confidence) #self.EmitInsights(insight) #self.Debug(f"Generated Insight: {insight}") if confidence > 0.2: self.EmitInsights(insight) self.Debug(f"Generated Insight: {insight}") return insight def OnEndOfDay(self): ''' Calculate daily return and Sharpe Ratio ''' currentPortfolioValue = self.Portfolio.TotalPortfolioValue dailyReturn = (currentPortfolioValue - self.previousPortfolioValue) / self.previousPortfolioValue self.dailyReturns.append(dailyReturn) self.previousPortfolioValue = currentPortfolioValue if len(self.dailyReturns) > 1: meanReturn = np.mean(self.dailyReturns) stdDeviation = np.std(self.dailyReturns) if stdDeviation != 0: sharpeRatio = (meanReturn / stdDeviation) * np.sqrt(252) # Annualizing the Sharpe Ratio self.sharpeRatios.append(sharpeRatio) else: self.sharpeRatios.append(0) # Update plot self.Plot("Sharpe Ratio", "Daily Annualized Sharpe Ratio", self.sharpeRatios[-1]) def OnEndOfAlgorithm(self): ''' Calculate the Sharpe Ratio at the end of the algorithm and plot it ''' if len(self.sharpeRatios) > 1: self.Plot("Sharpe Ratio", "Final Sharpe Ratio", self.sharpeRatios[-1]) else: self.Log("Not enough data to plot Sharpe Ratio.")
#region imports from AlgorithmImports import * #endregion # Note this class was copied from bottom of main.py, it can technically be run seperately as long as the import is there class SymbolData(object): def __init__(self, symbol): self._symbol = symbol self.tolerance = 1.01 self.fast = ExponentialMovingAverage(100) self.slow = ExponentialMovingAverage(300) self.is_uptrend = False self.scale = 1 print("SymbolData Init Complete") def update(self, time, value): if self.fast.update(time, value) and self.slow.update(time, value): fast = self.fast.current.value slow = self.slow.current.value self.is_uptrend = fast > slow * self.tolerance if self.is_uptrend: self.scale = (fast - slow) / ((fast + slow) / 2.0) return print("Update method ran successfully")