跳转到内容

投资组合

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 portfolio

updatePortfolio() 回调会把每一行组合明细整理成:

{
"contract": contract,
"position": position,
"marketPrice": marketPrice,
"marketValue": marketValue,
"averageCost": averageCost,
"unrealizedPNL": unrealizedPNL,
"realizedPNL": realizedPNL,
"accountName": accountName,
}

其中 accountNamepositionmarketValueaverageCost 和盈亏字段都属于敏感账户数据,公开展示前应脱敏或汇总。

下面示例读取组合数据,但只输出行数和字段名。即使账户有持仓,也不会把数量、成本、市值和盈亏直接打印出来。

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=True
PORTFOLIO_ROW_COUNT=0
IS_CONNECTED_AFTER_STOP=False

这说明账户更新订阅已完成,只是账户没有可返回的组合行。0 行组合不是错误。

字段中文含义
contract.symbol合约代码
contract.secType证券类型
contract.currency币种
position持仓数量
marketPrice市场价格
marketValue市值
averageCost平均成本
unrealizedPNL未实现盈亏
realizedPNL已实现盈亏
accountName账户代码,公开展示前必须脱敏

组合数据通常和 TWS 的账户窗口保持一致。官方文档说明,除非持仓发生变化,否则账户和组合更新可能按固定间隔刷新,而不是每个字段实时逐笔推送。

对比项持仓 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 是组合字段日志和文档发布前必须脱敏
多账户结果不完整账户代码为空或订阅被覆盖多账户场景传明确账户代码,必要时使用多账户更新接口

IBKR Campus: TWS API Documentation