跳转到内容

账户更新总览

reqAccountUpdates() 用来订阅某个账户的账户值、组合持仓和更新时间。它比账户摘要更偏向“账户页面数据流”,会通过多个回调持续推送数据:

reqAccountUpdates(True, account)
-> updateAccountValue(key, value, currency, accountName)
-> updatePortfolio(contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName)
-> updateAccountTime(timeStamp)
-> accountDownloadEnd(accountName)
reqAccountUpdates(False, account)

如果你要做账户面板、组合持仓面板、资金字段观察或下单前账户状态检查,这组接口很重要。

账户摘要和账户更新都能返回账户相关数据,但定位不同:

接口主要用途典型回调
reqAccountSummary()请求一组摘要标签,例如净清算值、购买力、可用资金accountSummary()accountSummaryEnd()
reqAccountUpdates()订阅某个账户的账户值、组合持仓和更新时间updateAccountValue()updatePortfolio()updateAccountTime()accountDownloadEnd()

账户摘要适合做轻量概览。账户更新更适合做账户状态页,因为它除了账户值,还会推送组合持仓行。

Python API 中的调用形式如下:

app.reqAccountUpdates(subscribe, acctCode)
参数中文含义说明
subscribe是否订阅True 开始接收账户更新,False 停止接收
acctCode账户代码登录会话可见的账户号;公开日志中应脱敏

普通单账户环境下,可以先通过 reqManagedAccts() 获取可见账户,再传给 reqAccountUpdates(True, account)

回调中文含义说明
updateAccountValue()账户字段值例如现金、保证金、购买力、Ledger 字段等
updatePortfolio()组合持仓行每个持仓合约一行,包含持仓数量、市价、市值、成本和盈亏
updateAccountTime()账户更新时间返回账户更新批次的时间字符串
accountDownloadEnd()初始批次结束表示这一轮账户值和组合持仓初始数据已经发完

accountDownloadEnd()accountSummaryEnd() 类似,只代表初始批次结束,不代表订阅已经自动停止。

TWS API 的传统 reqAccountUpdates() 一次只能订阅一个账户。官方错误码里常见的 2100 就和这个限制有关:当新的账户数据请求替换旧请求,或取消订阅时,TWS 可能提示账户数据已退订。

这不是普通网络断线错误。处理时要关注两点:

  • 程序里清楚记录正在订阅哪个账户。
  • 如果要换账户,先取消旧订阅,再发起新订阅。

多账户或模型组合场景,应使用 reqAccountUpdatesMulti(),这样每条回调都会带有 reqId、账户和模型代码,程序更容易归类。

下面示例会:

  • 连接TWS 模拟账户。
  • 通过 reqManagedAccts() 获取可见账户。
  • 订阅 reqAccountUpdates(True, account)
  • 等待 accountDownloadEnd()
  • 取消订阅并断开连接。

示例输出只保留字段名、数量和脱敏后的账户别名。

import threading
import time
from collections import Counter
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2104, 2106, 2158}
class AccountUpdatesOverviewApp(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.ready = threading.Event()
self.managed_ready = threading.Event()
self.download_end = threading.Event()
self.managed_accounts = []
self.account_values = []
self.portfolio_rows = []
self.account_times = []
self.download_end_account = 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 updateAccountValue(self, key, val, currency, accountName):
# val 和 accountName 都可能包含敏感信息,公开输出里只保留 key 和币种。
self.account_values.append((key, currency or "EMPTY", accountName))
def updatePortfolio(
self,
contract,
position,
marketPrice,
marketValue,
averageCost,
unrealizedPNL,
realizedPNL,
accountName,
):
# 持仓、盈亏、成本都属于敏感数据。公开输出只统计数量和证券类型。
self.portfolio_rows.append({
"secType": contract.secType,
"currency": contract.currency,
"accountName": accountName,
})
def updateAccountTime(self, timeStamp):
self.account_times.append(timeStamp)
def accountDownloadEnd(self, accountName):
self.download_end_account = accountName
self.download_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 = AccountUpdatesOverviewApp()
try:
app.connect("127.0.0.1", 7497, clientId=969)
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 超时")
account = app.managed_accounts[0]
app.reqAccountUpdates(True, account)
if not app.download_end.wait(8):
raise RuntimeError("等待 accountDownloadEnd 超时")
rows_before_cancel = len(app.account_values) + len(app.portfolio_rows)
app.reqAccountUpdates(False, account)
time.sleep(0.3)
rows_after_cancel = len(app.account_values) + len(app.portfolio_rows)
finally:
if app.isConnected():
app.disconnect()

使用TWS 模拟账户、127.0.0.1:7497 和独立 clientId 检查时,脱敏后的输出如下:

CONNECTED=True
ACCOUNT_ALIAS=ACCOUNT_1
MANAGED_ACCOUNT_COUNT=1
SUBSCRIBE_SENT=True
ACCOUNT_VALUE_ROW_COUNT=183
ACCOUNT_VALUE_UNIQUE_KEYS=158
PORTFOLIO_ROW_COUNT=0
ACCOUNT_TIME_COUNT=1
DOWNLOAD_END_RECEIVED=True
DOWNLOAD_END_ACCOUNT_MATCHES=True
CURRENCIES=BASE,EMPTY,USD
PORTFOLIO_SECURITY_TYPES=NONE
SAMPLE_ACCOUNT_VALUE_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
KEY_PREFIX_COUNTS=$LEDGER:50,AccountCode:1,AccountReady:1,AccountType:1,AccruedCash:4,AccruedDividend:4,AvailableFunds:4,Billable:4,BuyingPower:1,ColumnPrio:3,Cushion:1,EquityWithLoanValue:4
UNSUBSCRIBE_SENT=True
ROWS_BEFORE_UNSUBSCRIBE=183
ROWS_AFTER_UNSUBSCRIBE_WAIT=183
INFO_CODES=2100,2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

TWS 模拟账户返回了 183 行账户值、158 个去重字段、1 次更新时间,并收到 accountDownloadEnd()。组合持仓行是 0,表示这个环境没有通过该回调返回持仓行;这不代表接口不能返回持仓,只是账户状态下没有持仓数据进入 updatePortfolio()

INFO_CODES 中的 2100 是账户数据退订提示,210421062158 是常见数据服务连接状态提示。本例没有非信息类错误。

需求是否适合
显示账户资金字段适合
显示组合持仓行适合,但取决于账户是否有持仓数据
下单前检查购买力和可用资金适合,但还应结合订单预检查
只查少量摘要字段可以,但 reqAccountSummary() 更轻量
多账户同时订阅不适合传统 reqAccountUpdates(),应看 multi 接口
成交流水查询不适合,应使用成交接口

账户更新比账户摘要更容易泄露敏感信息,因为它可能包含账户号、现金、保证金、持仓、市值、成本和盈亏。

公开日志建议只保留:

  • 回调数量。
  • 字段名。
  • 币种。
  • 是否收到 accountDownloadEnd()
  • 脱敏账户别名,例如 ACCOUNT_1

不要公开打印真实账户号、真实金额、持仓合约、成本和盈亏。

账户更新这一组包含以下页面:

页面内容
请求账户更新reqAccountUpdates(True, account) 的参数、账户号来源和订阅限制
接收账户更新updateAccountValue()updatePortfolio()updateAccountTime()accountDownloadEnd() 的字段解释
账户值字段 key常见 account value key 的中文含义
取消账户更新reqAccountUpdates(False, account) 的取消流程和常见提示

IBKR Campus: TWS API Documentation