跳转到内容

请求账户盈亏

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() 后,如果只需要做连通性检查时,可以立即取消。

import threading
from ibapi.client import EClient
from 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=True
ACCOUNT_ALIAS=ACCOUNT_1
MANAGED_ACCOUNT_COUNT=1
ACCOUNT_REQUEST_ID=9301
ACCOUNT_PNL_CALLBACK_RECEIVED=True
ACCOUNT_PNL_ROW_COUNT=1
ACCOUNT_PNL_REQID_COUNTS=9301:1
ACCOUNT_PNL_VALUE_TYPE_COUNTS=number/number/number:1
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

TWS 模拟账户收到账户级 PnL 回调,说明 reqPnL()pnl()cancelPnL() 链路正常。示例没有打印真实金额,只验证请求、字段类型、等待、超时处理和取消流程。

顾问或 introducing broker 场景可能支持聚合请求,但普通新手代码建议先传明确账户代码。这样更容易排查权限、账户和模型过滤问题。

盈亏金额属于敏感交易数据。公开文档里验证字段结构和回调链路即可,真实系统应在权限控制后展示。

先确认 TWS 已经加载组合窗口数据,再确认账户代码、API 连接和 TWS PnL 设置。代码中必须设置超时;没有 PnL 回调但也没有非信息类错误时,不要让程序无限等待,应记录为“本次未收到数据”并继续取消订阅。