Overall Statistics
Total Trades
2228
Average Win
1.45%
Average Loss
-1.51%
Compounding Annual Return
-1.320%
Drawdown
57.100%
Expectancy
0.018
Net Profit
-12.440%
Sharpe Ratio
0.045
Sortino Ratio
0.045
Probabilistic Sharpe Ratio
0.035%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
0.96
Alpha
-0.064
Beta
1.016
Annual Standard Deviation
0.276
Annual Variance
0.076
Information Ratio
-0.269
Tracking Error
0.233
Treynor Ratio
0.012
Total Fees
$2352.67
Estimated Strategy Capacity
$0
Lowest Capacity Asset
BRKB R735QTJ8XC9X
Portfolio Turnover
24.80%
from AlgorithmImports import *

class MomentumAndSMAStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2014, 1, 1)  # Start Date
        self.SetEndDate(2024, 1, 1)  # End Date
        self.SetCash(10000)  # Set Strategy Cash

        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        
        self.numberOfSymbols = 10
        self.ranked_symbols = []

        # Add SPY for scheduling purpose, ensuring it's available in the algorithm
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.Schedule.On(self.DateRules.EveryDay(self.spy), self.TimeRules.BeforeMarketClose(self.spy, 10), self.RankAndRebalance)

    def CoarseSelectionFunction(self, coarse):
        filtered = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)[:100]
        return [c.Symbol for c in filtered if c.HasFundamentalData]

    def FineSelectionFunction(self, fine):
        return [f.Symbol for f in fine if f.MarketCap > 2e9][:self.numberOfSymbols]

    def RankAndRebalance(self):
        if self.Time.weekday() != 0:  # Only run on Mondays
            return
        
        self.liquidStocks = self.ranked_symbols
        
        scores = {}
        for symbol in self.liquidStocks:
            history = self.History(symbol, 210, Resolution.Daily)
            if history.empty or len(history) < 90:
                self.Log(f"Not enough data for {symbol.Value}")
                continue
            
            close = history['close']
            momentum = (close.iloc[-1] / close.iloc[-90]) - 1
            rsi = self.RSI(symbol, 14, Resolution.Daily).Current.Value
            sma50 = self.SMA(symbol, 50, Resolution.Daily).Current.Value
            sma200 = self.SMA(symbol, 200, Resolution.Daily).Current.Value
            
            score = momentum + rsi/100 + (sma50 + sma200)/2
            scores[symbol] = score
        
        self.ranked_symbols = sorted(scores, key=scores.get, reverse=True)[:self.numberOfSymbols]
        
        if not self.ranked_symbols:
            self.Log("No symbols to rank/rebalance.")
            return
        
        for holding in self.Portfolio.Values:
            if holding.Invested and holding.Symbol not in self.ranked_symbols:
                self.Liquidate(holding.Symbol)
        
        for symbol in self.ranked_symbols:
            self.SetHoldings(symbol, 1 / len(self.ranked_symbols))

    def OnSecuritiesChanged(self, changes):
        self.ranked_symbols = [c.Symbol for c in changes.AddedSecurities]