请求合约详情
reqContractDetails() 用来请求合约详情。你把一个 Contract 发给 TWS,TWS 会返回它能识别到的合约详情。
app.reqContractDetails(reqId, contract)| 参数 | 中文含义 | 说明 |
|---|---|---|
reqId | 请求编号 | 由你自己分配,用来把请求和回调对应起来。 |
contract | 合约对象 | 描述你要查询的标的,例如 AAPL 股票。 |
reqId 不是订单号,也不是合约 ID。它只是本次请求的流水号。比如你同时查询 AAPL 和 MSFT,就可以用不同 reqId 区分回调属于谁。
AAPL 请求示例
Section titled “AAPL 请求示例”from ibapi.contract import Contract
contract = Contract()contract.symbol = "AAPL" # 股票代码contract.secType = "STK" # STK 表示股票contract.exchange = "SMART" # 使用 IBKR 智能路由contract.currency = "USD" # 美股通常是 USD
app.reqContractDetails(9501, contract)这段代码的意思是:
请求编号 9501:请帮我查询 AAPL 美股的合约详情。如果你只想查美国合约,官方建议写清 currency="USD"。否则同一个代码可能跨市场或跨币种返回更多匹配。
完整 Python 示例
Section titled “完整 Python 示例”下面示例会连接 TWS,查询 AAPL 股票合约详情,并等待 contractDetailsEnd() 后再统一输出。
import threadingfrom collections import Counter
from ibapi.client import EClientfrom ibapi.contract import Contractfrom ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2103, 2104, 2105, 2106, 2107, 2108, 2158}
class ContractDetailsApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.end = threading.Event() self.rows = [] self.info_codes = [] self.errors_seen = []
def nextValidId(self, orderId): # 收到 nextValidId,说明初始连接握手完成。 self.ready.set()
def contractDetails(self, reqId, contractDetails): # 每返回一条合约详情,就会触发一次这个回调。 contract = contractDetails.contract self.rows.append({ "reqId": reqId, "conId": contract.conId, "symbol": contract.symbol, "secType": contract.secType, "exchange": contract.exchange, "primaryExchange": contract.primaryExchange, "currency": contract.currency, "localSymbol": contract.localSymbol, "tradingClass": contract.tradingClass, "longName": contractDetails.longName, "minTick": contractDetails.minTick, "timeZoneId": contractDetails.timeZoneId, "marketName": contractDetails.marketName, "validExchanges": contractDetails.validExchanges, })
def contractDetailsEnd(self, reqId): # 这个 reqId 的合约详情已经返回完毕。 self.end.set()
def error(self, reqId, errorTime, errorCode, errorString, advancedOrderRejectJson=""): if errorCode in INFO_CODES: self.info_codes.append(errorCode) else: self.errors_seen.append((reqId, errorCode, errorString))
def stock_contract(symbol: str) -> Contract: contract = Contract() contract.symbol = symbol contract.secType = "STK" contract.exchange = "SMART" contract.currency = "USD" return contract
def format_counter(counter): if not counter: return "NONE" return ",".join(f"{key}:{counter[key]}" for key in sorted(counter, key=str))
def format_codes(codes): if not codes: return "NONE" return ",".join(str(code) for code in sorted(set(codes)))
app = ContractDetailsApp()
try: app.connect("127.0.0.1", 7497, clientId=996) thread = threading.Thread(target=app.run, daemon=True) thread.start()
connected = app.ready.wait(8) print(f"CONNECTED={connected}")
if not connected: raise RuntimeError("等待 nextValidId 超时")
app.reqContractDetails(9501, stock_contract("AAPL")) print("REQUEST_SENT=True")
end_received = app.end.wait(10) print(f"CONTRACT_DETAILS_END_RECEIVED={end_received}")
if not end_received: raise RuntimeError("等待 contractDetailsEnd 超时")
finally: if app.isConnected(): app.disconnect()
sec_types = Counter(row["secType"] for row in app.rows)exchanges = Counter(row["exchange"] for row in app.rows)primary_exchanges = Counter(row["primaryExchange"] or "EMPTY" for row in app.rows)
print(f"CONTRACT_DETAILS_ROW_COUNT={len(app.rows)}")print(f"SEC_TYPE_COUNTS={format_counter(sec_types)}")print(f"EXCHANGE_COUNTS={format_counter(exchanges)}")print(f"PRIMARY_EXCHANGE_COUNTS={format_counter(primary_exchanges)}")if app.rows: first = app.rows[0] print(f"FIRST_CONID={first['conId']}") print(f"FIRST_SYMBOL={first['symbol']}") print(f"FIRST_LOCAL_SYMBOL={first['localSymbol']}") print(f"FIRST_TRADING_CLASS={first['tradingClass']}") print(f"FIRST_LONG_NAME={first['longName']}") print(f"FIRST_MIN_TICK={first['minTick']}") print(f"FIRST_TIME_ZONE_ID={first['timeZoneId']}") print(f"FIRST_MARKET_NAME={first['marketName']}") print(f"FIRST_VALID_EXCHANGES={first['validExchanges']}")print(f"INFO_CODES={format_codes(app.info_codes)}")print(f"NON_INFO_ERROR_COUNT={len(app.errors_seen)}")print(f"IS_CONNECTED_AFTER_DISCONNECT={app.isConnected()}")使用 TWS 模拟账户请求后参考返回:
CONNECTED=TrueREQUEST_SENT=TrueCONTRACT_DETAILS_END_RECEIVED=TrueCONTRACT_DETAILS_ROW_COUNT=1SEC_TYPE_COUNTS=STK:1EXCHANGE_COUNTS=SMART:1PRIMARY_EXCHANGE_COUNTS=NASDAQ:1FIRST_CONID=265598FIRST_SYMBOL=AAPLFIRST_LOCAL_SYMBOL=AAPLFIRST_TRADING_CLASS=NMSFIRST_LONG_NAME=APPLE INCFIRST_MIN_TICK=0.01FIRST_TIME_ZONE_ID=US/EasternFIRST_MARKET_NAME=NMSFIRST_VALID_EXCHANGES=SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,NASDAQ,DRCTEDGE,BEX,BATS,EDGEA,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,LTSE,MEMX,IBEOS,OVERNIGHT,TPLUS0,PSX,T24XINFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False这说明请求中的 AAPL + STK + SMART + USD 已经足够定位到一只明确的美国股票。
| 检查项 | 原因 |
|---|---|
是否已经收到 nextValidId() | 没完成连接握手就发请求,容易无回调。 |
secType 是否明确 | 只写代码可能匹配到多个产品类型。 |
exchange 是否合理 | 股票常用 SMART,有些产品需要具体交易所。 |
currency 是否明确 | 同一代码可能有不同币种。 |
是否需要 primaryExchange | 同代码歧义时补充。 |
是否等待 contractDetailsEnd() | 只看第一条可能漏数据。 |
如果返回多条结果,不要随便取第一条就下单。应打印 conId、symbol、secType、exchange、primaryExchange、currency、localSymbol,确认真正要交易的合约。