Skip to content

Commit 7cb2ec3

Browse files
authored
Merge pull request #28 from pbeets/examples/bracket-order
feat: Add bracket order example
2 parents 04152ed + a11df51 commit 7cb2ec3

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

examples/bracket_order.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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

Comments
 (0)