[Python] How to place IBKR bracket order before market open using actual open price for TP/SL?

Discussão em 'Python' iniciado por Stack, Junho 17, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to place a market-on-open (MOO) bracket order through Interactive Brokers using ib_insync. My goal is to:

    Enter the trade strictly at the market open (9:30 AM ET) Calculate take-profit (TP) and stop-loss (SL) levels based on the actual executed entry price. Submit a parent MOO buy order, followed by child sell orders for TP and SL. However, I’m facing several issues: I can’t reliably fetch the real open price using tick.last at or before 9:30 AM, even though I have real-time market data subscription.

    If I wait for 9:30, fetch the price, and then submit the bracket order, the execution lags and the entry price has often moved, making TP/SL levels inaccurate.

    If I try to submit the order before 9:30, I can't set realistic TP/SL prices yet because I don’t know what the actual entry price will be.

    So the problem is:

    How can I submit the MOO order before market opens, and

    Then calculate and place TP/SL child orders after execution, once I know the real entry price?

    from ib_insync import *
    import datetime
    import time
    import numpy as np
    import pytz
    import yfinance as yf
    import os
    import pandas as pd
    # Connect to IBKR
    ib = IB()
    try:
    ib.connect('127.0.0.1', 4002, clientId=2)
    print("Connected to IBKR!")
    except Exception as e:
    print("Connection failed:", e)
    exit()

    # Global variables
    invested_amount = None
    sell_price = None
    quantity_bought = 0
    trade_data = []
    buy_price = None # Global buy price

    def wait_for_market_open():
    ny_tz = pytz.timezone('America/New_York')
    while True:
    now = datetime.datetime.now(pytz.utc).astimezone(ny_tz)
    market_open_time = now.replace(hour=9, minute=30, second=0, microsecond=0)
    market_close_time = now.replace(hour=16, minute=0, second=0, microsecond=0)

    if market_open_time <= now <= market_close_time:
    print("Market is OPEN!")
    break
    else:
    time_to_open = (market_open_time - now).total_seconds() if now < market_open_time else 86400
    print(f"Waiting for market open... Time left: {int(time_to_open)} seconds")
    time.sleep(min(1, int(time_to_open)))

    def get_open_price(symbol):
    # contract = Stock(symbol, 'SMART', 'USD' , primaryExchange='NASDAQ')
    contract = Stock(symbol, 'SMART', 'USD')
    tick = ib.reqMktData(contract)

    # Wait a few seconds to receive data
    ib.sleep(1)


    # Check if valid data is returned
    if tick.last:
    print(f"Live Price Available: {tick.last}")
    return tick.last
    else:
    print("No real-time data received. Check market data subscription.")
    return None

    def get_live_price(symbol):
    print(f"Fetching live price for {symbol} from Yahoo Finance...")
    try:
    stock = yf.Ticker(symbol)
    data = stock.history(period="1d")
    live_price = data["Close"].iloc[-1]
    print(f"Live price fetched from Yahoo: {live_price}")
    return live_price
    except Exception as e:
    print(f"Failed to get live price from Yahoo: {e}")
    return None

    def create_bracket_order(symbol, quantity, target_profit_percent, stop_loss_percent):
    global invested_amount, quantity_bought, buy_price
    print('Placing order for ',quantity,' shares of ',symbol )

    wait_for_market_open()

    open_price = get_open_price(symbol)

    print('open ',open_price)
    if open_price is None or np.isnan(open_price):
    open_price = get_live_price(symbol)

    while open_price is None:
    try:
    open_price = float(input(f"Enter open price manually for {symbol}: "))
    except ValueError:
    print("Invalid input. Please enter a numeric value.")

    buy_price = open_price # Save for later use in CSV

    contract = Stock(symbol, 'SMART', 'USD')
    ib.qualifyContracts(contract)

    take_profit_price = round(open_price * (1 + target_profit_percent / 100), 2)
    stop_loss_price = round(open_price * (1 - stop_loss_percent / 100), 2)

    parent_order_id = ib.client.getReqId()

    parent_order = MarketOrder('BUY', quantity)
    parent_order.orderId = parent_order_id
    parent_order.transmit = False

    take_profit_order = LimitOrder('SELL', quantity, take_profit_price)
    take_profit_order.orderId = parent_order_id + 1
    take_profit_order.parentId = parent_order_id
    take_profit_order.transmit = False

    stop_loss_order = StopLimitOrder('SELL', quantity, stop_loss_price, stop_loss_price)
    stop_loss_order.orderId = parent_order_id + 2
    stop_loss_order.parentId = parent_order_id
    stop_loss_order.transmit = True

    ib.placeOrder(contract, parent_order)
    ib.placeOrder(contract, take_profit_order)
    ib.placeOrder(contract, stop_loss_order)

    print(f"Placed Bracket Order for {symbol} | Entry: {open_price} | TP: {take_profit_price} | SL: {stop_loss_price}")

    quantity_bought = quantity

    def order_status_handler(trade, fill):
    global invested_amount, sell_price, trade_data, buy_price

    print("Order Execution Event Triggered!")

    if fill.execution is not None:
    trade_date = datetime.datetime.now().strftime("%d/%m/%Y")
    stock_symbol = trade.contract.symbol

    if trade.order.action == "BUY":
    if fill.execution.avgPrice is not None:
    buy_price = fill.execution.avgPrice
    elif buy_price is None:
    buy_price = get_open_price(stock_symbol)
    invested_amount = buy_price * fill.execution.shares
    print(f"BUY Executed: {buy_price} per share | Total Invested: ${invested_amount:.2f}")

    elif trade.order.action == "SELL":
    sell_price = fill.execution.avgPrice
    total_sale = sell_price * fill.execution.shares
    profit_loss = total_sale - invested_amount

    win_loss = "W" if profit_loss > 0 else "L"

    print(f"SELL Executed: {sell_price:.2f} | P/L: ${profit_loss:.2f}")

    trade_data.append([
    trade_date, stock_symbol, target_profit_percent, -stop_loss_percent,
    fill.execution.shares, round(buy_price, 2), win_loss, round(abs(profit_loss), 2)
    ])

    save_to_csv(trade_data)

    def save_to_csv(data):
    file_name = "Alpha1_trade_log.csv"
    columns = ["Date", "Stock", "TP", "SL", "No. of Shares", "Buying Price", "Win/Loss", "P/L"]

    new_data = pd.DataFrame(data, columns=columns)

    if os.path.exists(file_name):
    existing_data = pd.read_csv(file_name)
    updated_data = pd.concat([existing_data, new_data], ignore_index=True)
    else:
    updated_data = new_data

    updated_data.to_csv(file_name, index=False)
    print("Trade log updated in CSV!")
    # Attach event handler
    ib.execDetailsEvent += order_status_handler

    # --- Order Parameters ---
    symbol = "AMAT"
    quantity =141
    target_profit_percent = 1
    stop_loss_percent = 0.5

    # --- Run the Order Process ---
    create_bracket_order(symbol, quantity, target_profit_percent, stop_loss_percent)

    print("Running IBKR Event Loop...")
    ib.run()


    Above is the Python script I wrote using ib_insync that:

    Connects to IBKR around 8:30 AM

    Waits for market open (9:30 AM)

    Fetches tick.last as the open price using reqMktData

    Calculates TP/SL based on the open price

    Places a bracket order with a market buy and two child limit sell and stop-limit sell orders

    I expected the order to execute immediately at market open with the TP/SL set appropriately. However:

    tick.last was often missing or delayed at market open

    The calculated prices were stale by the time the order was placed

    This caused the bracket to execute unrealistically or miss the intended levels

    I want a solution where I can:

    Place the parent MOO order ahead of time

    Wait for actual fill execution

    Use the real executed price to submit the TP/SL orders after the buy is filled

    Continue reading...

Compartilhe esta Página