Skip to main content

trader_bot/strategy/
settings.rs

1//! Trading settings for configuring strategy behavior.
2
3use chrono::NaiveTime;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6
7/// Configuration settings for trading strategies.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct TradingSettings {
10    /// The instrument to trade.
11    pub instrument: String,
12
13    /// Minimum profit in price steps before considering a sell.
14    pub minimum_profit_steps: u32,
15
16    /// Minimum market order size (in lots) to consider buying.
17    pub minimum_market_order_size_to_buy: u64,
18
19    /// Minimum market order size (in lots) to consider selling.
20    pub minimum_market_order_size_to_sell: u64,
21
22    /// Minimum market order size to change buy price.
23    pub minimum_market_order_size_to_change_buy_price: u64,
24
25    /// Minimum market order size to change sell price.
26    pub minimum_market_order_size_to_change_sell_price: u64,
27
28    /// Order book depth to analyze.
29    pub market_order_book_depth: usize,
30
31    /// Maximum number of lots to hold.
32    pub max_position_lots: u64,
33
34    /// Lot size for orders.
35    pub lot_size: u64,
36
37    /// Earliest time to start buying.
38    pub minimum_time_to_buy: Option<NaiveTime>,
39
40    /// Latest time to buy.
41    pub maximum_time_to_buy: Option<NaiveTime>,
42
43    /// Delta for early sell (sell if price drops by this many steps).
44    pub early_sell_owned_lots_delta: Option<u32>,
45
46    /// Multiplier for early sell.
47    pub early_sell_owned_lots_multiplier: Option<Decimal>,
48
49    /// Price step for the instrument (tick size).
50    pub price_step: Decimal,
51
52    /// Whether trading is enabled.
53    pub enabled: bool,
54}
55
56impl Default for TradingSettings {
57    fn default() -> Self {
58        Self {
59            instrument: String::new(),
60            minimum_profit_steps: 1,
61            minimum_market_order_size_to_buy: 10,
62            minimum_market_order_size_to_sell: 10,
63            minimum_market_order_size_to_change_buy_price: 50,
64            minimum_market_order_size_to_change_sell_price: 50,
65            market_order_book_depth: 5,
66            max_position_lots: 100,
67            lot_size: 1,
68            minimum_time_to_buy: None,
69            maximum_time_to_buy: None,
70            early_sell_owned_lots_delta: None,
71            early_sell_owned_lots_multiplier: None,
72            price_step: Decimal::new(1, 2), // 0.01
73            enabled: true,
74        }
75    }
76}
77
78impl TradingSettings {
79    /// Creates new trading settings for an instrument.
80    #[must_use]
81    pub fn new(instrument: impl Into<String>) -> Self {
82        Self {
83            instrument: instrument.into(),
84            ..Default::default()
85        }
86    }
87
88    /// Builder method to set minimum profit steps.
89    #[must_use]
90    pub fn with_minimum_profit_steps(mut self, steps: u32) -> Self {
91        self.minimum_profit_steps = steps;
92        self
93    }
94
95    /// Builder method to set price step.
96    #[must_use]
97    pub fn with_price_step(mut self, step: Decimal) -> Self {
98        self.price_step = step;
99        self
100    }
101
102    /// Builder method to set lot size.
103    #[must_use]
104    pub fn with_lot_size(mut self, size: u64) -> Self {
105        self.lot_size = size;
106        self
107    }
108
109    /// Builder method to set max position.
110    #[must_use]
111    pub fn with_max_position(mut self, lots: u64) -> Self {
112        self.max_position_lots = lots;
113        self
114    }
115
116    /// Builder method to set order book depth.
117    #[must_use]
118    pub fn with_order_book_depth(mut self, depth: usize) -> Self {
119        self.market_order_book_depth = depth;
120        self
121    }
122
123    /// Builder method to set minimum market order size to buy.
124    #[must_use]
125    pub fn with_min_order_size_to_buy(mut self, size: u64) -> Self {
126        self.minimum_market_order_size_to_buy = size;
127        self
128    }
129
130    /// Builder method to set minimum market order size to sell.
131    #[must_use]
132    pub fn with_min_order_size_to_sell(mut self, size: u64) -> Self {
133        self.minimum_market_order_size_to_sell = size;
134        self
135    }
136
137    /// Builder method to set trading time window.
138    #[must_use]
139    pub fn with_trading_hours(mut self, start: NaiveTime, end: NaiveTime) -> Self {
140        self.minimum_time_to_buy = Some(start);
141        self.maximum_time_to_buy = Some(end);
142        self
143    }
144
145    /// Builder method to enable/disable trading.
146    #[must_use]
147    pub fn with_enabled(mut self, enabled: bool) -> Self {
148        self.enabled = enabled;
149        self
150    }
151
152    /// Returns the minimum profit amount.
153    #[must_use]
154    pub fn minimum_profit(&self) -> Decimal {
155        self.price_step * Decimal::from(self.minimum_profit_steps)
156    }
157
158    /// Checks if trading is allowed at the given time.
159    #[must_use]
160    pub fn is_trading_time(&self, time: NaiveTime) -> bool {
161        match (self.minimum_time_to_buy, self.maximum_time_to_buy) {
162            (Some(start), Some(end)) => time >= start && time <= end,
163            (Some(start), None) => time >= start,
164            (None, Some(end)) => time <= end,
165            (None, None) => true,
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_default_settings() {
176        let settings = TradingSettings::default();
177        assert_eq!(settings.minimum_profit_steps, 1);
178        assert!(settings.enabled);
179    }
180
181    #[test]
182    fn test_builder_pattern() {
183        let settings = TradingSettings::new("TEST")
184            .with_minimum_profit_steps(2)
185            .with_price_step(Decimal::new(1, 1))
186            .with_max_position(50);
187
188        assert_eq!(settings.instrument, "TEST");
189        assert_eq!(settings.minimum_profit_steps, 2);
190        assert_eq!(settings.price_step, Decimal::new(1, 1));
191        assert_eq!(settings.max_position_lots, 50);
192    }
193
194    #[test]
195    fn test_minimum_profit() {
196        let settings = TradingSettings::new("TEST")
197            .with_minimum_profit_steps(3)
198            .with_price_step(Decimal::new(1, 2)); // 0.01
199
200        assert_eq!(settings.minimum_profit(), Decimal::new(3, 2)); // 0.03
201    }
202
203    #[test]
204    fn test_trading_time() {
205        let settings = TradingSettings::new("TEST").with_trading_hours(
206            NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
207            NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
208        );
209
210        assert!(settings.is_trading_time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()));
211        assert!(!settings.is_trading_time(NaiveTime::from_hms_opt(8, 0, 0).unwrap()));
212        assert!(!settings.is_trading_time(NaiveTime::from_hms_opt(18, 0, 0).unwrap()));
213    }
214
215    #[test]
216    fn test_no_trading_time_restrictions() {
217        let settings = TradingSettings::new("TEST");
218        assert!(settings.is_trading_time(NaiveTime::from_hms_opt(3, 0, 0).unwrap()));
219        assert!(settings.is_trading_time(NaiveTime::from_hms_opt(23, 0, 0).unwrap()));
220    }
221}