1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::model::MarketRegime;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[cfg_attr(
8 feature = "serde",
9 derive(Serialize, Deserialize),
10 serde(rename_all = "snake_case")
11)]
12pub enum VolatilityRegime {
13 High,
14 Normal,
15 Low,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(
20 feature = "serde",
21 derive(Serialize, Deserialize),
22 serde(rename_all = "snake_case")
23)]
24pub enum OpeningType {
25 InsideValue,
26 OutsideValue,
27 Unknown,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[cfg_attr(
35 feature = "serde",
36 derive(Serialize, Deserialize),
37 serde(rename_all = "snake_case")
38)]
39pub enum PlaybookState {
40 Preferred,
41 Neutral,
42 Suppressed,
43 NotAllowed,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
51#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52pub struct MarketStateOutput {
53 pub regime: MarketRegime,
54 pub volatility_regime: VolatilityRegime,
55 pub trend_stability: f64,
57 pub balance_probability: f64,
60 pub opening_range_percentile: f64,
64 pub opening_type: OpeningType,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub struct ExpectedPlaybook {
70 pub continuation_long: PlaybookState,
71 pub continuation_short: PlaybookState,
72 pub reversal_long: PlaybookState,
73 pub reversal_short: PlaybookState,
74}
75
76pub fn derive_playbook(regime: MarketRegime, trend_stability: f64) -> ExpectedPlaybook {
80 let strong = trend_stability >= 0.5;
81 match regime {
82 MarketRegime::BullishExpansion => ExpectedPlaybook {
83 continuation_long: if strong {
84 PlaybookState::Preferred
85 } else {
86 PlaybookState::Neutral
87 },
88 continuation_short: PlaybookState::NotAllowed,
89 reversal_long: PlaybookState::NotAllowed,
90 reversal_short: PlaybookState::Suppressed,
91 },
92 MarketRegime::BearishExpansion => ExpectedPlaybook {
93 continuation_long: PlaybookState::NotAllowed,
94 continuation_short: if strong {
95 PlaybookState::Preferred
96 } else {
97 PlaybookState::Neutral
98 },
99 reversal_long: PlaybookState::Suppressed,
100 reversal_short: PlaybookState::NotAllowed,
101 },
102 MarketRegime::Consolidation => ExpectedPlaybook {
103 continuation_long: PlaybookState::NotAllowed,
104 continuation_short: PlaybookState::NotAllowed,
105 reversal_long: PlaybookState::Preferred,
106 reversal_short: PlaybookState::Preferred,
107 },
108 MarketRegime::Transition => ExpectedPlaybook {
112 continuation_long: PlaybookState::Suppressed,
113 continuation_short: PlaybookState::Suppressed,
114 reversal_long: PlaybookState::Neutral,
115 reversal_short: PlaybookState::Neutral,
116 },
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn strong_bull_regime_prefers_continuation_long_only() {
126 let pb = derive_playbook(MarketRegime::BullishExpansion, 0.8);
127 assert_eq!(pb.continuation_long, PlaybookState::Preferred);
128 assert_eq!(pb.continuation_short, PlaybookState::NotAllowed);
129 assert_eq!(pb.reversal_long, PlaybookState::NotAllowed);
130 }
131
132 #[test]
133 fn balance_regime_prefers_both_reversal_sides() {
134 let pb = derive_playbook(MarketRegime::Consolidation, 0.9);
135 assert_eq!(pb.reversal_long, PlaybookState::Preferred);
136 assert_eq!(pb.reversal_short, PlaybookState::Preferred);
137 assert_eq!(pb.continuation_long, PlaybookState::NotAllowed);
138 }
139
140 #[test]
141 fn transition_regime_never_prefers_anything() {
142 let pb = derive_playbook(MarketRegime::Transition, 1.0);
143 assert_ne!(pb.continuation_long, PlaybookState::Preferred);
144 assert_ne!(pb.continuation_short, PlaybookState::Preferred);
145 assert_ne!(pb.reversal_long, PlaybookState::Preferred);
146 assert_ne!(pb.reversal_short, PlaybookState::Preferred);
147 }
148}