跳转到内容

TWS API 基础教程

TWS API 的学习难点不在某一行代码,而在它的工作方式和普通 HTTP 接口不同。你的程序不是向一个网页地址发请求后立刻拿到 JSON,而是先连接本机 TWS 或 IB Gateway,然后通过 Socket 发送请求,再从回调函数里陆续接收结果。

如果一开始就直接写行情、下单、账户同步,很容易把连接问题、合约问题、权限问题和订单问题混在一起。更稳妥的学习顺序是:先验证连接,再理解请求编号和回调,再逐步进入合约、行情和订单。

顺序主题先学什么为什么
1连接EClient.connect()nextValidId()reqCurrentTime()先确认程序真的连到了 TWS。
2请求和回调reqIdEWrapper、异步返回TWS API 的结果通常不是函数返回值,而是回调。
3合约ContractreqContractDetails()conId行情和订单都要先说明“交易什么”。
4行情reqMktData()reqHistoricalData()whatToShow行情最容易遇到订阅权限、交易所和限频问题。
5账户和持仓reqAccountUpdates()reqPositions()reqAccountSummary()先能读懂账户状态,再考虑交易系统。
6订单OrderplaceOrder()orderStatus()openOrder()下单前必须理解订单编号、状态回报和风险提示。
7错误处理error()、1100/1101/1102、100/200/354/10189实盘系统必须能解释错误,而不是只打印一行日志。

最小连接脚本只做一件事:连接 TWS,等到 nextValidId(),再请求服务器时间。

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
class App(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
def nextValidId(self, orderId: int):
# 收到这个回调,说明 API 握手已经完成。
print("nextValidId:", orderId)
self.reqCurrentTime()
def currentTime(self, time: int):
# 这里返回的是 IBKR 服务器时间戳。
print("server time:", time)
self.disconnect()
def error(self, reqId, errorCode, errorString, advancedOrderRejectJson=""):
print("error:", reqId, errorCode, errorString)
app = App()
app.connect("127.0.0.1", 7497, clientId=1)
app.run()

常见端口:

场景端口
TWS 模拟账户7497
TWS 真实账户7496
IB Gateway 模拟账户4002
IB Gateway 真实账户4001

如果这一步不通,先不要调行情或订单。先检查 TWS 是否已登录、是否勾选启用 Socket 客户端、端口是否正确、是否只允许本机连接。

很多 TWS API 请求都需要一个编号,例如 reqMktData(1001, contract, ...) 里的 1001。这个编号不是合约编号,也不是订单编号,而是你给这次请求起的名字。

当 TWS 后续通过回调返回行情、历史数据、合约详情或错误时,会带回同一个 reqId,程序就能知道这条结果属于哪次请求。

self.reqMktData(1001, contract, "", False, False, [])
def tickPrice(self, reqId, tickType, price, attrib):
print("行情请求编号:", reqId, "字段:", tickType, "价格:", price)

订单使用的是 orderId,它来自 nextValidId(),不要和行情、合约查询的 reqId 混用。

第三步:先确认合约,再请求行情或下单

Section titled “第三步:先确认合约,再请求行情或下单”

TWS API 不建议只靠一个股票代码猜合约。比如 AAPL 可能在多个交易所、多个货币、多个产品类型里出现。稳定做法是先用 reqContractDetails() 确认合约详情。

from ibapi.contract import Contract
contract = Contract()
contract.symbol = "AAPL"
contract.secType = "STK"
contract.exchange = "SMART"
contract.currency = "USD"
contract.primaryExchange = "NASDAQ"
self.reqContractDetails(2001, contract)

收到合约详情后,要重点看:

字段作用
conIdIBKR 内部合约编号,最稳定。
symbol交易标的代码。
secType产品类型,例如股票、期权、期货、外汇。
exchange路由或交易所,例如 SMART
primaryExchange股票常用,用来消除同名代码歧义。
currency交易货币。
tradingClass交易类别,期权、期货、事件合约等经常需要。
minTick最小价格变动单位,下单价格必须符合它。
marketRuleIds市场规则编号,可继续查询价格增量规则。

历史行情通常比实时行情更适合新手先学,因为它一次请求返回一批 K 线,排查起来更清楚。

实时行情会受到市场数据订阅、交易所权限、产品类型和限频影响。模拟账户不一定拥有所有实时行情权限,代码连通不代表一定能收到价格。

行情类型常用接口适合场景
历史 K 线reqHistoricalData()回测、图表、指标计算、入门验证。
一档实时行情reqMktData()最新 bid/ask/last、成交量、generic tick。
5 秒实时 K 线reqRealTimeBars()实时滚动 bar,但受权限和限制影响。
逐笔数据reqTickByTickData()成交逐笔、买卖盘逐笔,需要对应权限。
二档行情reqMktDepth()订单簿深度,需要市场深度权限。

第五步:订单先用 WhatIf 和模拟账户

Section titled “第五步:订单先用 WhatIf 和模拟账户”

订单代码至少要同时处理三类回调:

回调用途
openOrder()TWS 接收并打开订单时返回订单对象和合约。
orderStatus()返回提交、预提交、成交、取消等状态变化。
error()返回订单拒绝、字段错误、权限问题和风险提示。

新手建议先使用 whatIf=True 预览保证金和订单校验,再在模拟账户提交小数量、远离市场价的限价单,最后确认能收到撤单状态。

误区正确理解
调用函数后马上应该有返回值TWS API 是异步回调模型,结果从 EWrapper 回来。
有股票代码就能下单先确认 Contract,尤其是交易所、货币、产品类型和最小价格增量。
模拟账户能代表真实账户所有权限模拟账户可用于开发流程,但行情、交易权限和风控可能不同。
error() 都是失败2104、2106 等连接状态消息常见且不一定是失败;要按错误码分类。
只要连接成功就能拿到实时行情实时行情还需要市场数据订阅和交易所权限。

先看“连接”与“合约”,再看“历史行情”和“实时行情”,最后进入“账户与投资组合数据”和“订单”。这样每一层出问题时,都能判断是连接、合约、权限、参数还是业务逻辑的问题。