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_pnl and realized_pnl off the pnl callback.
  • 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()