Hello QuantConnect Community! 

I am trying to backtest the following strategy:

  1. Each day open a short SPY straddle at 9:35am ET at mid spread price using 0DTE
  2. Close the position if 10% profit is met
  3. If profit not met, close the position at 12pm ET. Don't hold the position overnight.
  4. This is sequential, meaning one position at a time

 

When I am running the strategy, the position is not opening each day and closing each day. For example, the position would open at 9:35am certain day but close several days or even a month later. Like it is inconsistent. I am not sure what I am doing wrong in my code.

I provided some inline comments for better clarity. I am fairly new to QuantConnect so any great feedback would be great. Thank you.

Here is the code:

from AlgorithmImports import *
from QuantConnect.DataSource import *
import datetime

class USEquityOptionsDataAlgorithm(QCAlgorithm):

    def Initialize(self) -> None:
        self.SetStartDate(2022, 1, 1)
        self.SetEndDate(2022, 12, 31)
        self.SetCash(100000)

        # Requesting data
        self.underlying = self.AddEquity("SPY").Symbol
        option = self.AddOption("SPY")
        self.option_symbol = option.Symbol
        # Set our strike/expiry filter for this option chain
        option.SetFilter(0, 0)
        
        self.contract_call = None
        self.contract_put = None
        self.entry_time = datetime.time(hour=9, minute=35)
        self.exit_time = datetime.time(hour=12)
        self.profit_target = 0.1
        self.position_open = False
        self.entry_price = None

    def OnData(self, slice: Slice) -> None:
        if self.Portfolio[self.underlying].Invested:
            self.Liquidate(self.underlying)

        if self.contract_call is not None and self.Portfolio[self.contract_call.Symbol].Invested:
            return

        chain = slice.OptionChains.get(self.option_symbol)
        if chain:
            # Select call and put contracts with zero days to expiration
            zero_expiry_calls = [contract for contract in chain if contract.Expiry.date() == self.Time.date() and contract.Right == OptionRight.Call]
            zero_expiry_puts = [contract for contract in chain if contract.Expiry.date() == self.Time.date() and contract.Right == OptionRight.Put]
            
            if len(zero_expiry_calls) == 0 or len(zero_expiry_puts) == 0:
                return

            # Select the call and put contracts with the nearest strike to the underlying price
            nearest_strike_call = min(zero_expiry_calls, key=lambda x: abs(chain.Underlying.Price - x.Strike))
            nearest_strike_put = min(zero_expiry_puts, key=lambda x: abs(chain.Underlying.Price - x.Strike))

            self.contract_call = nearest_strike_call
            self.contract_put = nearest_strike_put

            current_time = self.Time
            current_price = self.Securities[self.contract_call.Symbol].Price

            if current_time.time() == self.entry_time and not self.position_open and self.contract_call.Expiry.date() == self.Time.date():
                # Open short straddle position
                self.entry_price = current_price
                self.position_open = True
                self.MarketOrder(self.contract_call.Symbol, -1)  # Short the call option
                self.MarketOrder(self.contract_put.Symbol, -1)  # Short the put option

            if (current_time.time() >= self.exit_time or self.is_profit_target_met(self.entry_price, current_price)) and self.position_open:
                # Close position if profit target is met or it's after 12pm ET
                self.exit_price = current_price
                self.profit = self.calculate_profit(self.entry_price, self.exit_price)
                self.Liquidate(self.contract_call.Symbol)  # Close the call option
                self.Liquidate(self.contract_put.Symbol)  # Close the put option
                self.Debug(f"Closed position at {current_time}: Profit = {self.profit}")
                self.position_open = False

    def calculate_profit(self, entry_price, exit_price):
        if entry_price is None or exit_price is None:
            return 0.0
        return exit_price - entry_price

    def is_profit_target_met(self, entry_price, exit_price):
        profit = self.calculate_profit(entry_price, exit_price)
        return profit >= self.profit_target