Skip to main content

sandbox_quant/
strategy_catalog.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4pub struct StrategyProfile {
5    pub label: String,
6    pub source_tag: String,
7    pub fast_period: usize,
8    pub slow_period: usize,
9    pub min_ticks_between_signals: u64,
10}
11
12impl StrategyProfile {
13    pub fn periods_tuple(&self) -> (usize, usize, u64) {
14        (
15            self.fast_period,
16            self.slow_period,
17            self.min_ticks_between_signals,
18        )
19    }
20}
21
22#[derive(Debug, Clone)]
23pub struct StrategyCatalog {
24    profiles: Vec<StrategyProfile>,
25    next_custom_id: u32,
26}
27
28impl StrategyCatalog {
29    pub fn new(config_fast: usize, config_slow: usize, min_ticks_between_signals: u64) -> Self {
30        Self {
31            profiles: vec![
32                StrategyProfile {
33                    label: "MA(Config)".to_string(),
34                    source_tag: "cfg".to_string(),
35                    fast_period: config_fast,
36                    slow_period: config_slow,
37                    min_ticks_between_signals,
38                },
39                StrategyProfile {
40                    label: "MA(Fast 5/20)".to_string(),
41                    source_tag: "fst".to_string(),
42                    fast_period: 5,
43                    slow_period: 20,
44                    min_ticks_between_signals,
45                },
46                StrategyProfile {
47                    label: "MA(Slow 20/60)".to_string(),
48                    source_tag: "slw".to_string(),
49                    fast_period: 20,
50                    slow_period: 60,
51                    min_ticks_between_signals,
52                },
53            ],
54            next_custom_id: 1,
55        }
56    }
57
58    pub fn labels(&self) -> Vec<String> {
59        self.profiles.iter().map(|p| p.label.clone()).collect()
60    }
61
62    pub fn len(&self) -> usize {
63        self.profiles.len()
64    }
65
66    pub fn get(&self, index: usize) -> Option<&StrategyProfile> {
67        self.profiles.get(index)
68    }
69
70    pub fn get_by_source_tag(&self, source_tag: &str) -> Option<&StrategyProfile> {
71        self.profiles.iter().find(|p| p.source_tag == source_tag)
72    }
73
74    pub fn profiles(&self) -> &[StrategyProfile] {
75        &self.profiles
76    }
77
78    pub fn index_of_label(&self, label: &str) -> Option<usize> {
79        self.profiles.iter().position(|p| p.label == label)
80    }
81
82    pub fn from_profiles(
83        profiles: Vec<StrategyProfile>,
84        config_fast: usize,
85        config_slow: usize,
86        min_ticks_between_signals: u64,
87    ) -> Self {
88        if profiles.is_empty() {
89            return Self::new(config_fast, config_slow, min_ticks_between_signals);
90        }
91        let next_custom_id = profiles
92            .iter()
93            .filter_map(|profile| {
94                let tag = profile.source_tag.strip_prefix('c')?;
95                tag.parse::<u32>().ok()
96            })
97            .max()
98            .map(|id| id + 1)
99            .unwrap_or(1);
100        Self {
101            profiles,
102            next_custom_id,
103        }
104    }
105
106    pub fn add_custom_from_index(&mut self, base_index: usize) -> StrategyProfile {
107        let base = self
108            .profiles
109            .get(base_index)
110            .cloned()
111            .unwrap_or_else(|| self.profiles[0].clone());
112        let tag = format!("c{:02}", self.next_custom_id);
113        self.next_custom_id += 1;
114        let fast = base.fast_period.max(2);
115        let slow = base.slow_period.max(fast + 1);
116        let profile = StrategyProfile {
117            label: format!("MA(Custom {}/{}) [{}]", fast, slow, tag),
118            source_tag: tag,
119            fast_period: fast,
120            slow_period: slow,
121            min_ticks_between_signals: base.min_ticks_between_signals,
122        };
123        self.profiles.push(profile.clone());
124        profile
125    }
126
127    pub fn update_profile(
128        &mut self,
129        index: usize,
130        fast_period: usize,
131        slow_period: usize,
132        min_ticks_between_signals: u64,
133    ) -> Option<StrategyProfile> {
134        let profile = self.profiles.get_mut(index)?;
135        let fast = fast_period.max(2);
136        let slow = slow_period.max(fast + 1);
137        profile.fast_period = fast;
138        profile.slow_period = slow;
139        profile.min_ticks_between_signals = min_ticks_between_signals.max(1);
140        if profile.source_tag.starts_with('c') {
141            profile.label = format!(
142                "MA(Custom {}/{}) [{}]",
143                profile.fast_period, profile.slow_period, profile.source_tag
144            );
145        } else if profile.source_tag == "fst" {
146            profile.label = format!("MA(Fast {}/{})", profile.fast_period, profile.slow_period);
147        } else if profile.source_tag == "slw" {
148            profile.label = format!("MA(Slow {}/{})", profile.fast_period, profile.slow_period);
149        } else if profile.source_tag == "cfg" {
150            profile.label = format!("MA(Config {}/{})", profile.fast_period, profile.slow_period);
151        }
152        Some(profile.clone())
153    }
154
155}