Overall Statistics
Total Orders
1123
Average Win
6.32%
Average Loss
-6.22%
Compounding Annual Return
29.757%
Drawdown
63.400%
Expectancy
0.201
Start Equity
1000000
End Equity
44734356.33
Net Profit
4373.436%
Sharpe Ratio
0.687
Sortino Ratio
0.542
Probabilistic Sharpe Ratio
4.003%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
1.02
Alpha
0.154
Beta
2.175
Annual Standard Deviation
0.502
Annual Variance
0.252
Information Ratio
0.6
Tracking Error
0.429
Treynor Ratio
0.158
Total Fees
$1766334.61
Estimated Strategy Capacity
$11000000.00
Lowest Capacity Asset
BGU U7EC123NWZTX
Portfolio Turnover
30.63%
# region imports
from AlgorithmImports import *
# endregion

class MomentumStrategy(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2010, 1, 1)
        # self.set_end_date(2024, 7, 1)
        self.set_cash(1000000)
        self.spy = self.add_equity("SPXL", Resolution.DAILY).symbol
        self.set_benchmark("SPY")

        # Schedule recalibration at the start of each week
        self.schedule.on(self.date_rules.every_day(), self.time_rules.at(15, 58), self.rebalance)

        self.state = 0

        chart = Chart("My Custom Chart")
        self.add_chart(chart)
        chart.AddSeries(Series("SPY Close", SeriesType.LINE, '$'))
        chart.AddSeries(Series("Long_term_mean", SeriesType.LINE, '$'))
        chart.AddSeries(Series("Short_term_mean", SeriesType.LINE, '$'))
        chart.AddSeries(Series(name="Buy Signal", type=SeriesType.SCATTER, unit='$', color=Color.DarkGreen, symbol=ScatterMarkerSymbol.TRIANGLE))
        chart.AddSeries(Series("Sell Signal", SeriesType.SCATTER, '$', Color.DarkRed, ScatterMarkerSymbol.TRIANGLE_DOWN))
        chart.AddSeries(Series("Volatility Upper", SeriesType.Line, '$'))
        chart.AddSeries(Series("Volatility Lower", SeriesType.Line, '$'))

        self.volatility_period = 27  # Period for volatility calculation
        self.rolling_window = RollingWindow[float](self.volatility_period)


            
    def rebalance(self):
        history = self.history(self.spy, self.volatility_period+1, Resolution.DAILY)
        if history.empty or history.isnull().values.any():
            raise ValueError("Empty History")

        #self.Debug(history)
        
        self.data = history['close'].unstack(level=0)
        for i in range(len(self.data)):
            self.rolling_window.add(self.data.iloc[i])

        long_term_mean = float(self.data.mean().values) * float(self.data.std().values) * np.sqrt( 252 / len(self.data))
        short_term_mean = float(self.data[:5].mean().values) * float(self.data[:5].std().values) * np.sqrt( 252 / 5)

        self.Plot("My Custom Chart", "SPY Close", self.data.iloc[-1])
        self.Plot("My Custom Chart", "Long_term_mean", long_term_mean)
        self.Plot("My Custom Chart", "Short_term_mean", short_term_mean)

        if self.rolling_window.IsReady:
            returns = [self.rolling_window[i] / self.rolling_window[i+1] - 1 for i in range(self.volatility_period - 1)]
            volatility = np.std(returns)*500
            upper_bar = 100 + volatility / 2
            lower_bar = 100 - volatility / 2

            self.Plot("My Custom Chart", "Volatility Upper", upper_bar)
            self.Plot("My Custom Chart", "Volatility Lower", lower_bar)


        self.previous_state = self.state 

        if (float(self.data.std().values) * np.sqrt( 252 / len(self.data))) > 22.5:
            self.debug(f"HIGH VOL {(float(self.data.std().values) * np.sqrt( 252 / len(self.data)))}")
            self.liquidate()
            return

        if  short_term_mean < long_term_mean and self.state != 1:
            self.state = 1
        elif short_term_mean > long_term_mean and self.state != -1:
            self.state = -1

        if self.previous_state != self.state:
            self.trade()
        
    

    def trade(self):
        if self.state == 1:
            self.plot("My Custom Chart", "Buy Signal", self.data.iloc[-1])
            self.liquidate()
            self.set_holdings("SPXL", 1.9)

        if self.state == -1:
            self.plot("My Custom Chart", "Sell Signal", self.data.iloc[-1])
            self.liquidate()
            # self.set_holdings("SPXL", -1.9)