use chrono::NaiveTime;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingSettings {
pub instrument: String,
pub minimum_profit_steps: u32,
pub minimum_market_order_size_to_buy: u64,
pub minimum_market_order_size_to_sell: u64,
pub minimum_market_order_size_to_change_buy_price: u64,
pub minimum_market_order_size_to_change_sell_price: u64,
pub market_order_book_depth: usize,
pub max_position_lots: u64,
pub lot_size: u64,
pub minimum_time_to_buy: Option<NaiveTime>,
pub maximum_time_to_buy: Option<NaiveTime>,
pub early_sell_owned_lots_delta: Option<u32>,
pub early_sell_owned_lots_multiplier: Option<Decimal>,
pub price_step: Decimal,
pub enabled: bool,
}
impl Default for TradingSettings {
fn default() -> Self {
Self {
instrument: String::new(),
minimum_profit_steps: 1,
minimum_market_order_size_to_buy: 10,
minimum_market_order_size_to_sell: 10,
minimum_market_order_size_to_change_buy_price: 50,
minimum_market_order_size_to_change_sell_price: 50,
market_order_book_depth: 5,
max_position_lots: 100,
lot_size: 1,
minimum_time_to_buy: None,
maximum_time_to_buy: None,
early_sell_owned_lots_delta: None,
early_sell_owned_lots_multiplier: None,
price_step: Decimal::new(1, 2), enabled: true,
}
}
}
impl TradingSettings {
#[must_use]
pub fn new(instrument: impl Into<String>) -> Self {
Self {
instrument: instrument.into(),
..Default::default()
}
}
#[must_use]
pub fn with_minimum_profit_steps(mut self, steps: u32) -> Self {
self.minimum_profit_steps = steps;
self
}
#[must_use]
pub fn with_price_step(mut self, step: Decimal) -> Self {
self.price_step = step;
self
}
#[must_use]
pub fn with_lot_size(mut self, size: u64) -> Self {
self.lot_size = size;
self
}
#[must_use]
pub fn with_max_position(mut self, lots: u64) -> Self {
self.max_position_lots = lots;
self
}
#[must_use]
pub fn with_order_book_depth(mut self, depth: usize) -> Self {
self.market_order_book_depth = depth;
self
}
#[must_use]
pub fn with_min_order_size_to_buy(mut self, size: u64) -> Self {
self.minimum_market_order_size_to_buy = size;
self
}
#[must_use]
pub fn with_min_order_size_to_sell(mut self, size: u64) -> Self {
self.minimum_market_order_size_to_sell = size;
self
}
#[must_use]
pub fn with_trading_hours(mut self, start: NaiveTime, end: NaiveTime) -> Self {
self.minimum_time_to_buy = Some(start);
self.maximum_time_to_buy = Some(end);
self
}
#[must_use]
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn minimum_profit(&self) -> Decimal {
self.price_step * Decimal::from(self.minimum_profit_steps)
}
#[must_use]
pub fn is_trading_time(&self, time: NaiveTime) -> bool {
match (self.minimum_time_to_buy, self.maximum_time_to_buy) {
(Some(start), Some(end)) => time >= start && time <= end,
(Some(start), None) => time >= start,
(None, Some(end)) => time <= end,
(None, None) => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_settings() {
let settings = TradingSettings::default();
assert_eq!(settings.minimum_profit_steps, 1);
assert!(settings.enabled);
}
#[test]
fn test_builder_pattern() {
let settings = TradingSettings::new("TEST")
.with_minimum_profit_steps(2)
.with_price_step(Decimal::new(1, 1))
.with_max_position(50);
assert_eq!(settings.instrument, "TEST");
assert_eq!(settings.minimum_profit_steps, 2);
assert_eq!(settings.price_step, Decimal::new(1, 1));
assert_eq!(settings.max_position_lots, 50);
}
#[test]
fn test_minimum_profit() {
let settings = TradingSettings::new("TEST")
.with_minimum_profit_steps(3)
.with_price_step(Decimal::new(1, 2));
assert_eq!(settings.minimum_profit(), Decimal::new(3, 2)); }
#[test]
fn test_trading_time() {
let settings = TradingSettings::new("TEST").with_trading_hours(
NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
);
assert!(settings.is_trading_time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()));
assert!(!settings.is_trading_time(NaiveTime::from_hms_opt(8, 0, 0).unwrap()));
assert!(!settings.is_trading_time(NaiveTime::from_hms_opt(18, 0, 0).unwrap()));
}
#[test]
fn test_no_trading_time_restrictions() {
let settings = TradingSettings::new("TEST");
assert!(settings.is_trading_time(NaiveTime::from_hms_opt(3, 0, 0).unwrap()));
assert!(settings.is_trading_time(NaiveTime::from_hms_opt(23, 0, 0).unwrap()));
}
}