按模型获取账户更新总览
reqAccountUpdatesMulti() 用来按账户和模型组合请求账户值更新。它和前面的 reqAccountUpdates() 都属于账户数据接口,但 multi 版本多了 reqId 和 modelCode,更适合多账户、顾问账户、模型组合或需要同时区分多条请求来源的程序。
典型调用链路如下:
reqAccountUpdatesMulti(reqId, account, modelCode, ledgerAndNLV) -> accountUpdateMulti(reqId, account, modelCode, key, value, currency) -> accountUpdateMultiEnd(reqId)
cancelAccountUpdatesMulti(reqId)如果你的程序只是连接一个普通模拟账户,并且只想看账户资金、保证金和购买力,前面的 reqAccountUpdates() 更容易上手。等你需要用 reqId 管理多条账户数据请求,或要按模型组合拆分数据时,再使用本组接口。
和普通账户更新的区别
Section titled “和普通账户更新的区别”| 对比项 | reqAccountUpdates() | reqAccountUpdatesMulti() |
|---|---|---|
| 请求标识 | 没有独立 reqId | 需要传入 reqId |
| 账户参数 | acctCode | account |
| 模型组合参数 | 不支持 | 支持 modelCode |
| 返回账户值回调 | updateAccountValue() | accountUpdateMulti() |
| 返回结束回调 | accountDownloadEnd() | accountUpdateMultiEnd() |
| 取消方式 | reqAccountUpdates(False, account) | cancelAccountUpdatesMulti(reqId) |
multi 版本的核心优势是“可识别”。每条账户更新数据都会带回 reqId、account 和 modelCode,程序可以更清楚地判断这行数据属于哪一次请求、哪个账户、哪个模型组合。
Python API 中常用的三个方法或回调如下:
| 名称 | 类型 | 中文含义 | 说明 |
|---|---|---|---|
reqAccountUpdatesMulti() | 请求方法 | 请求按账户和模型返回账户更新 | 发起订阅或初始批次请求 |
accountUpdateMulti() | 回调方法 | 接收按模型返回的账户字段 | 每个字段一行,包含 key、value 和 currency |
accountUpdateMultiEnd() | 回调方法 | 接收本次初始批次结束信号 | 参数只有 reqId |
cancelAccountUpdatesMulti() | 请求方法 | 取消按模型账户更新 | 通过 reqId 取消对应请求 |
app.reqAccountUpdatesMulti(reqId, account, modelCode, ledgerAndNLV)| 参数 | 中文含义 | 常用取值 | 说明 |
|---|---|---|---|
reqId | 请求编号 | 例如 9101 | 由你的程序自己分配。回调里会带回同一个编号。 |
account | 账户代码 | 例如 DU1234567 | 这个 TWS 会话可见的账户号。公开日志应脱敏。 |
modelCode | 模型组合代码 | 空字符串或模型代码 | 没有模型组合时可以传空字符串;有模型组合时填对应代码。 |
ledgerAndNLV | 是否包含 Ledger 和净清算值相关字段 | True / False | True 时会请求更多账户账本和净值相关字段。 |
modelCode 不是证券代码,也不是策略名称。它指的是 IBKR 账户体系里的模型组合代码。如果账户没有模型组合,传空字符串可以请求账户层面的 multi 更新。
返回字段结构
Section titled “返回字段结构”def accountUpdateMulti(self, reqId, account, modelCode, key, value, currency): ...| 字段 | 中文含义 | 说明 |
|---|---|---|
reqId | 请求编号 | 对应你发起请求时传入的编号 |
account | 账户代码 | 真实账户号,公开输出时要脱敏 |
modelCode | 模型组合代码 | 没有模型组合时通常为空字符串 |
key | 账户字段名 | 例如 $LEDGER-CashBalance、NetLiquidation 等 |
value | 字段值 | 字符串形式返回,可能是数字,也可能是文本状态 |
currency | 币种 | 例如 USD、BASE,也可能为空 |
和 updateAccountValue() 类似,accountUpdateMulti() 的 value 也是字符串。写程序时不要直接假设所有字段都能转成浮点数,应该按 key 或字段用途分别处理。
Python 总览示例
Section titled “Python 总览示例”下面示例会先获取可见账户,再用空 modelCode 请求 multi 账户更新,等待 accountUpdateMultiEnd(),最后用 cancelAccountUpdatesMulti(reqId) 取消请求。示例输出只保留字段数量、币种、样例 key 和脱敏账户别名。
import threadingimport timefrom collections import Counterfrom ibapi.client import EClientfrom ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2104, 2106, 2158}REQ_ID = 9101
class AccountUpdatesMultiOverviewApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.managed_ready = threading.Event() self.multi_end = threading.Event() self.managed_accounts = [] self.rows = [] self.end_req_id = None self.info_codes = [] self.errors_seen = []
def nextValidId(self, orderId): self.ready.set()
def managedAccounts(self, accountsList): self.managed_accounts = [item for item in accountsList.split(",") if item] self.managed_ready.set()
def accountUpdateMulti(self, reqId, account, modelCode, key, value, currency): # value 和 account 可能包含敏感信息;示例只保存结构化统计所需字段。 self.rows.append({ "reqId": reqId, "account": account, "modelCode": modelCode or "EMPTY", "key": key, "value": value, "currency": currency or "EMPTY", })
def accountUpdateMultiEnd(self, reqId): self.end_req_id = reqId self.multi_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))
def value_type(value): try: float(value) return "decimal" except (TypeError, ValueError): return "text"
app = AccountUpdatesMultiOverviewApp()
try: app.connect("127.0.0.1", 7497, clientId=974)
thread = threading.Thread(target=app.run, daemon=True) thread.start()
if not app.ready.wait(8): raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts()
if not app.managed_ready.wait(8): raise RuntimeError("等待 managedAccounts 超时")
if not app.managed_accounts: raise RuntimeError("这个 TWS 会话没有返回可见账户")
account = app.managed_accounts[0] model_code = "" ledger_and_nlv = True
app.reqAccountUpdatesMulti(REQ_ID, account, model_code, ledger_and_nlv)
if not app.multi_end.wait(8): raise RuntimeError("等待 accountUpdateMultiEnd 超时")
rows_before_cancel = len(app.rows)
app.cancelAccountUpdatesMulti(REQ_ID) time.sleep(0.5)
rows_after_cancel = len(app.rows)
finally: if app.isConnected(): app.disconnect()
print(f"REQUEST_ID={REQ_ID}")print(f"ACCOUNT_ALIAS={'ACCOUNT_1' if app.managed_accounts else 'NONE'}")print("MODEL_CODE=EMPTY")print("LEDGER_AND_NLV=True")print(f"END_RECEIVED={app.multi_end.is_set()}")print(f"END_REQID_MATCHES={app.end_req_id == REQ_ID}")print(f"RAW_CALLBACK_ROWS={len(app.rows)}")print(f"UNIQUE_KEY_COUNT={len({row['key'] for row in app.rows})}")print("CURRENCY_COUNTS=" + ",".join(f"{key}:{value}" for key, value in Counter(row["currency"] for row in app.rows).items()))print("VALUE_TYPE_COUNTS=" + ",".join(f"{key}:{value}" for key, value in Counter(value_type(row["value"]) for row in app.rows).items()))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)}")使用TWS 模拟账户、127.0.0.1:7497 和独立 clientId 检查时,脱敏后的输出如下:
CONNECTED=TrueREQUEST_ID=9101ACCOUNT_ALIAS=ACCOUNT_1MODEL_CODE=EMPTYLEDGER_AND_NLV=TrueEND_RECEIVED=TrueEND_REQID_MATCHES=TrueRAW_CALLBACK_ROWS=50UNIQUE_KEY_COUNT=25REQID_COUNTS=9101:50MODEL_CODE_COUNTS=EMPTY:50CURRENCY_COUNTS=BASE:25,USD:25VALUE_TYPE_COUNTS=decimal:44,text:6SAMPLE_KEYS=$LEDGER-AccountOrGroup,$LEDGER-AccruedCash,$LEDGER-CashBalance,$LEDGER-CorporateBondValue,$LEDGER-Cryptocurrency,$LEDGER-Currency,$LEDGER-ExchangeRate,$LEDGER-FundValue,$LEDGER-FutureOptionValue,$LEDGER-FuturesPNL,$LEDGER-FxCashBalance,$LEDGER-IssuerOptionValue,$LEDGER-MoneyMarketFundValue,$LEDGER-MutualFundValueCANCEL_SENT=TrueROWS_BEFORE_CANCEL=50ROWS_AFTER_CANCEL_WAIT=50INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False这次验证说明:
reqAccountUpdatesMulti()能在普通模拟账户环境下正常返回数据。- 空
modelCode会在回调中显示为空模型组合,本例脱敏为EMPTY。 - 50 行回调全部带回同一个
reqId=9101,便于程序按请求归类。 ledgerAndNLV=True时,本例返回了 25 个$LEDGER-相关 key,每个 key 对应BASE和USD两种币种行。cancelAccountUpdatesMulti(9101)发送后,短时间等待没有新增行。
什么时候使用它
Section titled “什么时候使用它”| 场景 | 建议 |
|---|---|
| 单账户新手学习账户字段 | 优先使用 reqAccountUpdates() |
需要用 reqId 区分多条账户请求 | 使用 reqAccountUpdatesMulti() |
| 顾问账户、多个子账户或模型组合 | 使用 reqAccountUpdatesMulti(),并按 account、modelCode 分组 |
| 只需要净清算值、购买力等摘要字段 | 优先使用 reqAccountSummary() |
| 需要组合持仓明细 | 看持仓接口和 position multi 页面 |
modelCode 可以随便填吗?
Section titled “modelCode 可以随便填吗?”不可以。modelCode 要和 IBKR 账户里的模型组合代码对应。随便填一个字符串,不会自动创建模型组合,也不会变成策略标签。
ledgerAndNLV=True 是否越多越好?
Section titled “ledgerAndNLV=True 是否越多越好?”不一定。它会让返回字段更完整,但数据量也更多。如果你的页面只展示少量摘要字段,先用账户摘要接口更简单。如果你的程序要核对账本币种、现金余额、净值或多币种账户状态,再考虑打开它。
multi 账户更新会返回持仓明细吗?
Section titled “multi 账户更新会返回持仓明细吗?”它主要返回账户字段 key/value。持仓明细更适合用 reqPositions() 或 reqPositionsMulti() 处理。不要把账户更新字段当作完整持仓列表。