跳转到内容

请求合约详情

reqContractDetails() 用来请求合约详情。你把一个 Contract 发给 TWS,TWS 会返回它能识别到的合约详情。

app.reqContractDetails(reqId, contract)
参数中文含义说明
reqId请求编号由你自己分配,用来把请求和回调对应起来。
contract合约对象描述你要查询的标的,例如 AAPL 股票。

reqId 不是订单号,也不是合约 ID。它只是本次请求的流水号。比如你同时查询 AAPL 和 MSFT,就可以用不同 reqId 区分回调属于谁。

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"。否则同一个代码可能跨市场或跨币种返回更多匹配。

下面示例会连接 TWS,查询 AAPL 股票合约详情,并等待 contractDetailsEnd() 后再统一输出。

import threading
from collections import Counter
from ibapi.client import EClient
from ibapi.contract import Contract
from 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=True
REQUEST_SENT=True
CONTRACT_DETAILS_END_RECEIVED=True
CONTRACT_DETAILS_ROW_COUNT=1
SEC_TYPE_COUNTS=STK:1
EXCHANGE_COUNTS=SMART:1
PRIMARY_EXCHANGE_COUNTS=NASDAQ:1
FIRST_CONID=265598
FIRST_SYMBOL=AAPL
FIRST_LOCAL_SYMBOL=AAPL
FIRST_TRADING_CLASS=NMS
FIRST_LONG_NAME=APPLE INC
FIRST_MIN_TICK=0.01
FIRST_TIME_ZONE_ID=US/Eastern
FIRST_MARKET_NAME=NMS
FIRST_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,T24X
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

这说明请求中的 AAPL + STK + SMART + USD 已经足够定位到一只明确的美国股票。

检查项原因
是否已经收到 nextValidId()没完成连接握手就发请求,容易无回调。
secType 是否明确只写代码可能匹配到多个产品类型。
exchange 是否合理股票常用 SMART,有些产品需要具体交易所。
currency 是否明确同一代码可能有不同币种。
是否需要 primaryExchange同代码歧义时补充。
是否等待 contractDetailsEnd()只看第一条可能漏数据。

如果返回多条结果,不要随便取第一条就下单。应打印 conIdsymbolsecTypeexchangeprimaryExchangecurrencylocalSymbol,确认真正要交易的合约。