TWS API 基础教程
TWS API 的学习难点不在某一行代码,而在它的工作方式和普通 HTTP 接口不同。你的程序不是向一个网页地址发请求后立刻拿到 JSON,而是先连接本机 TWS 或 IB Gateway,然后通过 Socket 发送请求,再从回调函数里陆续接收结果。
如果一开始就直接写行情、下单、账户同步,很容易把连接问题、合约问题、权限问题和订单问题混在一起。更稳妥的学习顺序是:先验证连接,再理解请求编号和回调,再逐步进入合约、行情和订单。
最小学习路线
Section titled “最小学习路线”| 顺序 | 主题 | 先学什么 | 为什么 |
|---|---|---|---|
| 1 | 连接 | EClient.connect()、nextValidId()、reqCurrentTime() | 先确认程序真的连到了 TWS。 |
| 2 | 请求和回调 | reqId、EWrapper、异步返回 | TWS API 的结果通常不是函数返回值,而是回调。 |
| 3 | 合约 | Contract、reqContractDetails()、conId | 行情和订单都要先说明“交易什么”。 |
| 4 | 行情 | reqMktData()、reqHistoricalData()、whatToShow | 行情最容易遇到订阅权限、交易所和限频问题。 |
| 5 | 账户和持仓 | reqAccountUpdates()、reqPositions()、reqAccountSummary() | 先能读懂账户状态,再考虑交易系统。 |
| 6 | 订单 | Order、placeOrder()、orderStatus()、openOrder() | 下单前必须理解订单编号、状态回报和风险提示。 |
| 7 | 错误处理 | error()、1100/1101/1102、100/200/354/10189 | 实盘系统必须能解释错误,而不是只打印一行日志。 |
第一步:确认连接
Section titled “第一步:确认连接”最小连接脚本只做一件事:连接 TWS,等到 nextValidId(),再请求服务器时间。
from ibapi.client import EClientfrom 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 客户端、端口是否正确、是否只允许本机连接。
第二步:理解请求编号
Section titled “第二步:理解请求编号”很多 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)收到合约详情后,要重点看:
| 字段 | 作用 |
|---|---|
conId | IBKR 内部合约编号,最稳定。 |
symbol | 交易标的代码。 |
secType | 产品类型,例如股票、期权、期货、外汇。 |
exchange | 路由或交易所,例如 SMART。 |
primaryExchange | 股票常用,用来消除同名代码歧义。 |
currency | 交易货币。 |
tradingClass | 交易类别,期权、期货、事件合约等经常需要。 |
minTick | 最小价格变动单位,下单价格必须符合它。 |
marketRuleIds | 市场规则编号,可继续查询价格增量规则。 |
第四步:行情先分历史和实时
Section titled “第四步:行情先分历史和实时”历史行情通常比实时行情更适合新手先学,因为它一次请求返回一批 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 等连接状态消息常见且不一定是失败;要按错误码分类。 |
| 只要连接成功就能拿到实时行情 | 实时行情还需要市场数据订阅和交易所权限。 |
建议阅读顺序
Section titled “建议阅读顺序”先看“连接”与“合约”,再看“历史行情”和“实时行情”,最后进入“账户与投资组合数据”和“订单”。这样每一层出问题时,都能判断是连接、合约、权限、参数还是业务逻辑的问题。