rithmic_rs/api/commands/
order.rs1use super::triggers::{RithmicIfTouchedTrigger, TrailingStop};
4use super::validate_instrument;
5
6use crate::{
7 error::RithmicError,
8 types::{ManualOrAutoEntry, OrderSide, OrderType, TimeInForce},
9};
10
11#[derive(Debug, Clone, Default, PartialEq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76#[non_exhaustive]
77#[must_use = "an order does nothing until passed to a plant handle"]
78pub struct RithmicOrder {
79 pub symbol: String,
81 pub exchange: String,
83 pub quantity: i32,
85 pub price: Option<f64>,
87 pub transaction_type: OrderSide,
89 pub price_type: OrderType,
91 pub user_tag: String,
93 pub duration: TimeInForce,
95 pub trigger_price: Option<f64>,
97 pub trailing_stop: Option<TrailingStop>,
99 pub trade_route: Option<String>,
101 pub manual_or_auto: ManualOrAutoEntry,
103 pub window_name: Option<String>,
105 pub release_at_ssboe: Option<i32>,
107 pub release_at_usecs: Option<i32>,
109 pub cancel_at_ssboe: Option<i32>,
111 pub cancel_at_usecs: Option<i32>,
113 pub cancel_after_secs: Option<i32>,
115 pub if_touched: Option<RithmicIfTouchedTrigger>,
117}
118
119impl RithmicOrder {
120 pub fn new() -> Self {
122 Self::default()
123 }
124
125 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
127 self.symbol = symbol.into();
128 self
129 }
130
131 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
133 self.exchange = exchange.into();
134 self
135 }
136
137 pub fn quantity(mut self, quantity: i32) -> Self {
139 self.quantity = quantity;
140 self
141 }
142
143 pub fn transaction_type(mut self, transaction_type: OrderSide) -> Self {
145 self.transaction_type = transaction_type;
146 self
147 }
148
149 pub fn price_type(mut self, price_type: OrderType) -> Self {
151 self.price_type = price_type;
152 self
153 }
154
155 pub fn price(mut self, price: f64) -> Self {
157 self.price = Some(price);
158 self
159 }
160
161 pub fn trigger_price(mut self, trigger_price: f64) -> Self {
163 self.trigger_price = Some(trigger_price);
164 self
165 }
166
167 pub fn user_tag(mut self, user_tag: impl Into<String>) -> Self {
169 self.user_tag = user_tag.into();
170 self
171 }
172
173 pub fn duration(mut self, duration: TimeInForce) -> Self {
175 self.duration = duration;
176 self
177 }
178
179 pub fn trailing_stop(mut self, trailing_stop: TrailingStop) -> Self {
181 self.trailing_stop = Some(trailing_stop);
182 self
183 }
184
185 pub fn trailing_stop_by(self, trail_by_ticks: i32, trail_by_price_id: i32) -> Self {
187 self.trailing_stop(
188 TrailingStop::new()
189 .trail_by_ticks(trail_by_ticks)
190 .trail_by_price_id(trail_by_price_id),
191 )
192 }
193
194 pub fn trade_route(mut self, trade_route: impl Into<String>) -> Self {
196 self.trade_route = Some(trade_route.into());
197 self
198 }
199
200 pub fn manual_or_auto(mut self, manual_or_auto: ManualOrAutoEntry) -> Self {
202 self.manual_or_auto = manual_or_auto;
203 self
204 }
205
206 pub fn window_name(mut self, window_name: impl Into<String>) -> Self {
208 self.window_name = Some(window_name.into());
209 self
210 }
211
212 pub fn release_at_ssboe(mut self, ssboe: i32) -> Self {
214 self.release_at_ssboe = Some(ssboe);
215 self
216 }
217
218 pub fn release_at_usecs(mut self, usecs: i32) -> Self {
220 self.release_at_usecs = Some(usecs);
221 self
222 }
223
224 pub fn release_at(self, ssboe: i32, usecs: i32) -> Self {
226 self.release_at_ssboe(ssboe).release_at_usecs(usecs)
227 }
228
229 pub fn cancel_at_ssboe(mut self, ssboe: i32) -> Self {
231 self.cancel_at_ssboe = Some(ssboe);
232 self
233 }
234
235 pub fn cancel_at_usecs(mut self, usecs: i32) -> Self {
237 self.cancel_at_usecs = Some(usecs);
238 self
239 }
240
241 pub fn cancel_at(self, ssboe: i32, usecs: i32) -> Self {
243 self.cancel_at_ssboe(ssboe).cancel_at_usecs(usecs)
244 }
245
246 pub fn cancel_after_secs(mut self, secs: i32) -> Self {
248 self.cancel_after_secs = Some(secs);
249 self
250 }
251
252 pub fn if_touched(mut self, if_touched: RithmicIfTouchedTrigger) -> Self {
254 self.if_touched = Some(if_touched);
255 self
256 }
257
258 pub fn validate(&self) -> Result<(), RithmicError> {
266 validate_instrument(&self.symbol, &self.exchange, self.quantity)?;
267
268 super::require_prices(self.price_type, self.price, self.trigger_price)
269 }
270
271 pub fn build(self) -> Result<Self, RithmicError> {
273 self.validate()?;
274 Ok(self)
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 fn order() -> RithmicOrder {
283 RithmicOrder::new()
284 .symbol("ESH6")
285 .exchange("CME")
286 .quantity(1)
287 .transaction_type(OrderSide::Buy)
288 .price_type(OrderType::Limit)
289 }
290
291 #[test]
292 fn a_market_order_validates_without_a_price() {
293 let order = RithmicOrder {
294 price_type: OrderType::Market,
295 ..order()
296 };
297
298 assert!(order.validate().is_ok());
299 }
300
301 #[test]
302 fn a_limit_order_needs_a_price() {
303 let mut order = RithmicOrder {
304 price_type: OrderType::Limit,
305 ..order()
306 };
307
308 let err = order.validate().unwrap_err().to_string();
309 assert!(err.contains("price is required"), "{err}");
310
311 order.price = Some(5000.0);
312 assert!(order.validate().is_ok());
313 }
314
315 #[test]
316 fn a_stop_market_order_needs_a_trigger_but_no_price() {
317 let mut order = RithmicOrder {
318 price_type: OrderType::StopMarket,
319 ..order()
320 };
321
322 let err = order.validate().unwrap_err().to_string();
323 assert!(err.contains("trigger_price is required"), "{err}");
324
325 order.trigger_price = Some(4985.0);
326 assert!(order.validate().is_ok());
327 }
328
329 #[test]
330 fn a_stop_limit_order_needs_both() {
331 let mut order = RithmicOrder {
332 price_type: OrderType::StopLimit,
333 price: Some(4980.0),
334 ..order()
335 };
336
337 assert!(order.validate().is_err());
338
339 order.trigger_price = Some(4985.0);
340 assert!(order.validate().is_ok());
341 }
342
343 #[test]
346 fn the_error_names_the_order_type() {
347 let order = RithmicOrder {
348 price_type: OrderType::LimitIfTouched,
349 ..order()
350 };
351
352 let err = order.validate().unwrap_err().to_string();
353 assert!(err.contains("LIMIT_IF_TOUCHED"), "{err}");
354 }
355
356 #[test]
360 fn the_paired_setters_assign_their_arguments_in_order() {
361 let order = order()
362 .price(4980.0)
363 .trailing_stop_by(20, 1)
364 .release_at(35900, 500)
365 .cancel_at(36000, 250)
366 .build()
367 .unwrap();
368
369 let trailing = order.trailing_stop.unwrap();
370 assert_eq!(trailing.trail_by_ticks, 20);
371 assert_eq!(trailing.trail_by_price_id, 1);
372 assert_eq!(order.release_at_ssboe, Some(35900));
373 assert_eq!(order.release_at_usecs, Some(500));
374 assert_eq!(order.cancel_at_ssboe, Some(36000));
375 assert_eq!(order.cancel_at_usecs, Some(250));
376 }
377 #[test]
378 fn an_order_requires_its_identity() {
379 assert!(order().symbol("").price(5000.0).build().is_err());
380 assert!(order().exchange("").price(5000.0).build().is_err());
381 assert!(order().quantity(0).price(5000.0).build().is_err());
382 assert!(order().price(5000.0).build().is_ok());
383 }
384}