1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6use crate::error::IndicatorError;
7
8pub type TripleVec = (Vec<f64>, Vec<f64>, Vec<f64>);
10pub type MacdResult = Result<TripleVec, IndicatorError>;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Candle {
17 pub time: i64,
19 pub open: f64,
20 pub high: f64,
21 pub low: f64,
22 pub close: f64,
23 pub volume: f64,
24}
25
26impl Candle {
27 pub fn from_raw(row: &[serde_json::Value]) -> Option<Self> {
30 Some(Self {
31 time: row.first()?.as_str()?.parse().ok()?,
32 open: row.get(1)?.as_str()?.parse().ok()?,
33 high: row.get(2)?.as_str()?.parse().ok()?,
34 low: row.get(3)?.as_str()?.parse().ok()?,
35 close: row.get(4)?.as_str()?.parse().ok()?,
36 volume: row.get(5)?.as_str()?.parse().ok()?,
37 })
38 }
39
40 pub fn typical_price(&self) -> f64 {
42 (self.high + self.low + self.close) / 3.0
43 }
44
45 pub fn hl2(&self) -> f64 {
47 (self.high + self.low) / 2.0
48 }
49
50 pub fn true_range(&self, prev_close: Option<f64>) -> f64 {
52 let hl = self.high - self.low;
53 match prev_close {
54 Some(pc) => hl.max((self.high - pc).abs()).max((self.low - pc).abs()),
55 None => hl,
56 }
57 }
58}
59
60#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum MarketRegime {
65 Trending(TrendDirection),
67 MeanReverting,
69 Volatile,
71 #[default]
73 Uncertain,
74}
75
76impl MarketRegime {
77 pub fn is_tradeable(&self) -> bool {
78 matches!(
79 self,
80 MarketRegime::Trending(_) | MarketRegime::MeanReverting
81 )
82 }
83
84 pub fn size_multiplier(&self) -> f64 {
85 match self {
86 MarketRegime::Trending(_) => 1.0,
87 MarketRegime::MeanReverting => 0.8,
88 MarketRegime::Volatile => 0.3,
89 MarketRegime::Uncertain => 0.0,
90 }
91 }
92
93 pub fn recommended_strategy(&self) -> RecommendedStrategy {
94 RecommendedStrategy::from(self)
95 }
96}
97
98impl fmt::Display for MarketRegime {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 MarketRegime::Trending(TrendDirection::Bullish) => write!(f, "Trending (Bullish)"),
102 MarketRegime::Trending(TrendDirection::Bearish) => write!(f, "Trending (Bearish)"),
103 MarketRegime::MeanReverting => write!(f, "Mean-Reverting"),
104 MarketRegime::Volatile => write!(f, "Volatile/Choppy"),
105 MarketRegime::Uncertain => write!(f, "Uncertain"),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub enum TrendDirection {
114 Bullish,
115 Bearish,
116}
117
118impl fmt::Display for TrendDirection {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 TrendDirection::Bullish => write!(f, "Bullish"),
122 TrendDirection::Bearish => write!(f, "Bearish"),
123 }
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
130pub struct RegimeConfidence {
131 pub regime: MarketRegime,
132 pub confidence: f64,
133 pub adx_value: f64,
134 pub bb_width_percentile: f64,
135 pub trend_strength: f64,
136}
137
138impl RegimeConfidence {
139 pub fn new(regime: MarketRegime, confidence: f64) -> Self {
140 Self {
141 regime,
142 confidence: confidence.clamp(0.0, 1.0),
143 adx_value: 0.0,
144 bb_width_percentile: 0.0,
145 trend_strength: 0.0,
146 }
147 }
148
149 pub fn with_metrics(
150 regime: MarketRegime,
151 confidence: f64,
152 adx: f64,
153 bb_width: f64,
154 trend_strength: f64,
155 ) -> Self {
156 Self {
157 regime,
158 confidence: confidence.clamp(0.0, 1.0),
159 adx_value: adx,
160 bb_width_percentile: bb_width,
161 trend_strength,
162 }
163 }
164
165 pub fn is_actionable(&self) -> bool {
166 self.confidence >= 0.6
167 }
168 pub fn is_strong(&self) -> bool {
169 self.confidence >= 0.75
170 }
171}
172
173impl Default for RegimeConfidence {
174 fn default() -> Self {
175 Self::new(MarketRegime::Uncertain, 0.0)
176 }
177}
178
179impl fmt::Display for RegimeConfidence {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(
182 f,
183 "{} (conf: {:.0}%, ADX: {:.1}, BB%: {:.0}, trend: {:.2})",
184 self.regime,
185 self.confidence * 100.0,
186 self.adx_value,
187 self.bb_width_percentile,
188 self.trend_strength,
189 )
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
196pub enum RecommendedStrategy {
197 TrendFollowing,
198 MeanReversion,
199 ReducedExposure,
200 StayCash,
201}
202
203impl From<&MarketRegime> for RecommendedStrategy {
204 fn from(regime: &MarketRegime) -> Self {
205 match regime {
206 MarketRegime::Trending(_) => RecommendedStrategy::TrendFollowing,
207 MarketRegime::MeanReverting => RecommendedStrategy::MeanReversion,
208 MarketRegime::Volatile => RecommendedStrategy::ReducedExposure,
209 MarketRegime::Uncertain => RecommendedStrategy::StayCash,
210 }
211 }
212}
213
214impl fmt::Display for RecommendedStrategy {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 RecommendedStrategy::TrendFollowing => write!(f, "Trend Following"),
218 RecommendedStrategy::MeanReversion => write!(f, "Mean Reversion"),
219 RecommendedStrategy::ReducedExposure => write!(f, "Reduced Exposure"),
220 RecommendedStrategy::StayCash => write!(f, "Stay Cash"),
221 }
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct RegimeConfig {
229 pub adx_period: usize,
230 pub adx_trending_threshold: f64,
231 pub adx_ranging_threshold: f64,
232 pub bb_period: usize,
233 pub bb_std_dev: f64,
234 pub bb_width_volatility_threshold: f64,
235 pub ema_short_period: usize,
236 pub ema_long_period: usize,
237 pub atr_period: usize,
238 pub atr_expansion_threshold: f64,
239 pub regime_stability_bars: usize,
240 pub min_regime_duration: usize,
241}
242
243impl Default for RegimeConfig {
244 fn default() -> Self {
245 Self {
246 adx_period: 14,
247 adx_trending_threshold: 25.0,
248 adx_ranging_threshold: 20.0,
249 bb_period: 20,
250 bb_std_dev: 2.0,
251 bb_width_volatility_threshold: 75.0,
252 ema_short_period: 50,
253 ema_long_period: 200,
254 atr_period: 14,
255 atr_expansion_threshold: 1.5,
256 regime_stability_bars: 3,
257 min_regime_duration: 5,
258 }
259 }
260}
261
262impl RegimeConfig {
263 pub fn crypto_optimized() -> Self {
264 Self {
265 adx_trending_threshold: 20.0,
266 adx_ranging_threshold: 15.0,
267 bb_width_volatility_threshold: 70.0,
268 ema_short_period: 21,
269 ema_long_period: 50,
270 atr_expansion_threshold: 1.3,
271 regime_stability_bars: 2,
272 min_regime_duration: 3,
273 ..Default::default()
274 }
275 }
276
277 pub fn conservative() -> Self {
278 Self {
279 adx_trending_threshold: 30.0,
280 adx_ranging_threshold: 18.0,
281 bb_width_volatility_threshold: 80.0,
282 atr_expansion_threshold: 2.0,
283 regime_stability_bars: 5,
284 min_regime_duration: 10,
285 ..Default::default()
286 }
287 }
288}