投资组合
get_portfolio() 用来读取账户窗口中的投资组合明细。它比“持仓”多了市场价、市值、平均成本、未实现盈亏和已实现盈亏等字段,更适合做账户展示、风控看板和组合状态检查。
它对应原始 TWS API 的这一组调用和回调:
reqAccountUpdates(True, accountCode) -> updatePortfolio(contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName) -> accountDownloadEnd(accountName)reqAccountUpdates(False, accountCode)如果账户没有持仓,组合列表也可能是空列表。
本地官方源码中的同步封装如下:
def get_portfolio(self, account_code="", timeout=None): self.portfolio = []
self.reqAccountUpdates(True, account_code) portfolio = self._wait_for_response(0, "portfolio", timeout)
self.reqAccountUpdates(False, account_code)
return portfolioupdatePortfolio() 回调会把每一行组合明细整理成:
{ "contract": contract, "position": position, "marketPrice": marketPrice, "marketValue": marketValue, "averageCost": averageCost, "unrealizedPNL": unrealizedPNL, "realizedPNL": realizedPNL, "accountName": accountName,}其中 accountName、position、marketValue、averageCost 和盈亏字段都属于敏感账户数据,公开展示前应脱敏或汇总。
最小查询示例
Section titled “最小查询示例”下面示例读取组合数据,但只输出行数和字段名。即使账户有持仓,也不会把数量、成本、市值和盈亏直接打印出来。
from ibapi.sync_wrapper import TWSSyncWrapper, ResponseTimeout
app = TWSSyncWrapper(timeout=12)
try: if not app.connect_and_start("127.0.0.1", 7497, 958): raise RuntimeError("连接 TWS API 失败")
portfolio = app.get_portfolio(account_code="", timeout=10) print(f"PORTFOLIO_ROW_COUNT={len(portfolio)}")
if portfolio: item = portfolio[0] contract = item["contract"]
print( "SAMPLE_FIELDS=" "contract.symbol,contract.secType,contract.currency," "position,marketPrice,marketValue,averageCost," "unrealizedPNL,realizedPNL,accountName" ) print(f"SAMPLE_SYMBOL={contract.symbol}") print(f"SAMPLE_SEC_TYPE={contract.secType}") print(f"SAMPLE_CURRENCY={contract.currency}") print("SAMPLE_POSITION=<redacted>") print("SAMPLE_MARKET_PRICE=<redacted>") print("SAMPLE_MARKET_VALUE=<redacted>") print("SAMPLE_AVERAGE_COST=<redacted>") print("SAMPLE_UNREALIZED_PNL=<redacted>") print("SAMPLE_REALIZED_PNL=<redacted>") print("SAMPLE_ACCOUNT=<redacted>")
except ResponseTimeout: print("等待组合数据回调超时,请确认 TWS API 连接正常。")
finally: app.disconnect_and_stop() print(f"IS_CONNECTED_AFTER_STOP={app.isConnected()}")使用 TWS 模拟账户、127.0.0.1:7497 和独立 clientId 检查时,如果账户没有组合持仓明细,输出类似:
CONNECTED=TruePORTFOLIO_ROW_COUNT=0IS_CONNECTED_AFTER_STOP=False这说明账户更新订阅已完成,只是账户没有可返回的组合行。0 行组合不是错误。
| 字段 | 中文含义 |
|---|---|
contract.symbol | 合约代码 |
contract.secType | 证券类型 |
contract.currency | 币种 |
position | 持仓数量 |
marketPrice | 市场价格 |
marketValue | 市值 |
averageCost | 平均成本 |
unrealizedPNL | 未实现盈亏 |
realizedPNL | 已实现盈亏 |
accountName | 账户代码,公开展示前必须脱敏 |
组合数据通常和 TWS 的账户窗口保持一致。官方文档说明,除非持仓发生变化,否则账户和组合更新可能按固定间隔刷新,而不是每个字段实时逐笔推送。
和持仓接口的区别
Section titled “和持仓接口的区别”| 对比项 | 持仓 get_positions() | 组合 get_portfolio() |
|---|---|---|
| 原始请求 | reqPositions() | reqAccountUpdates(True, accountCode) |
| 结束信号 | positionEnd() | accountDownloadEnd() |
| 是否包含市场价 | 不包含 | 包含 |
| 是否包含市值 | 不包含 | 包含 |
| 是否包含盈亏 | 不包含 | 包含 |
| 适合用途 | 判断持有什么、持仓数量 | 展示组合、市值、成本和盈亏 |
如果你只需要知道账户持有哪些合约,使用持仓接口更轻;如果要给用户展示“组合”和“浮动盈亏”,使用组合数据更直接。
reqAccountUpdates(True, accountCode) 是订阅账户更新,reqAccountUpdates(False, accountCode) 是取消订阅。同步封装在收到 accountDownloadEnd() 后会自动取消订阅,适合一次性读取组合。
官方文档里还有几个重要边界:
| 规则 | 说明 |
|---|---|
| 单账户结构 | accountCode 可以为空,TWS 能推断账户 |
| 多账户结构 | 通常要传明确账户代码 |
| 同一时间订阅 | 普通 reqAccountUpdates() 同一时间只适合订阅一个账户 |
| 大型 FA / IBroker | 应考虑 reqAccountUpdatesMulti(),不要用单账户逻辑硬套所有子账户 |
accountReady=false | 表示服务器可能正在重置,账户字段可能不完整或过期 |
普通新手项目可以先从单账户读取开始,等需要多账户或模型组合时,再单独设计 reqAccountUpdatesMulti()。
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
PORTFOLIO_ROW_COUNT=0 | 没有持仓,或组合窗口没有可返回明细 | 这是正常结果;可在 TWS 账户窗口确认 |
| 查询超时 | 没有收到 accountDownloadEnd() | 检查 TWS 是否在线、API 是否连接、账户窗口是否正常 |
| 市值或盈亏不更新 | 官方账户更新不是逐 tick 推送 | 做实时风控时要结合行情和本地计算 |
| 账户号出现在日志 | accountName 是组合字段 | 日志和文档发布前必须脱敏 |
| 多账户结果不完整 | 账户代码为空或订阅被覆盖 | 多账户场景传明确账户代码,必要时使用多账户更新接口 |