Send a Stop Order

Place a BUY STOP on SPY far above market — the trigger never fires, so the order stays resting. Watch the acknowledgement, then cancel it.

What this shows

  • Allocating a fresh order_id from client.next_order_id().
  • Building an Order with order_type = "STP" and the trigger in aux_price.
  • Reading order_status callbacks (PreSubmittedSubmitted).
  • Cancelling with cancel_order and observing the Cancelled terminal status.

Paper account only. The trigger is set far above market so it will not fire — a real stop converts to a market order on touch.

Run it

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

Source

//! Recipe: place a BUY STP on SPY far above market, watch Submitted, then cancel.
//!
//! Usage: IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_stop_order

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

use ibx::api::client::{Contract, EClient, EClientConfig, Order};
use ibx::api::wrapper::Wrapper;

#[derive(Default)]
struct State {
    statuses: Vec<(i64, String)>,
}

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

impl Wrapper for OrderWrapper {
    fn order_status(
        &mut self, order_id: i64, status: &str, _filled: f64, _remaining: f64,
        _avg_fill: f64, _perm_id: i64, _parent_id: i64, _last_fill: f64,
        _client_id: i64, _why_held: &str, _mkt_cap_price: f64,
    ) {
        println!("[status] oid={order_id} status={status}");
        self.state.lock().unwrap().statuses.push((order_id, status.into()));
    }
    fn error(&mut self, req_id: i64, code: i64, msg: &str, _adv: &str) {
        eprintln!("[error] req_id={req_id} code={code} msg={msg}");
    }
}

fn pump_until<F: Fn(&State) -> bool>(
    client: &EClient, wrapper: &mut OrderWrapper, state: &Arc<Mutex<State>>,
    timeout: Duration, done: F,
) -> bool {
    let deadline = Instant::now() + timeout;
    while Instant::now() < deadline {
        client.process_msgs(wrapper);
        if done(&state.lock().unwrap()) { return true; }
        std::thread::sleep(Duration::from_millis(20));
    }
    false
}

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

    let order_id = client.next_order_id();

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

    let spy = Contract {
        con_id: 756733,
        symbol: "SPY".into(),
        sec_type: "STK".into(),
        exchange: "SMART".into(),
        currency: "USD".into(),
        ..Default::default()
    };
    let order = Order {
        action: "BUY".into(),
        total_quantity: 1.0,
        order_type: "STP".into(),
        aux_price: 9999.0,
        tif: "GTC".into(),
        outside_rth: true,
        ..Default::default()
    };

    println!("placing BUY 1 SPY STP 9999.00 (oid={order_id})");
    client.place_order(order_id, &spy, &order)?;

    pump_until(&client, &mut wrapper, &state, Duration::from_secs(15),
               |s| s.statuses.iter().any(|(id, st)| *id == order_id && st == "Submitted"));

    println!("cancelling oid={order_id}");
    client.cancel_order(order_id, "")?;

    pump_until(&client, &mut wrapper, &state, Duration::from_secs(15),
               |s| s.statuses.iter().any(|(id, st)| *id == order_id && st == "Cancelled"));

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