跳转到内容

接收持仓

reqPositions() 的持仓行通过 position() 回调返回。每个持仓合约一行。初始批次结束时,TWS 调用 positionEnd()

def position(self, account, contract, position, avgCost):
...
def positionEnd(self):
...
字段中文含义说明
account账户代码真实账户号,公开输出必须脱敏
contract合约对象包含合约 ID、代码、证券类型、交易所、币种等
position持仓数量可能是小数类型,不要只按整数处理
avgCost平均成本敏感字段,公开日志不要直接输出

常用 contract 字段:

字段中文含义示例
contract.conId合约 ID265598
contract.symbol标的代码AAPL
contract.secType证券类型STKOPTFUT
contract.exchange交易所或路由SMART
contract.currency币种USD

positionEnd() 表示初始持仓批次已经返回完。它不是取消订阅,也不代表之后一定不会有更新。若只需要快照,收到它之后再调用 cancelPositions()

如果 positionEnd() 到达但没有任何 position() 行,通常说明账户没有持仓。

import threading
from collections import Counter
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2104, 2106, 2158}
class ReceivePositionsApp(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,
})
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 = ReceivePositionsApp()
try:
app.connect("127.0.0.1", 7497, clientId=986)
thread = threading.Thread(target=app.run, daemon=True)
thread.start()
if not app.ready.wait(8):
raise RuntimeError("等待 nextValidId 超时")
app.reqPositions()
if not app.positions_end.wait(8):
raise RuntimeError("等待 positionEnd 超时")
finally:
if app.isConnected():
app.cancelPositions()
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"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("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)}")
POSITION_END_RECEIVED=True
POSITION_ROW_COUNT=0
SEC_TYPE_COUNTS=NONE
CURRENCY_COUNTS=NONE
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0

账户没有持仓行,所以证券类型和币种分布都是 NONE

如果你的账户有持仓,建议用结构化对象保存,不要直接拼字符串:

row = {
"account": account,
"conId": contract.conId,
"symbol": contract.symbol,
"secType": contract.secType,
"currency": contract.currency,
"position": str(position),
"avgCost": avgCost,
}

公开日志中可以只保留:

safe_row = {
"account": "ACCOUNT_1",
"conId": contract.conId,
"secType": contract.secType,
"currency": contract.currency,
}

Python API 里持仓数量可能是 Decimal 类型。写 JSON、日志或数据库前,转成字符串能避免精度和序列化问题。

不建议。平均成本能暴露交易成本和仓位信息,公开示例里应隐藏或只统计。

可能。positionEnd() 只表示初始批次结束。订阅还存在时,之后的持仓变化可能继续通过 position() 返回。

IBKR Campus: TWS API Documentation