Request Account PnL

Subscribe to the account-level PnL stream, take the first update, cancel and disconnect.

What this shows

  • Reading the account name off client.account_id, which connect populates.
  • req_pnl(req_id, account, model_code).
  • 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 pass "".

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=... cargo run --example hello_pnl

Source

//! Recipe: subscribe to account-level PnL, take one update, then cancel.
//!
//! Usage: IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_pnl

use std::env;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

use ib_dx::api::client::{EClient, EClientConfig};
use ib_dx::api::wrapper::Wrapper;

#[derive(Default)]
struct State {
    pnl: Option<(f64, f64, f64)>,
}

struct PnlWrapper {
    state: Arc<Mutex<State>>,
}

impl Wrapper for PnlWrapper {
    fn pnl(&mut self, _req_id: i64, daily: f64, unrealized: f64, realized: f64) {
        self.state.lock().unwrap().pnl = Some((daily, unrealized, realized));
    }
    fn error(&mut self, req_id: i64, _error_time: i64, code: i64, msg: &str, _adv: &str) {
        eprintln!("[error] req_id={req_id} code={code} msg={msg}");
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = EClient::connect(&EClientConfig {
        username: env::var("IB_USERNAME")?,
        password: env::var("IB_PASSWORD")?,
        paper: true,
        ..Default::default()
    })?;

    let account = client.account_id.clone();
    println!("account: {account}");

    let state = Arc::new(Mutex::new(State::default()));
    let mut wrapper = PnlWrapper { state: state.clone() };

    let req_id = 1;
    client.req_pnl(req_id, &account, "");

    let deadline = Instant::now() + Duration::from_secs(15);
    while Instant::now() < deadline {
        client.process_msgs(&mut wrapper);
        if state.lock().unwrap().pnl.is_some() { break; }
        std::thread::sleep(Duration::from_millis(20));
    }

    match state.lock().unwrap().pnl {
        Some((daily, unrealized, realized)) => {
            println!("daily={daily:.2}  unrealized={unrealized:.2}  realized={realized:.2}");
        }
        None => println!("no pnl update within 15s — try again with an open position"),
    }

    client.cancel_pnl(req_id);
    client.disconnect();
    Ok(())
}