Send a Limit Order
Place a BUY LMT on SPY far below market, watch it acknowledge, then cancel it. Connect, take an id, place, read status, cancel, disconnect.
What this shows
client.next_order_id(), which reserves one id.- An
Orderwithorder_type: "LMT"andlmt_priceset.outside_rth: trueis what lets it be acknowledged outside regular hours. order_statuscallbacks.PreSubmittedthenSubmittedwhile it rests,Cancelledonce the cancel lands.cancel_order(order_id, order_cancel). The second argument is what the withdrawal states about itself: anOrderCancel(who is withdrawing it, whether a person entered it, a manual time), or a time alone;""states nothing. The operator and who entered it travel on the cancel. A time does not, and the caller is told so onerror; a time a gateway cannot read is refused under 10301 and the order keeps working.
What comes back
order_status on every change and again on each fill. filled and remaining
are quantities, avg_fill_price is the average of what has filled so far. An
order that rests will sit at Submitted until you cancel it.
The perm_id on that callback is the number the order goes to the venue
under, as a gateway states it, which for this order is its own order id. It is
stated from the moment the order is sent and stays the same for the life of the
order, including across a modify.
Limits
next_order_id counts from one past the highest id the account is working an
order under, which the venue names at every connect, from every session. So it
does not collide with an order placed elsewhere.
tif is checked against what the venue carries: DAY, GTC, IOC, FOK,
OPG, GTD, GTX, DTC, AUC, NMIN, spelled exactly. An unrecognised value is
refused here rather than sent as DAY and quietly expiring at the close.
Paper account only. The price is set far below market so it will not fill.
The shorter form
place takes the id itself, sends the order, and waits up to five seconds for
the venue to settle it:
let report = client.place(&spy, &order)?;
println!("{} {} filled", report.status, report.filled);
It reports what is known either way. An order still working is reported as working; it is not cancelled.
Run it
IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_limit_order
Source
//! Recipe: place a non-marketable BUY LMT on SPY, watch Submitted, then cancel.
//!
//! Usage: IB_USERNAME=... IB_PASSWORD=... cargo run --example hello_limit_order
use std::env;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use ib_dx::api::client::{Contract, EClient, EClientConfig, Order};
use ib_dx::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: ib_dx::api::Decimal, _remaining: ib_dx::api::Decimal,
_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, _error_time: 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")?,
paper: true,
..Default::default()
})?;
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: "LMT".into(),
lmt_price: 1.00,
tif: "DAY".into(),
outside_rth: true,
..Default::default()
};
println!("placing BUY 1 SPY LMT 1.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(())
}