Login
The smallest program that opens a session: connect, read the order id this client would place under, disconnect.
Two flavours are shown. Paper for everyday testing, live for read-only checks against the real account.
What this shows
- Reading credentials from environment variables into
EClientConfig. paper: truefor the paper account,paper: falsefor the live one.client.account_id, which names the account the session opened under.req_ids, which reports the next order id.
What comes back
EClient::connect blocks until the session is up. When it returns, the session
is established and client.account_id is populated. There is nothing further to
wait for.
connect states the next order id once, as a gateway does: the first
process_msgs read after the venue has named what the account is working calls
next_valid_id on your wrapper, with one past the highest id the account is
working an order under. req_ids states it again without asking the venue
anything. The venue names that working set unprompted at every connect, from
every session, not only this one. Nothing is reserved by either; the
reservation happens when you place.
Limits
A live login may push a second factor. Approve it on your authenticator; the
connect call blocks until the gate clears, so give it a longer wall clock than
a paper login needs.
The live example is read-only: it logs in, prints, and disconnects. Send orders from the paper account.
Paper
Run it
IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_login
Source
//! Hello-world recipe: connect, request next_valid_id, disconnect.
//!
//! Usage: IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_login
use std::env;
use ib_dx::api::client::{EClient, EClientConfig};
use ib_dx::api::wrapper::Wrapper;
#[derive(Default)]
struct LoginWrapper {
next_id: Option<i64>,
}
impl Wrapper for LoginWrapper {
fn next_valid_id(&mut self, order_id: i64) {
self.next_id = Some(order_id);
}
}
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 mut wrapper = LoginWrapper::default();
client.req_ids(1); client.process_msgs(&mut wrapper);
let next_id = wrapper.next_id.ok_or("did not receive next_valid_id")?;
println!("logged in. account = {}, next_valid_id = {next_id}", client.account_id);
client.disconnect();
Ok(())
}
Live
Run it
IB_LIVE_USERNAME=... IB_LIVE_PASSWORD=... cargo run --example hello_login_live
Source
//! Live-account login recipe: connect with `paper: false`, request next_valid_id,
//! Disconnect. Read-only — no orders, no market data.
//!
//! When the live login triggers a second-factor push, approve it on your mobile
//! Authenticator. The connect call blocks until the gate clears.
//!
//! Usage: IB_LIVE_USERNAME=... IB_LIVE_PASSWORD=... cargo run --example hello_login_live
use std::env;
use ib_dx::api::client::{EClient, EClientConfig};
use ib_dx::api::wrapper::Wrapper;
#[derive(Default)]
struct LoginWrapper {
next_id: Option<i64>,
}
impl Wrapper for LoginWrapper {
fn next_valid_id(&mut self, order_id: i64) {
self.next_id = Some(order_id);
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = EClient::connect(&EClientConfig {
username: env::var("IB_LIVE_USERNAME")?,
password: env::var("IB_LIVE_PASSWORD")?,
host: env::var("IB_HOST").unwrap_or_default(),
paper: false,
..Default::default()
})?;
let mut wrapper = LoginWrapper::default();
client.req_ids(1); client.process_msgs(&mut wrapper);
let next_id = wrapper.next_id.ok_or("did not receive next_valid_id")?;
println!("logged in LIVE. account = {}, next_valid_id = {next_id}", client.account_id);
client.disconnect();
Ok(())
}