use super::triggers::RithmicIfTouchedTrigger;
use super::validate_instrument;
use crate::{
error::RithmicError,
types::{ManualOrAutoEntry, OrderType},
};
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[must_use = "a modification does nothing until passed to a plant handle"]
pub struct RithmicModifyOrder {
pub id: String,
pub exchange: String,
pub symbol: String,
pub quantity: i32,
pub price: Option<f64>,
pub price_type: OrderType,
pub trigger_price: Option<f64>,
pub manual_or_auto: ManualOrAutoEntry,
pub window_name: Option<String>,
pub trail_by_ticks: Option<i32>,
pub if_touched: Option<RithmicIfTouchedTrigger>,
}
impl RithmicModifyOrder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = id.into();
self
}
pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
self.symbol = symbol.into();
self
}
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = exchange.into();
self
}
pub fn quantity(mut self, quantity: i32) -> Self {
self.quantity = quantity;
self
}
pub fn price(mut self, price: f64) -> Self {
self.price = Some(price);
self
}
pub fn price_type(mut self, price_type: OrderType) -> Self {
self.price_type = price_type;
self
}
pub fn trigger_price(mut self, trigger_price: f64) -> Self {
self.trigger_price = Some(trigger_price);
self
}
pub fn manual_or_auto(mut self, manual_or_auto: ManualOrAutoEntry) -> Self {
self.manual_or_auto = manual_or_auto;
self
}
pub fn window_name(mut self, window_name: impl Into<String>) -> Self {
self.window_name = Some(window_name.into());
self
}
pub fn trail_by_ticks(mut self, trail_by_ticks: i32) -> Self {
self.trail_by_ticks = Some(trail_by_ticks);
self
}
pub fn if_touched(mut self, if_touched: RithmicIfTouchedTrigger) -> Self {
self.if_touched = Some(if_touched);
self
}
pub fn validate(&self) -> Result<(), RithmicError> {
if self.id.is_empty() {
return Err(RithmicError::InvalidArgument(
"a modify requires the basket_id of the order it restates".to_string(),
));
}
validate_instrument(&self.symbol, &self.exchange, self.quantity)?;
let (needs_price, needs_trigger) = super::price_requirements(self.price_type);
let order_type = self.price_type.as_str_name();
if needs_price && self.price.is_none() {
return Err(RithmicError::InvalidArgument(format!(
"price is required for a {order_type} order"
)));
}
if needs_trigger && self.trigger_price.is_none() && self.price.is_none() {
return Err(RithmicError::InvalidArgument(format!(
"trigger_price, or a price to stand in for it, is required for a {order_type} order"
)));
}
Ok(())
}
pub fn build(self) -> Result<Self, RithmicError> {
self.validate()?;
Ok(self)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[must_use = "a command does nothing until passed to a plant handle"]
pub struct RithmicModifyOrderReferenceData {
pub basket_id: String,
pub user_tag: String,
}
impl RithmicModifyOrderReferenceData {
pub fn new() -> Self {
Self::default()
}
pub fn basket_id(mut self, basket_id: impl Into<String>) -> Self {
self.basket_id = basket_id.into();
self
}
pub fn user_tag(mut self, user_tag: impl Into<String>) -> Self {
self.user_tag = user_tag.into();
self
}
pub fn validate(&self) -> Result<(), RithmicError> {
if self.basket_id.is_empty() {
return Err(RithmicError::InvalidArgument(
"a retag requires the basket_id of the order it retags".to_string(),
));
}
Ok(())
}
pub fn build(self) -> Result<Self, RithmicError> {
self.validate()?;
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn modify(price_type: OrderType) -> RithmicModifyOrder {
RithmicModifyOrder::new()
.id("b")
.symbol("ESM6")
.exchange("CME")
.quantity(1)
.price_type(price_type)
}
#[test]
fn a_modify_requires_the_prices_its_type_needs() {
assert!(modify(OrderType::Market).build().is_ok());
assert!(modify(OrderType::Limit).build().is_err());
assert!(modify(OrderType::Limit).price(5000.0).build().is_ok());
assert!(modify(OrderType::StopMarket).build().is_err());
assert!(modify(OrderType::StopMarket).price(5000.0).build().is_ok());
assert!(
modify(OrderType::StopMarket)
.trigger_price(5000.0)
.build()
.is_ok()
);
assert!(
modify(OrderType::StopLimit)
.trigger_price(4999.0)
.build()
.is_err()
);
assert!(modify(OrderType::StopLimit).price(5000.0).build().is_ok());
assert!(modify(OrderType::MarketIfTouched).build().is_err());
assert!(
modify(OrderType::LimitIfTouched)
.price(5000.0)
.build()
.is_ok()
);
}
#[test]
fn a_modify_requires_the_basket_id_and_instrument() {
assert!(modify(OrderType::Market).id("").build().is_err());
assert!(modify(OrderType::Market).symbol("").build().is_err());
assert!(modify(OrderType::Market).quantity(0).build().is_err());
}
#[test]
fn a_retag_requires_the_basket_id_but_takes_an_empty_tag() {
assert!(RithmicModifyOrderReferenceData::new().build().is_err());
assert!(
RithmicModifyOrderReferenceData::new()
.basket_id("b")
.user_tag("")
.build()
.is_ok()
);
}
}