| 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")