API Socket 连接断开
TWS API 程序不能假设连接永远在线。TWS 重启、每日维护、每周重新认证、网络变化、电脑休眠、端口修改、clientId 冲突和限频违规,都可能让 API Socket 断开。
断线处理不是只调用一次 connect()。更完整的流程是:
识别断线 -> 停止发送新请求 -> 重新连接 -> 等 nextValidId -> 恢复订阅和状态如果只重连 socket,却没有恢复行情订阅、账户订阅、订单状态和程序里的 reqId 映射,程序表面在线,实际业务状态仍然可能是空的或过期的。
断线有哪些信号
Section titled “断线有哪些信号”| 信号 | 含义 |
|---|---|
connectionClosed() | Python API 检测到 socket 关闭时触发的回调 |
isConnected() 返回 False | 这个客户端对象不再认为自己在线 |
error() 收到连接相关 code | TWS API 把很多连接通知也放在 error() 回调里 |
请求时报 504 Not connected | 程序已经不在线,却继续发送请求 |
新连接时报 502 | 客户端无法连接到 TWS / IB Gateway 端口 |
Python API 源码里,底层连接关闭会触发 wrapper.connectionClosed();EClient.isConnected() 会检查 socket 状态。因此程序里应该同时处理回调和主动状态检查。
常见连接消息码
Section titled “常见连接消息码”| Code | 常见含义 | 处理方式 |
|---|---|---|
1100 | TWS 与 IBKR 后端连接丢失 | 暂停请求,等待恢复或准备重连 |
1101 | TWS 与 IBKR 后端连接恢复,但数据可能丢失 | 重新订阅行情、账户、扫描器等流式请求 |
1102 | TWS 与 IBKR 后端连接恢复,数据仍被保留 | 仍建议检查关键订阅状态 |
1300 | TWS Socket 端口被重置 | 读取新端口后重新连接 |
502 | API 客户端无法连接到 TWS | 检查 TWS 是否运行、API 是否启用、端口和防火墙 |
504 | API 客户端未连接 | 停止发送业务请求,进入重连流程 |
326 | clientId 已被使用 | 换独立 clientId 或关闭冲突客户端 |
100 | 消息速率超过限制 | 降低请求速率,避免继续触发断开或拒绝 |
1100、1101、1102 更偏 TWS 到 IBKR 后端的连接状态;connectionClosed()、502、504 更偏你的程序到 TWS 的本地 socket 状态。两类问题需要分开看。
Python 断线处理骨架
Section titled “Python 断线处理骨架”下面示例展示结构:记录断线信号、等待 nextValidId()、并把恢复订阅留成单独函数。实际项目中,restore_subscriptions() 里应该重新请求行情、账户、持仓、扫描器或订单状态。
import threadingimport time
from ibapi.client import EClientfrom ibapi.wrapper import EWrapper
class App(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.closed = threading.Event() self.next_order_id = None
def nextValidId(self, orderId: int): # 重连后也会收到新的 nextValidId。 self.next_order_id = orderId self.ready.set()
def connectionClosed(self): # socket 关闭后,停止发送新请求,并交给外层重连循环处理。 print("API socket 已关闭") self.ready.clear() self.closed.set()
def error(self, reqId, errorCode, errorString, advancedOrderRejectJson=""): print(f"API message: reqId={reqId}, code={errorCode}, message={errorString}")
if errorCode in (1100, 1101, 1102, 1300, 502, 504, 326, 100): # 这里不要直接重连;先记录状态,让主循环统一处理。 self.closed.set()
def restore_subscriptions(self): # 按你的业务重新订阅,例如: # self.reqAccountSummary(...) # self.reqPositions() # self.reqMktData(...) pass
def connect_once(host: str, port: int, client_id: int) -> App: app = App() app.connect(host, port, clientId=client_id)
thread = threading.Thread(target=app.run, daemon=True) thread.start()
if not app.ready.wait(10): app.disconnect() raise TimeoutError("连接超时:没有收到 nextValidId")
app.restore_subscriptions() return app
HOST = "127.0.0.1"PORT = 7497CLIENT_ID = 401
while True: try: app = connect_once(HOST, PORT, CLIENT_ID)
while app.isConnected() and not app.closed.wait(1): # 主循环可以在这里做心跳、队列消费或业务调度。 pass
app.disconnect()
except Exception as exc: print(f"连接不可用,准备稍后重试: {exc}")
time.sleep(5)这只是连接层骨架,不代表完整交易系统。真实系统还要处理请求去重、订单恢复、持仓同步、限频、日志和异常告警。
重连后必须恢复什么
Section titled “重连后必须恢复什么”断线恢复后,至少检查这些状态:
| 状态 | 为什么要恢复 |
|---|---|
| 行情订阅 | 流式行情断开后通常要重新请求 |
| 账户和持仓 | 程序内缓存可能已经过期 |
| 订单状态 | 断线期间订单可能成交、取消或被系统拒绝 |
nextValidId | 下单前必须使用有效的订单 ID |
reqId 映射 | 旧请求和新请求不要混在一起 |
| 限频计数 | 重连后不要一次性补发过多请求 |
最容易漏的是订单状态。程序断线期间,TWS 和 IBKR 后端仍可能继续处理已经提交的订单。重连后要主动请求开放订单、完成订单或执行明细,不能只相信断线前的内存状态。
不同断线场景怎么处理
Section titled “不同断线场景怎么处理”| 场景 | 建议处理 |
|---|---|
| TWS 每日自动重启 | 程序进入等待状态,TWS 恢复后重新连接 |
| 每周重新认证 | 等人工完成认证后再重连 |
| Wi-Fi 或服务器网络抖动 | 使用退避重试,不要高频重连 |
clientId 冲突 | 停止冲突程序或换编号 |
| 端口被改成新值 | 更新配置,按新端口连接 |
| 限频导致连接不稳定 | 降低请求速率,先恢复连接再恢复业务 |
不要让多个线程同时重连同一个 App 对象。更稳的方式是:断开旧对象,创建新对象,等待 nextValidId(),再恢复业务状态。
新手排查顺序
Section titled “新手排查顺序”- 先确认 TWS / IB Gateway 还在运行并已登录。
- 用最小
reqCurrentTime()脚本验证端口是否还通。 - 看
error()输出是否有1100、1101、1102、1300、502、504。 - 确认没有另一个程序占用相同
clientId。 - 重连成功后,先恢复账户和订单状态,再恢复批量行情。
如果最小连接脚本都失败,不要先改业务代码。连接层没恢复之前,合约、行情和订单接口都会跟着失败。