rithmic_rs/api/commands/
modify.rs1use super::triggers::RithmicIfTouchedTrigger;
4use super::validate_instrument;
5
6use crate::{
7 error::RithmicError,
8 types::{ManualOrAutoEntry, OrderType},
9};
10
11#[derive(Debug, Clone, Default, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[non_exhaustive]
33#[must_use = "a modification does nothing until passed to a plant handle"]
34pub struct RithmicModifyOrder {
35 pub id: String,
37 pub exchange: String,
39 pub symbol: String,
41 pub quantity: i32,
43 pub price: Option<f64>,
47 pub price_type: OrderType,
49 pub trigger_price: Option<f64>,
52 pub manual_or_auto: ManualOrAutoEntry,
54 pub window_name: Option<String>,
56 pub trail_by_ticks: Option<i32>,
61 pub if_touched: Option<RithmicIfTouchedTrigger>,
63}
64
65impl RithmicModifyOrder {
66 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn id(mut self, id: impl Into<String>) -> Self {
76 self.id = id.into();
77 self
78 }
79
80 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
82 self.symbol = symbol.into();
83 self
84 }
85
86 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
88 self.exchange = exchange.into();
89 self
90 }
91
92 pub fn quantity(mut self, quantity: i32) -> Self {
94 self.quantity = quantity;
95 self
96 }
97
98 pub fn price(mut self, price: f64) -> Self {
100 self.price = Some(price);
101 self
102 }
103
104 pub fn price_type(mut self, price_type: OrderType) -> Self {
106 self.price_type = price_type;
107 self
108 }
109
110 pub fn trigger_price(mut self, trigger_price: f64) -> Self {
113 self.trigger_price = Some(trigger_price);
114 self
115 }
116
117 pub fn manual_or_auto(mut self, manual_or_auto: ManualOrAutoEntry) -> Self {
119 self.manual_or_auto = manual_or_auto;
120 self
121 }
122
123 pub fn window_name(mut self, window_name: impl Into<String>) -> Self {
125 self.window_name = Some(window_name.into());
126 self
127 }
128
129 pub fn trail_by_ticks(mut self, trail_by_ticks: i32) -> Self {
131 self.trail_by_ticks = Some(trail_by_ticks);
132 self
133 }
134
135 pub fn if_touched(mut self, if_touched: RithmicIfTouchedTrigger) -> Self {
137 self.if_touched = Some(if_touched);
138 self
139 }
140
141 pub fn validate(&self) -> Result<(), RithmicError> {
147 if self.id.is_empty() {
148 return Err(RithmicError::InvalidArgument(
149 "a modify requires the basket_id of the order it restates".to_string(),
150 ));
151 }
152
153 validate_instrument(&self.symbol, &self.exchange, self.quantity)?;
154
155 let (needs_price, needs_trigger) = super::price_requirements(self.price_type);
156
157 let order_type = self.price_type.as_str_name();
158
159 if needs_price && self.price.is_none() {
160 return Err(RithmicError::InvalidArgument(format!(
161 "price is required for a {order_type} order"
162 )));
163 }
164
165 if needs_trigger && self.trigger_price.is_none() && self.price.is_none() {
166 return Err(RithmicError::InvalidArgument(format!(
167 "trigger_price, or a price to stand in for it, is required for a {order_type} order"
168 )));
169 }
170
171 Ok(())
172 }
173
174 pub fn build(self) -> Result<Self, RithmicError> {
177 self.validate()?;
178 Ok(self)
179 }
180}
181
182#[derive(Debug, Clone, Default, PartialEq)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
198#[non_exhaustive]
199#[must_use = "a command does nothing until passed to a plant handle"]
200pub struct RithmicModifyOrderReferenceData {
201 pub basket_id: String,
203 pub user_tag: String,
205}
206
207impl RithmicModifyOrderReferenceData {
208 pub fn new() -> Self {
210 Self::default()
211 }
212
213 pub fn basket_id(mut self, basket_id: impl Into<String>) -> Self {
215 self.basket_id = basket_id.into();
216 self
217 }
218
219 pub fn user_tag(mut self, user_tag: impl Into<String>) -> Self {
221 self.user_tag = user_tag.into();
222 self
223 }
224
225 pub fn validate(&self) -> Result<(), RithmicError> {
227 if self.basket_id.is_empty() {
228 return Err(RithmicError::InvalidArgument(
229 "a retag requires the basket_id of the order it retags".to_string(),
230 ));
231 }
232 Ok(())
233 }
234
235 pub fn build(self) -> Result<Self, RithmicError> {
237 self.validate()?;
238 Ok(self)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 fn modify(price_type: OrderType) -> RithmicModifyOrder {
247 RithmicModifyOrder::new()
248 .id("b")
249 .symbol("ESM6")
250 .exchange("CME")
251 .quantity(1)
252 .price_type(price_type)
253 }
254
255 #[test]
259 fn a_modify_requires_the_prices_its_type_needs() {
260 assert!(modify(OrderType::Market).build().is_ok());
261
262 assert!(modify(OrderType::Limit).build().is_err());
263 assert!(modify(OrderType::Limit).price(5000.0).build().is_ok());
264
265 assert!(modify(OrderType::StopMarket).build().is_err());
267 assert!(modify(OrderType::StopMarket).price(5000.0).build().is_ok());
268 assert!(
269 modify(OrderType::StopMarket)
270 .trigger_price(5000.0)
271 .build()
272 .is_ok()
273 );
274
275 assert!(
277 modify(OrderType::StopLimit)
278 .trigger_price(4999.0)
279 .build()
280 .is_err()
281 );
282 assert!(modify(OrderType::StopLimit).price(5000.0).build().is_ok());
283
284 assert!(modify(OrderType::MarketIfTouched).build().is_err());
285 assert!(
286 modify(OrderType::LimitIfTouched)
287 .price(5000.0)
288 .build()
289 .is_ok()
290 );
291 }
292 #[test]
293 fn a_modify_requires_the_basket_id_and_instrument() {
294 assert!(modify(OrderType::Market).id("").build().is_err());
295 assert!(modify(OrderType::Market).symbol("").build().is_err());
296 assert!(modify(OrderType::Market).quantity(0).build().is_err());
297 }
298
299 #[test]
300 fn a_retag_requires_the_basket_id_but_takes_an_empty_tag() {
301 assert!(RithmicModifyOrderReferenceData::new().build().is_err());
302 assert!(
303 RithmicModifyOrderReferenceData::new()
304 .basket_id("b")
305 .user_tag("")
306 .build()
307 .is_ok()
308 );
309 }
310}