接收按模型持仓
reqPositionsMulti() 的返回结果通过 positionMulti() 接收。每一行代表一个合约持仓。
def positionMulti(self, reqId, account, modelCode, contract, pos, avgCost): ...
def positionMultiEnd(self, reqId): ...普通 position() 没有请求编号,也没有模型代码;positionMulti() 会额外返回 reqId 和 modelCode,更适合复杂程序做数据归属判断。
positionMulti() 字段
Section titled “positionMulti() 字段”| 字段 | 中文含义 | 说明 |
|---|---|---|
reqId | 请求编号 | 对应 reqPositionsMulti(reqId, ...) 的请求编号 |
account | 账户代码 | 真实账户号,公开输出时需要脱敏 |
modelCode | 模型代码 | 没有模型时通常为空字符串 |
contract | 合约对象 | 包含合约 ID、代码、证券类型、交易所、币种等 |
pos | 持仓数量 | 可能是小数类型,建议转字符串保存 |
avgCost | 平均成本 | 敏感字段,公开日志中不要直接展示 |
常用 contract 字段:
| 字段 | 中文含义 | 示例 |
|---|---|---|
contract.conId | 合约 ID | 265598 |
contract.symbol | 标的代码 | AAPL |
contract.secType | 证券类型 | STK、OPT、FUT |
contract.exchange | 交易所或路由 | SMART |
contract.currency | 币种 | USD |
positionMultiEnd(reqId) 的含义
Section titled “positionMultiEnd(reqId) 的含义”positionMultiEnd(reqId) 表示这个 reqId 的初始持仓批次已经返回完成。它可以用来判断页面是否结束加载,或者触发汇总计算。
如果没有任何 positionMulti() 行,但收到了 positionMultiEnd(reqId),通常表示账户和模型组合下没有持仓。
Python 接收示例
Section titled “Python 接收示例”import threadingfrom collections import Counter
from ibapi.client import EClientfrom ibapi.wrapper import EWrapper
class ReceivePositionsMultiApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.accounts_ready = threading.Event() self.positions_end = threading.Event() self.accounts = [] self.positions = []
def nextValidId(self, orderId): self.ready.set()
def managedAccounts(self, accountsList): self.accounts = [item for item in accountsList.split(",") if item] self.accounts_ready.set()
def positionMulti(self, reqId, account, modelCode, contract, pos, avgCost): self.positions.append({ "reqId": reqId, "account": "ACCOUNT_1", # 示例输出脱敏,不直接打印真实账户号 "modelCode": modelCode or "EMPTY", "conId": contract.conId, "symbol": contract.symbol, "secType": contract.secType, "currency": contract.currency, "position": str(pos), })
def positionMultiEnd(self, reqId): self.positions_end.set()
app = ReceivePositionsMultiApp()req_id = 9201
try: app.connect("127.0.0.1", 7497, clientId=120) thread = threading.Thread(target=app.run, daemon=True) thread.start()
if not app.ready.wait(8): raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts() if not app.accounts_ready.wait(5): raise RuntimeError("等待账户列表超时")
app.reqPositionsMulti(req_id, app.accounts[0], "") app.positions_end.wait(8)
finally: if app.isConnected(): app.cancelPositionsMulti(req_id) app.disconnect()
sec_type_counts = Counter(row["secType"] or "EMPTY" for row in app.positions)model_code_counts = Counter(row["modelCode"] or "EMPTY" for row in app.positions)
print(f"POSITION_MULTI_END_RECEIVED={app.positions_end.is_set()}")print(f"POSITION_MULTI_ROW_COUNT={len(app.positions)}")print("SEC_TYPE_COUNTS=" + (",".join(f"{k}:{v}" for k, v in sec_type_counts.items()) if sec_type_counts else "NONE"))print("MODEL_CODE_COUNTS=" + (",".join(f"{k}:{v}" for k, v in model_code_counts.items()) if model_code_counts else "NONE"))print(f"IS_CONNECTED_AFTER_DISCONNECT={app.isConnected()}")POSITION_MULTI_END_RECEIVED=TruePOSITION_MULTI_ROW_COUNT=0SEC_TYPE_COUNTS=NONEMODEL_CODE_COUNTS=NONEIS_CONNECTED_AFTER_DISCONNECT=False这个环境没有持仓行,所以证券类型和模型代码统计都是 NONE。
真实持仓建议保存结构
Section titled “真实持仓建议保存结构”有持仓时,建议保存结构化数据:
row = { "reqId": reqId, "account": account, "modelCode": modelCode, "conId": contract.conId, "symbol": contract.symbol, "secType": contract.secType, "currency": contract.currency, "position": str(pos), "avgCost": avgCost,}公开日志中可以脱敏为:
safe_row = { "reqId": reqId, "account": "ACCOUNT_1", "modelCode": modelCode or "EMPTY", "conId": contract.conId, "secType": contract.secType, "currency": contract.currency,}为什么 pos 要转字符串?
Section titled “为什么 pos 要转字符串?”Python API 中持仓数量可能是 Decimal。写入 JSON、日志或数据库前转成字符串,可以避免精度和序列化问题。
modelCode 为空表示错误吗?
Section titled “modelCode 为空表示错误吗?”不是。普通账户没有模型组合时,模型代码为空很常见。
能不能直接把 avgCost 打到日志?
Section titled “能不能直接把 avgCost 打到日志?”不建议。平均成本会暴露交易成本和仓位细节,公开日志和页面返回值都应该谨慎处理。