跳转到内容

接收按模型持仓

reqPositionsMulti() 的返回结果通过 positionMulti() 接收。每一行代表一个合约持仓。

def positionMulti(self, reqId, account, modelCode, contract, pos, avgCost):
...
def positionMultiEnd(self, reqId):
...

普通 position() 没有请求编号,也没有模型代码;positionMulti() 会额外返回 reqIdmodelCode,更适合复杂程序做数据归属判断。

字段中文含义说明
reqId请求编号对应 reqPositionsMulti(reqId, ...) 的请求编号
account账户代码真实账户号,公开输出时需要脱敏
modelCode模型代码没有模型时通常为空字符串
contract合约对象包含合约 ID、代码、证券类型、交易所、币种等
pos持仓数量可能是小数类型,建议转字符串保存
avgCost平均成本敏感字段,公开日志中不要直接展示

常用 contract 字段:

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

positionMultiEnd(reqId) 表示这个 reqId 的初始持仓批次已经返回完成。它可以用来判断页面是否结束加载,或者触发汇总计算。

如果没有任何 positionMulti() 行,但收到了 positionMultiEnd(reqId),通常表示账户和模型组合下没有持仓。

import threading
from collections import Counter
from ibapi.client import EClient
from 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=True
POSITION_MULTI_ROW_COUNT=0
SEC_TYPE_COUNTS=NONE
MODEL_CODE_COUNTS=NONE
IS_CONNECTED_AFTER_DISCONNECT=False

这个环境没有持仓行,所以证券类型和模型代码统计都是 NONE

有持仓时,建议保存结构化数据:

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,
}

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

不是。普通账户没有模型组合时,模型代码为空很常见。

能不能直接把 avgCost 打到日志?

Section titled “能不能直接把 avgCost 打到日志?”

不建议。平均成本会暴露交易成本和仓位细节,公开日志和页面返回值都应该谨慎处理。