Request Contract Details
Resolve a symbol: send a partial description, collect every contract the venue
lists under it, stop when the end marker lands. This is how you turn "AAPL"
into a con_id you can quote and trade on.
What this shows
- Building a partial
Contractfromsymbol,sec_type,exchangeandcurrency. req_contract_details(req_id, contract)and thecontract_detailscallback.- Driving the callback loop on a daemon thread with
EClient.run. - Reading
con_id,primary_exchangeandtrading_classoff the returnedContractDetails.
What comes back
One contract_details callback per matching contract, then one
contract_details_end for the request id. A description that matches several
listings answers with all of them, so more than one row means an ambiguous
description rather than an error.
The shorter form
Two blocking methods do the same work with no wrapper and no callbacks:
rows = c.contract_details(aapl) # list, every match
one = c.qualify_contract(aapl) # a single Contract
qualify_contract refuses an ambiguous description rather than handing back
whichever the venue listed first, which would be a different contract from the
one you asked about. c.qualify_contracts([...]) takes several at once.
Nothing that answers these calls reaches your wrapper, even what arrives after
the call has returned.
These send, wait and return. They take their answer off the queue by request id
and release the interpreter lock while waiting, so they need no run() thread
of their own. Do not run both styles on one client: run() drains every queue
rather than only its own, so a dispatch loop beside a blocking call competes
with it for the answer. Pick the callbacks or pick these.
Lookups by contract id
A lookup by con_id includes the requested exchange. Leaving the exchange
empty asks for the preferred definition. The same rule applies when a market
data request needs to name a contract given only by id.
Contract details reuse exchange rule scopes already returned during the session. A first lookup can still need a separate request for each missing exchange; later lookups reuse those answers. CORPACT is excluded because it does not name a trading venue.
Run it
IB_USERNAME=... IB_PASSWORD=... python examples/hello_contract_details.py
Source
"""Recipe: req_contract_details for AAPL on paper, print con_id and primary exchange.
Usage:
IB_USERNAME=... IB_PASSWORD=... python examples/hello_contract_details.py
"""
import os
import threading
from ib_dx import EClient, EWrapper, Contract
class DetailsWrapper(EWrapper):
def __init__(self):
self.rows = []
self.done = threading.Event()
def contract_details(self, req_id, details):
self.rows.append(details)
def contract_details_end(self, req_id):
self.done.set()
def error(self, req_id, error_time, code, msg, advanced=""):
if code not in (2104, 2106, 2158):
print(f"[error] {code}: {msg}")
w = DetailsWrapper()
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()
aapl = Contract()
aapl.symbol = "AAPL"
aapl.sec_type = "STK"
aapl.exchange = "SMART"
aapl.currency = "USD"
c.req_contract_details(1, aapl)
if not w.done.wait(timeout=15):
raise RuntimeError("contract_details_end not received")
print(f"matches: {len(w.rows)}")
for d in w.rows:
print(f" con_id={d.contract.con_id:>8} "
f"primary={d.contract.primary_exchange:<10} "
f"trading_class={d.contract.trading_class}")
c.disconnect()