Send a Limit Order
Place a BUY LMT on SPY far below market, watch it acknowledge, then cancel it. Connect, take an id, place, read status, cancel, disconnect.
What this shows
- Taking the order id off
next_valid_id, whichconnect()fires before it returns.c.next_order_id()reserves one directly if you would rather ask. - An
Orderwithorder_type = "LMT"andlmt_priceset.outside_rth = Trueis what lets it be acknowledged outside regular hours. order_statuscallbacks.PreSubmittedthenSubmittedwhile it rests,Cancelledonce the cancel lands.cancel_order(order_id, order_cancel). The second argument is what the withdrawal states about itself: anOrderCancel(who is withdrawing it, whether a person entered it, a manual time), or a time alone;""states nothing. The operator and who entered it travel on the cancel. A time does not, and the caller is told so onerror; a time a gateway cannot read is refused under 10301 and the order keeps working.
What comes back
order_status on every change and again on each fill. filled and remaining
are quantities, avg_fill_price is the average of what has filled so far. An
order that rests will sit at Submitted until you cancel it.
The perm_id on that callback is the number the order goes to the venue
under, as a gateway states it, which for this order is its own order id. It is
stated from the moment the order is sent and stays the same for the life of the
order, including across a modify.
Limits
Order ids count from one past the highest id the account is working an order under, which the venue names at every connect, from every session. So one taken here does not collide with an order placed elsewhere.
tif is checked against what the venue carries: DAY, GTC, IOC, FOK,
OPG, GTD, GTX, DTC, AUC, NMIN, spelled exactly. An unrecognised value is
refused here rather than sent as DAY and quietly expiring at the close.
Paper account only. The price is set far below market so it will not fill.
Run it
IB_USERNAME=... IB_PASSWORD=... python examples/hello_limit_order.py
Source
"""Recipe: place a non-marketable BUY LMT on SPY, watch Submitted, then cancel.
Usage:
IB_USERNAME=... IB_PASSWORD=... python examples/hello_limit_order.py
"""
import os
import threading
from ib_dx import EClient, EWrapper, Contract, Order
class OrderWrapper(EWrapper):
def __init__(self):
self.next_id = None
self.submitted = threading.Event()
self.cancelled = threading.Event()
def next_valid_id(self, order_id):
self.next_id = order_id
def order_status(self, order_id, status, filled, remaining,
avg_fill_price, perm_id, parent_id,
last_fill_price, client_id, why_held, mkt_cap_price):
print(f"[status] oid={order_id} status={status}")
if status == "Submitted":
self.submitted.set()
if status == "Cancelled":
self.cancelled.set()
def error(self, req_id, error_time, code, msg, advanced=""):
if code not in (2104, 2106, 2158):
print(f"[error] {code}: {msg}")
w = OrderWrapper()
c = EClient(w)
c.connect(
username=os.environ["IB_USERNAME"],
password=os.environ["IB_PASSWORD"],
paper=True,
)
threading.Thread(target=c.run, daemon=True).start()
order_id = w.next_id
spy = Contract()
spy.con_id = 756733
spy.symbol = "SPY"
spy.sec_type = "STK"
spy.exchange = "SMART"
spy.currency = "USD"
order = Order()
order.action = "BUY"
order.total_quantity = 1
order.order_type = "LMT"
order.lmt_price = 1.00
order.tif = "DAY"
order.outside_rth = True
print(f"placing BUY 1 SPY LMT 1.00 (oid={order_id})")
c.place_order(order_id, spy, order)
w.submitted.wait(timeout=15)
print(f"cancelling oid={order_id}")
c.cancel_order(order_id, "")
w.cancelled.wait(timeout=15)
c.disconnect()