Overall Statistics
Total Orders
4144
Average Win
0.25%
Average Loss
-0.25%
Compounding Annual Return
8.663%
Drawdown
39.400%
Expectancy
0.154
Net Profit
129.512%
Sharpe Ratio
0.341
Sortino Ratio
0.309
Probabilistic Sharpe Ratio
1.728%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
1.02
Alpha
-0.004
Beta
0.799
Annual Standard Deviation
0.164
Annual Variance
0.027
Information Ratio
-0.157
Tracking Error
0.12
Treynor Ratio
0.07
Total Fees
$4008.00
Estimated Strategy Capacity
$270000000.00
Lowest Capacity Asset
AVGO UEW4IOBWVPT1
Portfolio Turnover
4.71%
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

        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction)

        self.ma_short = 150
        self.ma_long = 200
        self.volume_multiplier = 1.5

        self.symbols = {}

    def CoarseSelectionFunction(self, coarse):
        sorted_coarse = sorted([x for x in coarse if x.HasFundamentalData], 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, Resolution.Daily),
                    "200MA": self.SMA(symbol, self.ma_long, Resolution.Daily),
                    "VolumeSMA": self.SMA(symbol, 30, Resolution.Daily)
                }

    def OnData(self, data):
        for symbol, indicators in self.symbols.items():
            if not (indicators["150MA"].IsReady and indicators["200MA"].IsReady):
                continue

            if symbol not in data or not data[symbol]:
                continue

            if self.IsStage2(indicators, data[symbol].Price, data[symbol].Volume, indicators["VolumeSMA"].Current.Value):
                if self.MeetsEntryCriteria(symbol, indicators, data[symbol]):
                    size = self.PositionSizing(symbol)
                    self.SetHoldings(symbol, size)
            else:
                if self.Portfolio[symbol].Invested:
                    self.Liquidate(symbol)

    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 MeetsEntryCriteria(self, symbol, indicators, security_data):
        # Expand this method to include more sophisticated checks as needed
        return indicators["200MA"].Current.Value > indicators["200MA"].Current.Value * (1 - 0.01)

    def PositionSizing(self, symbol):
        # Implement your volatility calculation here. This is a simplified example.
        history = self.History(symbol, 30, Resolution.Daily)
        if not history.empty:
            daily_returns = history['close'].pct_change().dropna()
            volatility = daily_returns.std()
            size = min(0.1, 1 / volatility)  # Simplified risk-adjusted sizing
            return size
        return 0.1

    def RebalancePortfolio(self):
        # Implement your rebalance logic. This is triggered quarterly in the example.
        if self.Time.month % 3 == 0 and self.Time.day == 1:
            for symbol in self.symbols:
                if symbol in self.Portfolio:
                    self.AdjustPosition(symbol)

    # Placeholder for AdjustPosition. Define based on your rebalancing strategy.
    def AdjustPosition(self, symbol):
        # Adjust position based on updated criteria or indicators
        pass