Skip to main content

rithmic_rs/api/commands/
exit.rs

1//! Flattening a position.
2
3use crate::{error::RithmicError, types::ManualOrAutoEntry};
4
5/// Flatten the position in one instrument, or every position on the account.
6///
7/// The symbol and exchange come as a pair: set both to flatten one
8/// instrument, set neither to flatten the whole account.
9///
10/// # Example
11///
12/// ```
13/// use rithmic_rs::RithmicExitPosition;
14/// # fn main() -> Result<(), rithmic_rs::RithmicError> {
15/// let one = RithmicExitPosition::new()
16///     .symbol("ESM6")
17///     .exchange("CME")
18///     .build()?;
19/// let all = RithmicExitPosition::new().build()?;
20/// # Ok(())
21/// # }
22/// ```
23#[derive(Debug, Clone, Default, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[non_exhaustive]
26#[must_use = "a command does nothing until passed to a plant handle"]
27pub struct RithmicExitPosition {
28    /// Trading symbol (e.g., "ESM6"). Unset together with `exchange`, the
29    /// exit flattens every position on the account.
30    pub symbol: Option<String>,
31    /// Exchange code (e.g., "CME"). Comes as a pair with `symbol`.
32    pub exchange: Option<String>,
33    /// Whether the exit was made by a human or automatically.
34    pub manual_or_auto: ManualOrAutoEntry,
35    /// Originating window name reported to Rithmic.
36    pub window_name: Option<String>,
37    /// Name of the trading algorithm credited with the exit.
38    pub trading_algorithm: Option<String>,
39}
40
41impl RithmicExitPosition {
42    /// Start from the defaults.
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Instrument symbol of the position to exit.
48    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
49        self.symbol = Some(symbol.into());
50        self
51    }
52
53    /// Exchange the instrument trades on.
54    pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
55        self.exchange = Some(exchange.into());
56        self
57    }
58
59    /// Whether this was done by a human or automatically.
60    pub fn manual_or_auto(mut self, manual_or_auto: ManualOrAutoEntry) -> Self {
61        self.manual_or_auto = manual_or_auto;
62        self
63    }
64
65    /// Window name to report this exit under.
66    pub fn window_name(mut self, window_name: impl Into<String>) -> Self {
67        self.window_name = Some(window_name.into());
68        self
69    }
70
71    /// Trading algorithm to credit with this exit.
72    pub fn trading_algorithm(mut self, trading_algorithm: impl Into<String>) -> Self {
73        self.trading_algorithm = Some(trading_algorithm.into());
74        self
75    }
76
77    /// Requires the symbol and exchange together, or neither.
78    pub fn validate(&self) -> Result<(), RithmicError> {
79        match (&self.symbol, &self.exchange) {
80            (Some(symbol), _) if symbol.is_empty() => Err(RithmicError::InvalidArgument(
81                "the exit symbol must be non-empty; leave both unset to flatten the account"
82                    .to_string(),
83            )),
84            (_, Some(exchange)) if exchange.is_empty() => Err(RithmicError::InvalidArgument(
85                "the exit exchange must be non-empty; leave both unset to flatten the account"
86                    .to_string(),
87            )),
88            (Some(_), None) | (None, Some(_)) => Err(RithmicError::InvalidArgument(
89                "symbol and exchange come as a pair: set both to flatten one instrument, \
90                 neither to flatten the account"
91                    .to_string(),
92            )),
93            _ => Ok(()),
94        }
95    }
96
97    /// Requires the symbol and exchange together, or neither.
98    pub fn build(self) -> Result<Self, RithmicError> {
99        self.validate()?;
100        Ok(self)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn an_exit_takes_the_instrument_as_a_pair_or_not_at_all() {
110        // Neither set: flatten the whole account.
111        assert!(RithmicExitPosition::new().build().is_ok());
112
113        // One of the pair alone is refused, as is an empty member.
114        assert!(RithmicExitPosition::new().symbol("ESM6").build().is_err());
115        assert!(RithmicExitPosition::new().exchange("CME").build().is_err());
116        assert!(
117            RithmicExitPosition::new()
118                .symbol("")
119                .exchange("CME")
120                .build()
121                .is_err()
122        );
123        assert!(
124            RithmicExitPosition::new()
125                .symbol("ESM6")
126                .exchange("")
127                .build()
128                .is_err()
129        );
130
131        assert!(
132            RithmicExitPosition::new()
133                .symbol("ESM6")
134                .exchange("CME")
135                .build()
136                .is_ok()
137        );
138    }
139}