跳转到内容

请求单个持仓盈亏

reqPnLSingle() 用来订阅单个持仓的实时盈亏。

app.reqPnLSingle(reqId, account, modelCode, conid)

它只适合请求账户中已经存在的持仓。如果 conid 无效,或者账户里没有这个合约持仓,可能不会返回 pnlSingle() 回调。

def reqPnLSingle(self, reqId: int, account: str, modelCode: str, conid: int):
...
参数中文含义说明
reqId请求编号自己分配,用于接收和取消这次订阅
account账户代码建议从 reqManagedAccts() 获取,不写死
modelCode模型代码没有模型组合时传空字符串 ""
conid合约 ID必须是合约的 IBKR conId,不是股票代码

conid 可以通过合约详情接口查到。比如 AAPL 股票常见 conId265598,但只有账户确实持有该合约时,单持仓 PnL 才有意义。

nextValidId()
-> reqManagedAccts()
-> reqPnLSingle(reqId, account, "", conid)
-> pnlSingle(...)
-> cancelPnLSingle(reqId)

没有收到 pnlSingle() 不一定是程序崩了。对于单持仓 PnL,需要同时检查账户是否有该持仓、conId 是否正确、TWS 是否已经加载组合数据。

import threading
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2103, 2104, 2105, 2106, 2107, 2108, 2158}
class RequestPnLSingleApp(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.ready = threading.Event()
self.accounts_ready = threading.Event()
self.pnl_single_ready = threading.Event()
self.accounts = []
self.row_count = 0
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 pnlSingle(self, reqId, pos, dailyPnL, unrealizedPnL, realizedPnL, value):
self.row_count += 1
self.pnl_single_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 = RequestPnLSingleApp()
req_id = 9302
conid = 265598 # AAPL 示例 conId;真实使用时应替换为账户中实际持仓的 conId
try:
app.connect("127.0.0.1", 7497, clientId=122)
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.reqPnLSingle(req_id, app.accounts[0], "", conid)
callback_received = app.pnl_single_ready.wait(5)
finally:
if app.isConnected():
app.cancelPnLSingle(req_id)
app.disconnect()
print(f"CONNECTED={connected}")
print(f"SINGLE_PNL_CALLBACK_RECEIVED={callback_received}")
print(f"SINGLE_PNL_ROW_COUNT={app.row_count}")
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
SINGLE_REQUEST_ID=9302
SINGLE_CONID=265598
SINGLE_PNL_CALLBACK_RECEIVED=False
SINGLE_PNL_ROW_COUNT=0
SINGLE_PNL_REQID_COUNTS=NONE
SINGLE_PNL_VALUE_TYPE_COUNTS=NONE
SINGLE_CANCEL_SENT=True
SINGLE_ROWS_BEFORE_CANCEL=0
SINGLE_ROWS_AFTER_CANCEL_WAIT=0
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

TWS 模拟账户没有 AAPL 持仓,因此没有收到 pnlSingle()。这和官方边界一致:合约不在账户持仓中时,单持仓 PnL 可能没有响应。

不是。conid 是 IBKR 合约 ID。股票代码是 symbol,两者不是同一个字段。

不是。单持仓 PnL 对“账户中是否存在该持仓”很敏感。没有持仓时可能没有回调,也没有非信息类错误。

需要账户里真实持有这个合约。模拟账户中可以先通过持仓接口确认 conId,再请求对应合约的 PnL。