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, whichconnectpopulates. req_pnl(req_id, account, model_code).- 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 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(())
}