Request Contract Details

Resolve a symbol: send a partial description, collect every contract the venue lists under it, stop when the end marker lands. This is how you turn "AAPL" into a con_id you can quote and trade on.

What this shows

  • Building a partial Contract from symbol, sec_type, exchange and currency.
  • req_contract_details(req_id, &contract), and reading answers off the contract_details callback.
  • Pumping the callbacks with process_msgs until contract_details_end fires.
  • Reading con_id, primary_exchange and trading_class out of ContractDetails.

What comes back

One contract_details callback per matching contract, then one contract_details_end for the request id. A description that matches several listings answers with all of them, so rows.len() above one is an ambiguous description rather than an error.

The shorter form

req_contract_details is the request-and-callback shape. Two blocking calls do the same work without the wrapper:

let rows = client.contract_details(&aapl)?;   // every match
let one  = client.qualify_contract(&aapl)?;   // the single match

qualify_contract refuses an ambiguous description rather than handing back whichever the venue listed first, which would be a different contract from the one you asked about. Nothing that answers either call reaches the wrapper process_msgs is handed, even what arrives after the call has returned.

Lookups by contract id

A lookup by con_id includes the requested exchange. Leaving the exchange empty asks for the preferred definition. The same rule applies when a market data request needs to name a contract given only by id.

Contract details reuse exchange rule scopes already returned during the session. A first lookup can still need a separate request for each missing exchange; later lookups reuse those answers. CORPACT is excluded because it does not name a trading venue.

Run it

IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_contract_details

Source

//! Recipe: req_contract_details for AAPL on paper, print con_id and primary exchange.
//!
//! Usage: IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_contract_details

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

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

#[derive(Default)]
struct State {
    rows: Vec<ContractDetails>,
    end_seen: bool,
}

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

impl Wrapper for DetailsWrapper {
    fn contract_details(&mut self, _req_id: i64, details: &ContractDetails) {
        self.state.lock().unwrap().rows.push(details.clone());
    }
    fn contract_details_end(&mut self, _req_id: i64) {
        self.state.lock().unwrap().end_seen = true;
    }
    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 state = Arc::new(Mutex::new(State::default()));
    let mut wrapper = DetailsWrapper { state: state.clone() };

    let aapl = Contract {
        symbol: "AAPL".into(),
        sec_type: "STK".into(),
        exchange: "SMART".into(),
        currency: "USD".into(),
        ..Default::default()
    };
    client.req_contract_details(1, &aapl);

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

    let s = state.lock().unwrap();
    println!("matches: {}", s.rows.len());
    for d in &s.rows {
        println!(
            "  con_id={:>8}  primary={:<10}  trading_class={}",
            d.contract.con_id, d.contract.primary_exchange, d.contract.trading_class,
        );
    }

    drop(s);
    client.disconnect();
    Ok(())
}