sandbox_quant/
strategy_catalog.rs1use 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}