请求账户盈亏
reqPnL() 用来订阅账户级实时盈亏。
app.reqPnL(reqId, account, modelCode)它返回账户维度的当日盈亏、未实现盈亏和已实现盈亏,回调方法是 pnl()。
def reqPnL(self, reqId: int, account: str, modelCode: str): ...| 参数 | 中文含义 | 说明 |
|---|---|---|
reqId | 请求编号 | 用于追踪这次 PnL 订阅 |
account | 账户代码 | 建议从 reqManagedAccts() 获取 |
modelCode | 模型代码 | 没有模型过滤时传空字符串 "" |
账户级 PnL 数据与 TWS Portfolio Window 显示相关。官方文档说明,账户 PnL 需要 TWS 配置中准备组合 PnL 数据的选项支持。
nextValidId() -> reqManagedAccts() -> reqPnL(reqId, account, "") -> pnl(reqId, dailyPnL, unrealizedPnL, realizedPnL) -> cancelPnL(reqId)reqPnL() 是订阅,不是一次性查询。收到第一条 pnl() 后,如果只需要做连通性检查时,可以立即取消。
Python 请求示例
Section titled “Python 请求示例”import threading
from ibapi.client import EClientfrom ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2103, 2104, 2105, 2106, 2107, 2108, 2158}
class RequestAccountPnLApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.accounts_ready = threading.Event() self.pnl_ready = threading.Event() self.accounts = [] self.pnl_count = 0 self.reqid_counts = {} self.value_type_counts = {} self.info_codes = [] self.errors_seen = []
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 pnl(self, reqId, dailyPnL, unrealizedPnL, realizedPnL): self.pnl_count += 1 self.reqid_counts[reqId] = self.reqid_counts.get(reqId, 0) + 1 key = ( "number" if isinstance(dailyPnL, (int, float)) else type(dailyPnL).__name__, "number" if isinstance(unrealizedPnL, (int, float)) else type(unrealizedPnL).__name__, "number" if isinstance(realizedPnL, (int, float)) else type(realizedPnL).__name__, ) self.value_type_counts[key] = self.value_type_counts.get(key, 0) + 1 self.pnl_ready.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 = RequestAccountPnLApp()req_id = 9301
try: app.connect("127.0.0.1", 7497, clientId=124) thread = threading.Thread(target=app.run, daemon=True) thread.start()
connected = app.ready.wait(8)
if not connected: raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts() if not app.accounts_ready.wait(5): raise RuntimeError("等待账户列表超时")
app.reqPnL(req_id, app.accounts[0], "") callback_received = app.pnl_ready.wait(8)
finally: if app.isConnected(): app.cancelPnL(req_id) app.disconnect()
print(f"CONNECTED={connected}")print("ACCOUNT_ALIAS=ACCOUNT_1")print(f"MANAGED_ACCOUNT_COUNT={len(app.accounts)}")print(f"ACCOUNT_REQUEST_ID={req_id}")print(f"ACCOUNT_PNL_CALLBACK_RECEIVED={callback_received}")print(f"ACCOUNT_PNL_ROW_COUNT={app.pnl_count}")print("ACCOUNT_PNL_REQID_COUNTS=" + (",".join(f"{k}:{v}" for k, v in app.reqid_counts.items()) if app.reqid_counts else "NONE"))print("ACCOUNT_PNL_VALUE_TYPE_COUNTS=" + (",".join(f"{'/'.join(k)}:{v}" for k, v in app.value_type_counts.items()) if app.value_type_counts else "NONE"))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)}")print(f"IS_CONNECTED_AFTER_DISCONNECT={app.isConnected()}")CONNECTED=TrueACCOUNT_ALIAS=ACCOUNT_1MANAGED_ACCOUNT_COUNT=1ACCOUNT_REQUEST_ID=9301ACCOUNT_PNL_CALLBACK_RECEIVED=TrueACCOUNT_PNL_ROW_COUNT=1ACCOUNT_PNL_REQID_COUNTS=9301:1ACCOUNT_PNL_VALUE_TYPE_COUNTS=number/number/number:1INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=FalseTWS 模拟账户收到账户级 PnL 回调,说明 reqPnL()、pnl() 和 cancelPnL() 链路正常。示例没有打印真实金额,只验证请求、字段类型、等待、超时处理和取消流程。
可以传 "All" 吗?
Section titled “可以传 "All" 吗?”顾问或 introducing broker 场景可能支持聚合请求,但普通新手代码建议先传明确账户代码。这样更容易排查权限、账户和模型过滤问题。
为什么示例不打印 PnL 金额?
Section titled “为什么示例不打印 PnL 金额?”盈亏金额属于敏感交易数据。公开文档里验证字段结构和回调链路即可,真实系统应在权限控制后展示。
没有回调怎么办?
Section titled “没有回调怎么办?”先确认 TWS 已经加载组合窗口数据,再确认账户代码、API 连接和 TWS PnL 设置。代码中必须设置超时;没有 PnL 回调但也没有非信息类错误时,不要让程序无限等待,应记录为“本次未收到数据”并继续取消订阅。