Trading 0DTE Options with the IBKR Native API

    Date:

    The article “Trading 0DTE Options with the IBKR Native API” first appeared on Robot Wealth blog.

    Here’s a thing that I suspect will make money, but that I haven’t yet tested (for reasons that I will explain shortly):

    • Every day, at the start of the trading day, get the SPX straddle price and convert it to an expected SPX price move.
    • Then at the end of the trading day, take the SPX price and calculate if it moved more or less than the straddle implied.
    • Aggregate this over a few days – the simplest way would be to take the average expected move and the average actual move.
    • The trading signal for the next day’s open is as follows:
      • If SPX moved more than was implied by the straddle on average over the last few days, buy the straddle.
      • If SPX moved less than implied on average, sell the straddle.

    That is:

    if mean(actual_move) > mean(expected_move):
      buy tomorrow's 0DTE ATM straddle
    else if mean(actual_move) < mean(expected_move):
      sell tomorrow's 0DTE ATM straddle

    It’s essentially fitting to how well the options market predicted spoos volatility in the very short term.

    I think there’s probably an edge here, but I’m also concerned about costs, since the cost of trading a 0DTE straddle will be a large percentage of its return, most of the time.

    Why haven’t I tested this properly?

    Because the data is hard to come by. I didn’t have it at hand, and I couldn’t get enough from the usual free sources to test it properly.

    When I have a suspicion about a possible edge, or someone tells me about an edge they have a hunch about (which is where this idea came from), I like to move fast and test it out with minimal investment in time and money. Most of the time, these things don’t work out, and so I want to disprove ideas quickly and move on. To do that, you need ready access to data.

    But the data for this idea is a little awkward. Specifically, you need historical ATM SPXW options daily open and close prices (which I don’t have). And most of the usual sources (including IBKR) don’t provide data for expired contracts.

    What to do instead?

    You have some options. You could go and buy the data and test it properly. Or, you could just start trading it in small size and collect the data you need along the way.

    In this case, I’m thinking about doing the latter. I like this approach because I learn a lot about the strategy by actually executing it. And if it has no edge, then the expected costs are just the trading fees.

    Also, I promised I’d show you some 0DTE options stuff with the IB Native API! So here goes.

    In this article, I’ll show you how to calculate the signal for this strategy and place the appropriate trades via the IB Native API.

    Straddle prices and expected price moves

    The price of the ATM straddle allows you to estimate the expected move in the underlying that the options are pricing in. The generally accepted formula, which is just an estimate, is:

    ΔPu ​= 0.85 ∗ Ps

    where Pu​ is the price of the underlying, and Ps​ is the price of the ATM straddle.

    For example say our underlying was priced at 100.The ATM straddle costs 15. The expected move is therefore 0.85*15 =

    That means that at expiry of the straddle, we expect price to be within 100 +/- 12.75, that is between 12.75. That means that at expiry of the straddle, we expect price to be within 100+/−12.75, that is between 87.25 and $112.75.

    Data requirements

    For this strategy, we need the following data:

    • Daily open and close SPX prices (the open price will be our straddle strike price, and the close will allow us to calculate the actual price move)
    • Daily 0DTE ATM call and put option open prices

    Assuming we have the appropriate market data subscriptions, we can get daily SPX prices directly out of TWS.

    Options data is more problematic.

    Even if you have the appropriate subscriptions, you won’t be able to get expired options contracts out of TWS. And we need a few days’ worth of expired contracts to calculate our trading signal.

    So we’ll get that data from Yahoo Finance instead.

    You can get XPSW options chains from Yahoo at this url: https://finance.yahoo.com/quote/%5ESPX/options/

    And you can get price data for options that haven’t yet expired by selecting the expiry date from the dropdown:

    I did some experimenting and found that you could also get price data for recently expired options using the following pattern for the URL:

    https://finance.yahoo.com/quote/SPXW{expiry}{contract_type}0{strike*1000}

    where contract_type is “C” or “P” for call and put respectively, and expiry is of the format yymmdd.

    Here’s an example of what’s available for an option that expired yesterday:

    We have the open price on expiry day in the table, and I think the number in large bold is the last traded price on expiry day. I don’t know whether the open price represents the mid price, the bid, the ask, the first trade, or something else.

    Unfortunately it seems Yahoo only makes a few days’ worth of contracts available. I could find nothing older than about five days. So this data source won’t be useful for backtesting, but maybe we can use it for calculating our trade signal.

    A strategy architecture

    Given our data requirements, we can craft a trading application using the IB Native API that consists of the following:

    • Connect to TWS
    • Request four (say) days of SPX price data from TWS
    • Using SPX open prices as our strike prices, get SPXW call and put open and last traded prices on expiry day from Yahoo for the prior four days
    • Calculate the ATM 0DTE straddle price and implied expected move for each of the four days
    • Calculate the actual move from the open and close prices of SPX for each of the four days
    • Calculate the mean expected and actual moves over the four days
    • Calculate the trade signal
    • Place the appropriate trades in TWS

    Strategy implementation

    Following is some Python code for implementing this strategy.

    The main purpose is to demonstrate how to trade options using the IB Native API. If you do trade this strategy, be aware of the following caveats:

    • I don’t have confidence in it at the moment – it’s only based on a hunch
    • The options data source is dubious (I don’t even have confidence that the data represents what I think it does)

    With that out of the way, here’s the code. I’ve heavily commented it so that you can follow along if this is new to you.

    For an introduction to the IB Native API, read this article first.

    I ran this shortly after Friday’s open (22 March 2024). You would need to make some modifications to run it in the future (for example update the start_date and end_date parameters that control the SPX price data download) etc.

    Also, I’ve only included the bare minimum you need to get started with a strategy such as this. In particular, I’ve only included the minimum order handling logic required to trade a straddle using limit orders at the current bid/ask price. I’ve not made any attempt to handle cases where the order isn’t filled. I’ve also not included handling of any edge cases (for example, where an fails to be received by TWS).

    # Strategy for trading 0DTE straddles via the IBKR native API
    # Buy/sell the 0DTE ATM straddle if recent 0DTE straddles under/over-predicted the day's spoos move
    
    from threading import Thread, Event
    import time
    from typing import Any
    from ibapi.common import BarData
    from ibapi.wrapper import EWrapper
    from ibapi.client import EClient
    from ibapi.contract import Contract, ContractDetails
    from ibapi.order import *
    from ibapi.common import *
    from ibapi.account_summary_tags import AccountSummaryTags
    import pandas as pd
    import requests
    from bs4 import BeautifulSoup
    
    
    # make url for 0DTE ATM option contracts from yahoo
    # can only get approx 5 days' worth of expired contracts
    def make_url(expiry, contract_type, strike):
        return f"https://finance.yahoo.com/quote/SPXW{expiry}{contract_type}0{strike*1000}"
    
    # get 0dte options prices from yahoo
    # use beautiful soup to parse the HTML and extract values from relevant tags
    def get_0dte_prices(expiry, contract_type, strike):
        url = make_url(expiry, contract_type, strike)
    
        # headers to simulate browser request
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"
        }
    
        # get web page
        response = requests.get(url, headers=headers)
    
        # parse
        soup = BeautifulSoup(response.text, "html.parser")
    
        # extract last traded price (HTML specification is from inspection of Yahoo options data pages)
        last_traded_price_tag = soup.find(
            "fin-streamer", {"data-test": "qsp-price", "data-field": "regularMarketPrice"}
        )
        last_traded_price = (
            float(last_traded_price_tag["value"]) if last_traded_price_tag else "Not found"
        )
    
        # extract open price (HTML specification is from inspection of Yahoo options data pages)
        open_price_tag = soup.find("td", {"data-test": "OPEN-value"})
        open_price = float(open_price_tag.text) if open_price_tag else "Not found"
    
        return (open_price, last_traded_price)
    
    
    # class for trading the straddles strategy
    class ibStraddlesApp(EClient, EWrapper):
        def __init__(self, ticker):
            EClient.__init__(self, self)
            self.ticker = ticker
            self.next_req_id = 0  # keep track of request ids
            self.connection_ready = Event()  # to signal the connection has been established
            self.done = (
                Event()
            )  # for signalling between threads when a message has been fully processed
            self.historical_data = []  # for storing historical data
            self.price = {}  # for storing current option contract price data
    
        def increment_req_id(self):
            self.next_req_id += 1
    
        # override Ewrapper.error
        def error(
            self, reqId: TickerId, errorCode: int, errorString: str, contract: Any = None
        ):
            print("Error: ", reqId, " ", errorCode, " ", errorString)
            if errorCode == 502:
                # not connected
                # set self.done (a threading.Event) to True
                self.done.set()
    
        # override Ewrapper.nextValidID - used to signal that the connection between application and TWS is complete, and
        # returns the next valid orderID (for any future transactions).
        # if we send messages before the connection has been established, they can be lost
        # so wait for this method to be called
        def nextValidId(self, orderId: int):
            self.nextorderId = orderId
            print(f"Connection ready, next valid order ID: {orderId}")
            self.connection_ready.set()  # signal that the connection is ready
    
        # override Ewrapper.contractDetails
        # gets back contract details
        def contractDetails(self, reqId: int, contractDetails: ContractDetails):
            super().contractDetails(reqId, contractDetails)
            print(contractDetails)
    
        # override Ewraper.contractDetailsEnd
        # signals that the contract details request has completed
        def contractDetailsEnd(self, reqId: int):
            print(f"Contract details request {reqId} complete")
            self.done.set()
    
        # override Ewrapper.historicalData
        # TWS sends data from reqHistoricalData to this callback
        def historicalData(self, reqId: int, bar: BarData):
            d = {
                "Ticker": self.ticker,
                "Date": bar.date,
                "Open": bar.open,
                "High": bar.high,
                "Low": bar.close,
                "Close": bar.close,
            }
            self.historical_data.append(d)
    
        # override EWrapper.historicalDataEnd is received
        # signals that historical market data request is complete
        # (note only sent if reqHistoricalData invoked with keepUpToDate=False)
        def historicalDataEnd(self, reqId: int, start: str, end: str):
            print(f"Historical data request complete")
            self.done.set()
    
        # callback for handling current price data requests
        # see https://ibkrcampus.com/ibkr-api-page/twsapi-doc/#available-tick-types for available tickTypes
        def tickPrice(self, reqId, tickType, price, attrib):
            """Key self.price with the reqId that made the request so that you can keep track of which item belongs to which contract.
            See usage below for example.
            """
            if not reqId in self.price.keys():
                self.price[reqId] = {}
    
            # handle bid price (tickType = 1)
            if tickType == 1:
                print("The current bid price is: ", price)
                self.price[reqId]["bid"] = price
    
            # handle ask price (tickType = 2)
            if tickType == 2:
                print("The current ask price is: ", price)
                self.price[reqId]["ask"] = price
    
        # override Ewrapper.tickSnapshotEnd
        # signals snapshot request is complete
        def tickSnapshotEnd(self, reqId: int):
            print(f"Current price data request complete")
            self.done.set()
    
        # override Ewrapper order callbacks - just print execution and order status to screen
        # in practice, you would monitor these and handle appropriately (eg if order wasn't filled)
        def orderStatus(
            self,
            orderId,
            status,
            filled,
            remaining,
            avgFullPrice,
            permId,
            parentId,
            lastFillPrice,
            clientId,
            whyHeld,
            mktCapPrice,
        ):
            print(
                "orderStatus - orderid:",
                orderId,
                "status:",
                status,
                "filled",
                filled,
                "remaining",
                remaining,
                "lastFillPrice",
                lastFillPrice,
            )
    
        def openOrder(self, orderId, contract, order, orderState):
            print(
                "openOrder id:",
                orderId,
                contract.symbol,
                contract.secType,
                "@",
                contract.exchange,
                ":",
                order.action,
                order.orderType,
                order.totalQuantity,
                orderState.status,
            )
    
        def execDetails(self, reqId, contract, execution):
            print(
                "Order Executed: ",
                reqId,
                contract.symbol,
                contract.secType,
                contract.currency,
                execution.execId,
                execution.orderId,
                execution.shares,
                execution.lastLiquidity,
            )
    
    
    # define our event loop - this will run in its own thread
    def run_loop(app):
        app.run()
    
    
    ##### Instantiate a trading app and wait for a successful connection
    
    # instantiate an ibStraddlesApp
    # ticker of the underlying
    app = ibStraddlesApp(ticker="SPX")
    
    # connect
    # clientID identifies our application
    app.connect("127.0.0.1", 7496, clientId=0)
    
    # start the application's event loop in a thread
    api_thread = Thread(target=run_loop, args=(app,), daemon=True)
    api_thread.start()
    
    # wait until the Ewrapper.nextValidId callback is triggered, indicating a successful connection
    app.connection_ready.wait()
    
    ##### Get SPX data required for trading signal
    
    # required date range - will need to update this
    start_date = "20240318"
    end_date = "20240322"
    date_range = pd.date_range(start=start_date, end=end_date)
    num_days = len(date_range)
    
    # get recent SPX data
    spx = Contract()
    spx.symbol = "SPX"
    spx.secType = "IND"
    spx.currency = "USD"
    spx.exchange = "CBOE"
    
    # request historical SPX data and increment request id
    app.reqHistoricalData(
        reqId=app.next_req_id,
        contract=spx,
        # 8:30 chicago is 9:30 NY
        endDateTime=f"{end_date} 16:00:00 US/Central",
        durationStr=f"{num_days} D",
        whatToShow="TRADES",
        barSizeSetting="1 day",
        useRTH=1,
        formatDate=1,
        keepUpToDate=False,
        chartOptions=[],
    )
    app.increment_req_id()
    
    # wait for historical data to come back before continuing
    app.done.wait()
    
    # reset event
    app.done.clear()
    
    # make dataframe from receieved SPX data
    spx_prices = pd.DataFrame(app.historical_data)
    
    # save to disk
    # spx_prices.to_csv("./ibkr-api/spx_prices.csv")
    
    print("spx price data:")
    print(spx_prices)
    
    # check you have the expected data:
    if len(spx_prices["Date"]) < 5:
        raise Exception("Missing SPX data, stopping")
    
    ##### Get options data and calculate trading signal
    
    # get historical ATM 0DTE XPSW options prices from Yahoo
    expected_moves = []
    actual_moves = []
    for d in spx_prices["Date"][:-1]:  # don't get today's data yet
        # get ATM strike from each day's SPX open price
        spx_open = spx_prices.loc[spx_prices["Date"] == d, "Open"].iloc[0]
    
        # round to nearest $5
        strike = round(spx_open / 5) * 5
    
        # get epxiry from date string
        expiry = str(d)[2:]
    
        # get put open and last traded prices
        p_open, p_close = get_0dte_prices(expiry=expiry, contract_type="P", strike=strike)
    
        # if we didn't find an ATM strike:
        # a bit of hack to get the nearest existing strike
        # in reality you'd be more careful
        while p_open == "Not found":
            strike -= 5
            p_open, p_close = get_0dte_prices(
                expiry=expiry, contract_type="P", strike=strike
            )
    
        # get call open and last traded prices
        c_open, c_close = get_0dte_prices(expiry=expiry, contract_type="C", strike=strike)
    
        # calculate straddle open and last traded prices
        straddle_open_price = p_open + c_open
    
        # calculate straddle-implied expected move and store
        expected_move = 0.85 * straddle_open_price
        expected_moves.append(expected_move)
    
        # calculate actual move from SPX prices and store
        spx_close = spx_prices.loc[spx_prices["Date"] == d, "Close"].iloc[0]
        actual_move = abs(spx_close - spx_open)
        actual_moves.append(actual_move)
    
        print("Expected move, actual move:")
        print(expected_move, actual_move)
    
    # calculate recent average expected and actual moves
    ave_expected_move = sum(expected_moves) / len(expected_moves)
    ave_actual_move = sum(actual_moves) / len(actual_moves)
    print("Average expected move, average actual move")
    print(ave_expected_move, ave_actual_move)
    
    ##### Prepare contract objects and get current prices for trading
    
    # get today's ATM strike and expiry
    today_open = spx_prices["Open"].iloc[-1]
    strike = round(today_open / 5) * 5
    expiry = spx_prices["Date"].iloc[-1]
    print(f"Today's strike:{strike}, today's expiry: {expiry}")
    
    # create an options contract representing today's 0DTE ATM call
    call = Contract()
    call.symbol = "SPXW"
    call.secType = "OPT"
    call.currency = "USD"
    call.exchange = "SMART"
    call.lastTradeDateOrContractMonth = expiry
    call.strike = strike
    call.right = "C"
    call.tradingClass = "SPXW"
    
    # keep track of the reqId for the call
    call_req_id = app.next_req_id
    
    # get the call's current bid-ask prices
    print("Current call prices:")
    app.reqMktData(
        reqId=call_req_id,
        contract=call,
        genericTickList="",  # get all available data
        snapshot=True,  # requires appropriate market data subscription
        regulatorySnapshot=False,
        mktDataOptions=[],
    )
    app.increment_req_id()
    app.done.wait()
    app.done.clear()
    
    # call prices:
    # note app.price is keyed by reqId
    print(app.price[call_req_id])
    
    # create an options contract representing today's 0DTE ATM put
    put = Contract()
    put.symbol = "SPXW"
    put.secType = "OPT"
    put.currency = "USD"
    put.exchange = "SMART"
    put.lastTradeDateOrContractMonth = expiry
    put.strike = strike
    put.right = "P"
    put.tradingClass = "SPXW"
    
    # keep track of the reqId for the put
    put_req_id = app.next_req_id
    
    # get the put's current bid-ask prices
    print("Current put prices:")
    app.reqMktData(
        reqId=put_req_id,
        contract=put,
        genericTickList="",  # get all available data types
        snapshot=True,  # requires appropriate market data subscription
        regulatorySnapshot=False,
        mktDataOptions=[],
    )
    app.increment_req_id()
    app.done.wait()
    app.done.clear()
    
    # put prices:
    # note app.price is keyed by reqId
    print(app.price[put_req_id])
    
    ##### Trade logic
    
    # Now we have call and put prices for today, we can shoot off limit orders at these prices
    # I prefer this to shooting off a market order into a potentially illiquid product
    # You can even add a buffer to your limit order beyond the current top of book price to give you more chance of getting filled
    # Many ways to do this - up to you.
    # But in practice, you'll need to manage your orders and fills - you might end up chasing the market
    
    # sell today's 0DTE ATM straddle if SPX has moved less than implied by recent straddle prices
    if ave_actual_move < ave_expected_move:
        # sell today's 0DTE stradle
        # sell call:
        print("Selling today's 0DTE ATM straddle")
        order = Order()
        order.action = "SELL"
        order.totalQuantity = 1  # note will be multiplied by contract multiplier
        order.orderType = "LMT"
        order.lmtPrice = app.price[call_req_id]["bid"]  # limit sell the call at the bid
        order.transmit = True
        app.placeOrder(app.nextorderId, call, order)
        app.nextorderId += 1
    
        # sell put:
        order = Order()
        order.action = "SELL"
        order.totalQuantity = 1  # note will be multiplied by contract multiplier
        order.orderType = "LMT"
        order.lmtPrice = app.price[put_req_id]["bid"]  # limit sell the put at the bid
        order.transmit = True
        app.placeOrder(app.nextorderId, put, order)
        app.nextorderId += 1
    # buy today's 0DTE ATM straddle if SPX has moved more than implied by recent straddle prices
    else:
        # buy today's 0DTE stradle
        # buy call:
        print("Buying today's 0DTE ATM straddle")
        order = Order()
        order.action = "BUY"
        order.totalQuantity = 1  # note will be multiplied by contract multiplier
        order.orderType = "LMT"
        order.lmtPrice = app.price[call_req_id]["ask"]  # limit buy the call at the ask
        order.transmit = True
        app.placeOrder(app.nextorderId, call, order)
        app.nextorderId += 1
    
        # buy put:
        order = Order()
        order.action = "BUY"
        order.totalQuantity = 1  # note will be multiplied by contract multiplier
        order.orderType = "LMT"
        order.lmtPrice = app.price[put_req_id]["ask"]  # limit buy the put at the ask
        order.transmit = True
        app.placeOrder(app.nextorderId, put, order)
        app.nextorderId += 1
    
    # You'll want to handle the order as well - maybe cancel it if the market runs away for instance.
    # When placing orders via the API and building a robust trading system, it is important to monitor for callback notifications,
    # specifically for IBApi::EWrapper::error, IBApi::EWrapper::orderStatus changes, IBApi::EWrapper::openOrder warnings, and
    # IBApi::EWrapper::execDetails to ensure proper operation.
    # Here we just use these methods to print execution details to screen.
    
    # disconnect once orders handled satisfactorily
    # commented here for simplicity since we didn't do any order handling
    # app.disconnect()

    Here’s the output from running this code shortly after Friday’s open:

    Connection ready, next valid order ID: 16
    Error:  -1   2104   Market data farm connection is OK:usfarm.nj
    Error:  -1   2104   Market data farm connection is OK:usfuture
    Error:  -1   2104   Market data farm connection is OK:usopt
    Error:  -1   2104   Market data farm connection is OK:usfarm
    Error:  -1   2106   HMDS data farm connection is OK:euhmds
    Error:  -1   2106   HMDS data farm connection is OK:cashhmds
    Error:  -1   2106   HMDS data farm connection is OK:fundfarm
    Error:  -1   2106   HMDS data farm connection is OK:ushmds
    Error:  -1   2158   Sec-def data farm connection is OK:secdefnj
    Historical data request complete
    spx price data:
      Ticker      Date     Open     High      Low    Close
    0    SPX  20240318  5154.77  5175.60  5149.42  5149.42
    1    SPX  20240319  5139.09  5180.31  5178.51  5178.51
    2    SPX  20240320  5181.69  5226.19  5224.62  5224.62
    3    SPX  20240321  5253.43  5261.10  5241.53  5241.53
    4    SPX  20240322  5242.48  5244.21  5241.18  5244.18
    Expected move, actual move:
    32.852500000000006 5.350000000000364
    Expected move, actual move:
    22.8905 39.42000000000007
    Expected move, actual move:
    31.517999999999997 42.93000000000029
    Expected move, actual move:
    21.504999999999995 11.900000000000546
    Average expected move, average actual move
    27.1915 24.90000000000032
    Today's strike:5240, today's expiry: 20240322
    Current call prices:
    The current bid price is:  16.43
    The current ask price is:  16.45
    Current price data request complete
    {'bid': 16.43, 'ask': 16.45}
    Current put prices:
    The current bid price is:  12.44
    The current ask price is:  12.49
    Current price data request complete
    {'bid': 12.44, 'ask': 12.49}
    Selling today's 0DTE ATM straddle
    openOrder id: 17 SPX OPT @ SMART : SELL LMT 1 PreSubmitted
    orderStatus - orderid: 17 status: PreSubmitted filled 0 remaining 1 lastFillPrice 0.0
    Order Executed:  -1 SPX OPT USD 00020057.65fd0c22.01.01 12 1 2
    openOrder id: 17 SPX OPT @ SMART : SELL LMT 1 Filled
    orderStatus - orderid: 17 status: Filled filled 1 remaining 0 lastFillPrice 16.43
    openOrder id: 17 SPX OPT @ SMART : SELL LMT 1 Filled
    orderStatus - orderid: 17 status: Filled filled 1 remaining 0 lastFillPrice 16.43
    openOrder id: 18 SPX OPT @ SMART : SELL LMT 1 Submitted
    orderStatus - orderid: 18 status: Submitted filled 0 remaining 1 lastFillPrice 0.0
    Order Executed:  -1 SPX OPT USD 00020057.65fd0c2b.01.01 13 1 1
    openOrder id: 18 SPX OPT @ SMART : SELL LMT 1 Filled
    orderStatus - orderid: 18 status: Filled filled 1 remaining 0 lastFillPrice 12.44
    openOrder id: 18 SPX OPT @ SMART : SELL LMT 1 Filled
    orderStatus - orderid: 18 status: Filled filled 1 remaining 0 lastFillPrice 12.44

    You can see that the strategy successfully connected to TWS, and received the SPX data for today and the previous 4 sessions. Note that today’s data is incomplete – the OHLC data only represents a short period after 8:30am Chicago time. But we only need today’s open price (for figuring out today’s ATM strike).

    It then calculated the last four days’ straddle-implied expected moves and the actual SPX moves, and then took the averages to get the trade signal.

    Next, it got today’s strike from today’s SPX open price.

    It then asked TWS for a current snapshot of top of book prices for the 0DTE ATM call and put, and used these prices to submit limit orders.

    In this case, shortly after the orders were sent, they were filled. In practice, you would need to handle this more carefully, for example dealing with cases where the market ran away from you and your limit order was unfilled.

    Conclusion

    In this article, we saw a minimal implementation of a strategy that uses the recent SPX actual moves and straddle-implied moves to directionally trade 0DTE straddles.

    The main hurdle was obtaining data for expired SPXW options, but luckily the very recent contracts are available on Yahoo Finance.

    It would be nice to simulate this strategy, but it would require historical SPXW option opening and closing prices. Until then, I am considering trading this at very small size (using the minis) just to gain some insight into how it trades.

    Please note that this is only a minimal example. In particular, I’ve not included any order handling logic, other than to submit limit orders and print their status to screen.

    Disclosure: Interactive Brokers

    Information posted on IBKR Campus that is provided by third-parties does NOT constitute a recommendation that you should contract for the services of that third party. Third-party participants who contribute to IBKR Campus are independent of Interactive Brokers and Interactive Brokers does not make any representations or warranties concerning the services offered, their past or future performance, or the accuracy of the information provided by the third party. Past performance is no guarantee of future results.

    This material is from Robot Wealth and is being posted with its permission. The views expressed in this material are solely those of the author and/or Robot Wealth and Interactive Brokers is not endorsing or recommending any investment or trading discussed in the material. This material is not and should not be construed as an offer to buy or sell any security. It should not be construed as research or investment advice or a recommendation to buy, sell or hold any security or commodity. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.

    Disclosure: Order Types / TWS

    The order types available through Interactive Brokers LLC’s Trader Workstation are designed to help you limit your loss and/or lock in a profit. Market conditions and other factors may affect execution. In general, orders guarantee a fill or guarantee a price, but not both. In extreme market conditions, an order may either be executed at a different price than anticipated or may not be filled in the marketplace.

    Disclosure: Options Trading

    Options involve risk and are not suitable for all investors. Multiple leg strategies, including spreads, will incur multiple commission charges. For more information read the “Characteristics and Risks of Standardized Options” also known as the options disclosure document (ODD) or visit ibkr.com/occ

    Disclosure: API Examples Discussed

    Throughout the lesson, please keep in mind that the examples discussed are purely for technical demonstration purposes, and do not constitute trading advice. Also, it is important to remember that placing trades in a paper account is recommended before any live trading.

    Go Source

    Chart

    Sign up for Breaking Alerts

    Share post:

    Popular

    More like this
    Related

    Folks Are Saving as If Tomorrow Won’t Arrive: Apr. 26, 2024

    Big-tech earnings last night from Alphabet and Microsoft are...

    Bond Modified Duration in Excel and R

    Bond duration is a basic building block for bond...

    Google, How Does One Get a Stock Higher?  And What’s Stagflation?

    The story this morning is clearly the stunning rise...

    Investors Are Growing Wary. 6 Rules for a Nervous Stock Market.

    Your Privacy When you visit any website it may use...