| Overall Statistics |
|
Total Orders 2221 Average Win 0.41% Average Loss -0.41% Compounding Annual Return 10.269% Drawdown 40.000% Expectancy 0.208 Net Profit 165.795% Sharpe Ratio 0.394 Sortino Ratio 0.359 Probabilistic Sharpe Ratio 2.517% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.01 Alpha 0.005 Beta 0.856 Annual Standard Deviation 0.175 Annual Variance 0.031 Information Ratio -0.048 Tracking Error 0.124 Treynor Ratio 0.081 Total Fees $2169.00 Estimated Strategy Capacity $170000000.00 Lowest Capacity Asset BA R735QTJ8XC9X Portfolio Turnover 2.69% |
from AlgorithmImports import *
class MinerviniSEPAStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2014, 1, 1) # Start date
self.SetEndDate(2024, 1, 1) # End date
self.SetCash(10000) # Starting cash
# Add SPY to the algorithm's data subscriptions
self.spy = self.AddEquity("SPY").Symbol
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.ma_short_period = 150
self.ma_long_period = 200
self.volume_multiplier = 1.5
self.max_position_size = 0.10 # Max size of any position relative to the portfolio
self.symbols = {}
self.next_rebalance_date = self.Time + timedelta(days=7) # Initialize next rebalance date
# Now that SPY is added, schedule the rebalance check using SPY's market open time
self.Schedule.On(self.DateRules.EveryDay(self.spy), self.TimeRules.AfterMarketOpen(self.spy, 10), self.RebalanceIfNeeded)
def CoarseSelectionFunction(self, coarse):
sorted_coarse = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5], key=lambda x: x.DollarVolume, reverse=True)[:100]
return [x.Symbol for x in sorted_coarse]
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
if security.Symbol in self.symbols:
self.Liquidate(security.Symbol)
del self.symbols[security.Symbol]
for security in changes.AddedSecurities:
symbol = security.Symbol
if symbol not in self.symbols:
self.symbols[symbol] = {
"150MA": self.SMA(symbol, self.ma_short_period, Resolution.Daily),
"200MA": self.SMA(symbol, self.ma_long_period, Resolution.Daily),
"VolumeSMA": self.SMA(symbol, 30, Resolution.Daily),
}
def RebalanceIfNeeded(self):
if self.Time < self.next_rebalance_date:
return
for symbol, indicators in self.symbols.items():
if not (indicators["150MA"].IsReady and indicators["200MA"].IsReady):
continue
if symbol not in self.Securities:
continue
price = self.Securities[symbol].Price
volume = self.Securities[symbol].Volume
avg_volume = indicators["VolumeSMA"].Current.Value
if self.IsStage2(indicators, price, volume, avg_volume):
size = self.PositionSizing(symbol)
self.SetHoldings(symbol, size)
else:
if self.Portfolio[symbol].Invested:
self.Liquidate(symbol)
# Update next rebalance date
self.next_rebalance_date = self.Time + timedelta(days=7)
def IsStage2(self, indicators, price, volume, avg_volume):
avg150 = indicators["150MA"].Current.Value
avg200 = indicators["200MA"].Current.Value
return price > avg150 > avg200 and volume > avg_volume * self.volume_multiplier
def PositionSizing(self, symbol):
history = self.History(symbol, 31, Resolution.Daily).close
if history.empty or len(history) < 31:
return self.max_position_size
daily_returns = history.pct_change().dropna()
volatility = daily_returns.std()
if volatility == 0:
return self.max_position_size
size = min(self.max_position_size, 1 / (volatility * 10)) # Adjusted for volatility
return size
# Remember, this is a starting point. You may need to adjust your universe selection criteria,
# add more complex entry and exit criteria, incorporate earnings data, and refine the position sizing
# based on your risk tolerance and strategy goals.