持仓总览
持仓接口用来请求这个 TWS 会话可见账户的实时持仓数据。它会返回每个持仓合约的账户、合约信息、持仓数量和平均成本。
reqPositions() -> position(account, contract, position, avgCost) -> positionEnd()
cancelPositions()reqPositions() 没有参数,也没有 reqId。它请求的是这个会话可见账户的持仓,而不是某一个单独账户。需要按账户或模型组合过滤时,可以使用 reqPositionsMulti()。
和账户更新里的持仓有什么区别
Section titled “和账户更新里的持仓有什么区别”| 接口 | 主要用途 | 回调 |
|---|---|---|
reqAccountUpdates() | 账户状态页,可能包含组合持仓行 | updatePortfolio() |
reqPositions() | 专门请求这个会话可见账户的持仓 | position()、positionEnd() |
reqPositionsMulti() | 按账户和模型组合请求持仓 | positionMulti()、positionMultiEnd() |
如果你的目标是“拿持仓列表”,优先看 reqPositions()。如果你的目标是“账户资金字段和账户状态”,看账户更新。
| 名称 | 类型 | 中文含义 | 说明 |
|---|---|---|---|
reqPositions() | 请求方法 | 请求持仓 | 没有参数,返回可见账户的持仓 |
position() | 回调方法 | 接收单条持仓 | 每个持仓合约一行 |
positionEnd() | 回调方法 | 初始持仓批次结束 | 表示这一轮初始持仓行已经发完 |
cancelPositions() | 请求方法 | 取消持仓更新 | 停止继续接收实时持仓更新 |
position() 字段
Section titled “position() 字段”def position(self, account, contract, position, avgCost): ...| 字段 | 中文含义 | 说明 |
|---|---|---|
account | 账户代码 | 真实账户号,公开输出应脱敏 |
contract | 合约对象 | 包含 conId、symbol、secType、exchange、currency 等 |
position | 持仓数量 | 可能是小数类型,期权、股票、期货都用这个字段 |
avgCost | 平均成本 | TWS 返回的平均成本,公开日志应避免直接显示真实值 |
positionEnd() 到达但 position() 行数为 0,表示这个会话没有返回持仓行。这通常是正常的空持仓结果,不应直接判定为失败。
Python 总览示例
Section titled “Python 总览示例”import threadingimport timefrom collections import Counterfrom ibapi.client import EClientfrom ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2104, 2106, 2158}
class PositionsOverviewApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.positions_end = threading.Event() self.positions = [] self.info_codes = [] self.errors_seen = []
def nextValidId(self, orderId): self.ready.set()
def position(self, account, contract, position, avgCost): # 真实账户号、持仓数量和成本都可能敏感;公开输出只统计结构。 self.positions.append({ "account": account, "conId": contract.conId, "symbol": contract.symbol, "secType": contract.secType, "currency": contract.currency, "position": position, "avgCost": avgCost, })
def positionEnd(self): self.positions_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))
app = PositionsOverviewApp()cancel_sent = False
try: app.connect("127.0.0.1", 7497, clientId=984)
thread = threading.Thread(target=app.run, daemon=True) thread.start()
connected = app.ready.wait(8)
if not connected: raise RuntimeError("等待 nextValidId 超时")
app.reqPositions()
if not app.positions_end.wait(8): raise RuntimeError("等待 positionEnd 超时")
rows_before_cancel = len(app.positions)
app.cancelPositions() cancel_sent = True time.sleep(0.5)
rows_after_cancel = len(app.positions)
finally: if app.isConnected(): app.disconnect()
sec_type_counts = Counter(row["secType"] or "EMPTY" for row in app.positions)currency_counts = Counter(row["currency"] or "EMPTY" for row in app.positions)
print(f"CONNECTED={connected}")print("REQUEST_SENT=True")print(f"POSITION_END_RECEIVED={app.positions_end.is_set()}")print(f"POSITION_ROW_COUNT={len(app.positions)}")print("SEC_TYPE_COUNTS=" + (",".join(f"{key}:{value}" for key, value in sec_type_counts.items()) if sec_type_counts else "NONE"))print("CURRENCY_COUNTS=" + (",".join(f"{key}:{value}" for key, value in currency_counts.items()) if currency_counts else "NONE"))print(f"CANCEL_SENT={cancel_sent}")print(f"ROWS_BEFORE_CANCEL={rows_before_cancel}")print(f"ROWS_AFTER_CANCEL_WAIT={rows_after_cancel}")print("INFO_CODES=" + (",".join(map(str, sorted(set(app.info_codes)))) if app.info_codes else "NONE"))print(f"NON_INFO_ERROR_COUNT={len(app.errors_seen)}")print(f"IS_CONNECTED_AFTER_DISCONNECT={app.isConnected()}")如果 TWS 模拟账户没有持仓,脱敏输出通常类似:
CONNECTED=TrueREQUEST_SENT=TruePOSITION_END_RECEIVED=TruePOSITION_ROW_COUNT=0SEC_TYPE_COUNTS=NONECURRENCY_COUNTS=NONECANCEL_SENT=TrueROWS_BEFORE_CANCEL=0ROWS_AFTER_CANCEL_WAIT=0INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False这说明接口请求成功、结束回调正常到达,只是账户没有返回持仓行。
适合用来做什么
Section titled “适合用来做什么”| 需求 | 是否适合 |
|---|---|
| 展示持仓列表 | 适合 |
| 获取合约、数量、平均成本 | 适合 |
| 按账户和模型组合过滤 | 看 reqPositionsMulti() |
| 查询账户资金字段 | 不适合,应看账户摘要或账户更新 |
| 查询订单状态 | 不适合,应看订单管理接口 |
| 查询历史成交 | 不适合,应看成交接口 |
持仓数据通常比账户摘要更敏感,因为它能暴露账户持有的证券、数量、成本和账户号。公开日志建议只输出:
- 持仓行数。
- 证券类型分布。
- 币种分布。
- 是否收到
positionEnd()。 - 非信息类错误数量。
不要公开打印真实账户号、真实持仓数量、平均成本和完整持仓列表。