|
| 1 | +//! Example: Place a bracket order (entry with profit target and stop loss) |
| 2 | +//! |
| 3 | +//! A bracket order consists of: |
| 4 | +//! - Entry order: your initial position (Buy or Sell) |
| 5 | +//! - Profit target: limit order to take profits at a specified tick distance |
| 6 | +//! - Stop loss: stop order to limit losses at a specified tick distance |
| 7 | +//! |
| 8 | +//! When the entry fills, Rithmic automatically creates and links the profit and stop orders. |
| 9 | +//! When either the profit or stop fills, the other is automatically canceled. |
| 10 | +//! |
| 11 | +//! Run with: cargo run --example bracket_order |
| 12 | +
|
| 13 | +use tokio::sync::broadcast; |
| 14 | +use tracing::info; |
| 15 | + |
| 16 | +use rithmic_rs::{ |
| 17 | + BracketDuration, BracketPriceType, BracketTransactionType, ConnectStrategy, |
| 18 | + RithmicBracketOrder, RithmicConfig, RithmicEnv, RithmicOrderPlant, RithmicResponse, |
| 19 | + rti::messages::RithmicMessage, |
| 20 | +}; |
| 21 | + |
| 22 | +/// Spawns a task to listen for order notifications |
| 23 | +fn spawn_order_listener(mut receiver: broadcast::Receiver<RithmicResponse>) { |
| 24 | + tokio::spawn(async move { |
| 25 | + loop { |
| 26 | + match receiver.recv().await { |
| 27 | + Ok(update) => { |
| 28 | + if let Some(error) = &update.error { |
| 29 | + info!("Connection error: {}", error); |
| 30 | + break; |
| 31 | + } |
| 32 | + |
| 33 | + match &update.message { |
| 34 | + RithmicMessage::HeartbeatTimeout |
| 35 | + | RithmicMessage::ForcedLogout(_) |
| 36 | + | RithmicMessage::ConnectionError => { |
| 37 | + info!("Connection lost"); |
| 38 | + break; |
| 39 | + } |
| 40 | + RithmicMessage::RithmicOrderNotification(notif) => { |
| 41 | + info!( |
| 42 | + "Rithmic order: status={:?} symbol={} qty={} price={:?} basket_id={}", |
| 43 | + notif.status, |
| 44 | + notif.symbol.as_deref().unwrap_or("?"), |
| 45 | + notif.quantity.unwrap_or(0), |
| 46 | + notif.price, |
| 47 | + notif.basket_id.as_deref().unwrap_or("?") |
| 48 | + ); |
| 49 | + } |
| 50 | + RithmicMessage::ExchangeOrderNotification(notif) => { |
| 51 | + info!( |
| 52 | + "Exchange order: status={:?} symbol={} filled_qty={} avg_price={:?}", |
| 53 | + notif.status.as_deref().unwrap_or("?"), |
| 54 | + notif.symbol.as_deref().unwrap_or("?"), |
| 55 | + notif.total_fill_size.unwrap_or(0), |
| 56 | + notif.avg_fill_price |
| 57 | + ); |
| 58 | + } |
| 59 | + RithmicMessage::BracketUpdates(bracket) => { |
| 60 | + info!( |
| 61 | + "Bracket update: basket_id={} target_ticks={:?} stop_ticks={:?}", |
| 62 | + bracket.basket_id.as_deref().unwrap_or("?"), |
| 63 | + bracket.target_ticks, |
| 64 | + bracket.stop_ticks |
| 65 | + ); |
| 66 | + } |
| 67 | + _ => {} |
| 68 | + } |
| 69 | + } |
| 70 | + Err(broadcast::error::RecvError::Closed) => { |
| 71 | + info!("Subscription channel closed"); |
| 72 | + break; |
| 73 | + } |
| 74 | + Err(broadcast::error::RecvError::Lagged(n)) => { |
| 75 | + info!("Listener lagged, missed {} messages", n); |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + }); |
| 80 | +} |
| 81 | + |
| 82 | +#[tokio::main] |
| 83 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 84 | + dotenvy::dotenv().ok(); |
| 85 | + tracing_subscriber::fmt().init(); |
| 86 | + |
| 87 | + // Connect to the order plant (use Demo for paper trading) |
| 88 | + let config = RithmicConfig::from_env(RithmicEnv::Demo)?; |
| 89 | + let order_plant = RithmicOrderPlant::connect(&config, ConnectStrategy::Simple).await?; |
| 90 | + let handle = order_plant.get_handle(); |
| 91 | + |
| 92 | + // Login to the order plant |
| 93 | + handle.login().await?; |
| 94 | + info!("Logged in to order plant"); |
| 95 | + |
| 96 | + // Subscribe to order and bracket updates |
| 97 | + handle.subscribe_order_updates().await?; |
| 98 | + handle.subscribe_bracket_updates().await?; |
| 99 | + info!("Subscribed to order and bracket updates"); |
| 100 | + |
| 101 | + // Spawn listener task before placing orders (fire-and-forget, runs until disconnect) |
| 102 | + // Use resubscribe() to get a new receiver from the same broadcast channel |
| 103 | + let listener_receiver = handle.subscription_receiver.resubscribe(); |
| 104 | + spawn_order_listener(listener_receiver); |
| 105 | + |
| 106 | + // Define the bracket order |
| 107 | + // Note: Update symbol to a valid front-month contract for your use case |
| 108 | + let bracket_order = RithmicBracketOrder { |
| 109 | + action: BracketTransactionType::Buy, |
| 110 | + duration: BracketDuration::Day, |
| 111 | + exchange: "CME".to_string(), |
| 112 | + localid: "example-bracket-1".to_string(), |
| 113 | + price_type: BracketPriceType::Limit, |
| 114 | + price: Some(5000.00), // Entry limit price |
| 115 | + profit_ticks: 20, // Take profit 20 ticks above entry |
| 116 | + stop_ticks: 10, // Stop loss 10 ticks below entry |
| 117 | + qty: 1, |
| 118 | + symbol: "ESH6".to_string(), // E-mini S&P 500 March 2026 |
| 119 | + }; |
| 120 | + |
| 121 | + info!("Placing bracket order: {:?}", bracket_order); |
| 122 | + |
| 123 | + // Place the bracket order |
| 124 | + let responses = handle.place_bracket_order(bracket_order).await?; |
| 125 | + for resp in &responses { |
| 126 | + info!("Order response: {:?}", resp); |
| 127 | + } |
| 128 | + |
| 129 | + // Keep the main task alive to receive notifications |
| 130 | + // In a real application, you'd have other logic here or wait for a shutdown signal |
| 131 | + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; |
| 132 | + |
| 133 | + handle.disconnect().await?; |
| 134 | + info!("Disconnected from order plant"); |
| 135 | + |
| 136 | + Ok(()) |
| 137 | +} |
0 commit comments