Overall Statistics
Total Orders
126
Average Win
0.53%
Average Loss
-0.32%
Compounding Annual Return
8.171%
Drawdown
2.400%
Expectancy
0.294
Start Equity
100000
End Equity
105384.2
Net Profit
5.384%
Sharpe Ratio
0.043
Sortino Ratio
0.054
Probabilistic Sharpe Ratio
58.263%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.64
Alpha
-0.013
Beta
0.076
Annual Standard Deviation
0.047
Annual Variance
0.002
Information Ratio
-1.997
Tracking Error
0.101
Treynor Ratio
0.027
Total Fees
$238.65
Estimated Strategy Capacity
$3100000000.00
Lowest Capacity Asset
ES YJHOAMPYKQGX
Portfolio Turnover
106.41%
# region imports
from AlgorithmImports import *
# endregion

class SwimmingYellowGreenBaboon(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 10, 1)
        self.set_end_date(2024, 6, 1)
        self.set_cash(100000)
    
        # Adds the ES chain
        # The default mapping mode for the continous contract is OPEN_INTEREST, but we will set it explicitly
        # The default data normalization mode for the continous contract is RAW, but we will set it explicitly
        # For other options, see https://www.quantconnect.com/docs/v2/writing-algorithms/universes/futures#12-Continous-Contracts
        self.future = self.add_future(Futures.Indices.SP_500_E_MINI, 
            extended_market_hours=True,
            data_mapping_mode=DataMappingMode.OPEN_INTEREST,
            data_normalization_mode=DataNormalizationMode.RAW)

        self.schedule.on(self.date_rules.every_day(self.future.symbol), self.time_rules.midnight, self.reset)

        self.window = RollingWindow[TradeBar](60)
        self.selloff_window = RollingWindow[bool](3)
        self.selloff_range = 10 * self.future.symbol_properties.minimum_price_variation * 10
        self.trailing_amount = 5 * self.future.symbol_properties.minimum_price_variation * 10

    def on_data(self, data: Slice):
        
        # Get the trade bar information from the Slice object, and update the following window
        bar = data.bars.get(self.future.symbol)
        if bar:
            self.window.add(bar)

        # Do not open new positions if we hold the contract
        if self.portfolio[self.future.mapped].invested:
            return

        movement = self.get_market_movement()
        self.selloff_window.add(movement > self.selloff_range)

        # IF we're remained below the 1 hour range for the entire 2 minutes.
        if self.selloff_window.is_ready and all(self.selloff_window):
            self.selloff_window.reset()
            # If less than 1 hour close price; continues to sell off; enter buy order
            self.market_order(self.future.mapped, 1)
            # Then create a trailing stop 5 ticks below, trail up.
            self.trailing_stop_order(self.future.mapped, -1, self.trailing_amount, trailing_as_percentage=False)

    def reset(self):
        # Exit on end of day
        self.liquidate()
        self.selloff_window.reset()

    def get_market_movement(self):
        # Wait until we have all the elements to calculate the min and max
        if not self.window.is_ready:
            return 0

        # ADD End - Start
        start_value = self.window[self.window.count-1].close
        end_value = self.window[0].close
        return end_value - start_value

        # RANGE WITHIN WINDOW
        #min_value = min(self.window, key=lambda bar: bar.low).low
        #max_value = max(self.window, key=lambda bar: bar.high).high
        #return max_value - min_value