Overall Statistics
Total Orders
1126
Average Win
0.61%
Average Loss
-0.26%
Compounding Annual Return
14.967%
Drawdown
12.900%
Expectancy
0.121
Start Equity
100000
End Equity
118201.91
Net Profit
18.202%
Sharpe Ratio
0.559
Sortino Ratio
0.743
Probabilistic Sharpe Ratio
52.595%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
2.39
Alpha
-0.068
Beta
0.794
Annual Standard Deviation
0.096
Annual Variance
0.009
Information Ratio
-1.942
Tracking Error
0.051
Treynor Ratio
0.068
Total Fees
$1361.55
Estimated Strategy Capacity
$44000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
255.63%
# region imports
from AlgorithmImports import *
import numpy as np
# endregion

class SwimmingFluorescentPinkLemur(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2024, 6, 1)
        self.set_cash(100000)
        self.symbol = self.add_equity("SPY").symbol

        # Set up indicators
        self._macd = self.MACD(self.symbol, 8, 17, 9, MovingAverageType.Simple)
        self._bb = self.BB(self.symbol, 18, 2, MovingAverageType.Simple)
        self._kc = self.KCH(self.symbol, 18, 1.5, MovingAverageType.Simple)

        # Create a RollingWindow to store the past 3 values of the MACD Histogram
        self._macd_hist_window = RollingWindow[IndicatorDataPoint](3)

        # Consolidate the data into 5-minute bars
        self.Consolidate(self.symbol, timedelta(minutes=5), self.on_data_consolidated)
        self.register_indicator(self.symbol, self._macd, timedelta(minutes=5))
        self.register_indicator(self.symbol, self._bb, timedelta(minutes=5))
        self.register_indicator(self.symbol, self._kc, timedelta(minutes=5))

        # Setting stoploss
        self.stop_loss_len = 20*5
        self.stop_loss_indicator = self.MIN(self.symbol, self.stop_loss_len, Resolution.MINUTE)
        self.stop_loss = None

        # Warming up engine
        self.set_warm_up(5*20, Resolution.MINUTE)

    def on_data_consolidated(self, data: slice):

        # Check if the data strategy is warming up
        if self.is_warming_up:
            return
        
        # Check if the Bollinger Bands are within the Keltner Channels
        self.squeeze = self._bb.UpperBand.Current.Value < self._kc.UpperBand.Current.Value and self._bb.LowerBand.Current.Value > self._kc.LowerBand.Current.Value
        self.Log(f"Squeeze indicator: {self.squeeze}")

        # Check for MACD entry signal
        self._macd_hist_window.Add(self._macd.Histogram.Current)

        # Ensure we have 3 data points in the window
        if self._macd_hist_window.IsReady:
            macd_hist = self._macd_hist_window[0].Value  # Current MACD Histogram value
            macd_hist_1 = self._macd_hist_window[1].Value  # MACD Histogram value 1 bar ago
            macd_hist_2 = self._macd_hist_window[2].Value  # MACD Histogram value 2 bars ago

            self.macd_long_in = (macd_hist > macd_hist_1 or macd_hist > macd_hist_2) and macd_hist > 0

            self.Log(f"MACD entry: {self.macd_long_in}")
        
        # Entry
        if not self.portfolio.invested:
            if self.squeeze and self.macd_long_in:
                self.log(f"Price is {data.Close}")
                self.set_holdings(self.symbol, 1)
                self.stop_loss = self.stop_loss_indicator.Current.Value
                self.log(f"Stop loss level {self.stop_loss}")
        # Exit
        if self.portfolio.invested:
            if data.Close < self.stop_loss or not self.Time.hour in range(9, 16):
                self.log(f"Stop loss at {data.Close}")
                self.liquidate(self.symbol)