Request Account PnL
Subscribe to the account-level PnL stream, take the first update, cancel and disconnect.
What this shows
c.get_account_id(), which names the account the session opened under.req_pnl(req_id, account).- Reading
daily_pnl,unrealized_pnlandrealized_pnloff thepnlcallback. cancel_pnl(req_id)before disconnecting.
account empty means the account this session opened under.
What comes back
The venue states what each holding was worth at midnight and what it has
realised since. The three figures on the pnl callback are worked out from
those against the prices this session is being told, so they move as the
session's quotes move.
Limits
account is checked as a gateway checks it, in its order and words: a blank
one is refused (Account must not be empty, 321); one the login does not hold
is refused (Invalid account code, 321), and so is All on a login holding one
account or where the logon says the login may not ask for every account; All
on a login the venue adds accounts to is refused (This API request for All is
not supported for Dynamic Account Addition, 321). Another account the login
holds, and All where a gateway would take it, are refused too, on both
clients: the figures are worked out from one account's midnight seeds and
holdings, the one this session opened under. Nothing is subscribed for a
refused request.
model_code is taken and not applied. There is no model portfolio to name here,
so leave it at its default.
cancel_pnl stops the updates. The venue has no message withdrawing the
subscription itself, on a gateway as here, so the updates stopping is what the
call does.
An account with no position reports zeros. Pair this with the limit-order recipe if you want to watch the numbers move.
Run it
IB_USERNAME=... IB_PASSWORD=... python examples/hello_pnl.py
Source
"""Recipe: subscribe to account-level PnL, take one update, then cancel.
Usage:
IB_USERNAME=... IB_PASSWORD=... python examples/hello_pnl.py
"""
import os
import threading
from ib_dx import EClient, EWrapper
class PnlWrapper(EWrapper):
def __init__(self):
self.got_pnl = threading.Event()
self.pnl_data = None
def pnl(self, req_id, daily_pnl, unrealized_pnl, realized_pnl):
self.pnl_data = (daily_pnl, unrealized_pnl, realized_pnl)
self.got_pnl.set()
def error(self, req_id, error_time, code, msg, advanced=""):
if code not in (2104, 2106, 2158):
print(f"[error] {code}: {msg}")
w = PnlWrapper()
c = EClient(w)
c.connect(
username=os.environ["IB_USERNAME"],
password=os.environ["IB_PASSWORD"],
paper=True,
)
threading.Thread(target=c.run, daemon=True).start()
account = c.get_account_id()
print(f"account: {account}")
req_id = 1
c.req_pnl(req_id, account)
if w.got_pnl.wait(timeout=15):
daily, unrealized, realized = w.pnl_data
print(f"daily={daily:.2f} unrealized={unrealized:.2f} realized={realized:.2f}")
else:
print("no pnl update within 15s — try again with an open position")
c.cancel_pnl(req_id)
c.disconnect()