Skip to main content

kestrel_chartkit/
signal.rs

1use std::fmt;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use crate::engine::market_state::PlaybookState;
7use crate::model::{MarketRegime, RiskPlan, SupportResistanceZone};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
10#[cfg_attr(
11    feature = "serde",
12    derive(Serialize, Deserialize),
13    serde(rename_all = "lowercase")
14)]
15pub enum SignalDirection {
16    Bullish,
17    #[default]
18    Neutral,
19    Bearish,
20}
21
22impl fmt::Display for SignalDirection {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            SignalDirection::Bullish => write!(f, "BULLISH"),
26            SignalDirection::Neutral => write!(f, "NEUTRAL"),
27            SignalDirection::Bearish => write!(f, "BEARISH"),
28        }
29    }
30}
31
32/// Konkreter Exekutions-Trigger ("Geh rein" / "Raus").
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
34#[cfg_attr(
35    feature = "serde",
36    derive(Serialize, Deserialize),
37    serde(rename_all = "snake_case")
38)]
39pub enum TriggerAction {
40    Buy,
41    Sell,
42    Exit,
43    #[default]
44    Hold,
45}
46
47impl fmt::Display for TriggerAction {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            TriggerAction::Buy => write!(f, "BUY"),
51            TriggerAction::Sell => write!(f, "SELL"),
52            TriggerAction::Exit => write!(f, "EXIT"),
53            TriggerAction::Hold => write!(f, "HOLD"),
54        }
55    }
56}
57
58/// Rechnerische Zielzone ("wie weit?").
59#[derive(Debug, Clone, PartialEq)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct TargetZone {
62    pub price: f64,
63    pub name: String,
64    pub reward_atr: f64,
65}
66
67/// Strukturelles Ungültigkeits-Level / Stop-Loss ("wo ungültig?").
68#[derive(Debug, Clone, PartialEq)]
69#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
70pub struct InvalidationZone {
71    pub price: f64,
72    pub reason: String,
73    pub risk_atr: f64,
74}
75
76/// Zeitliche / Bar-basierte Gültigkeit des Signal-Kontexts ("wie lange?").
77#[derive(Debug, Clone, Copy, PartialEq)]
78#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
79pub struct SetupDuration {
80    pub max_bars: u32,
81    pub elapsed_bars: u32,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
85#[cfg_attr(
86    feature = "serde",
87    derive(Serialize, Deserialize),
88    serde(rename_all = "snake_case")
89)]
90pub enum PermissionGrade {
91    ClearToTrade,
92    Caution,
93    #[default]
94    Veto,
95}
96
97impl fmt::Display for PermissionGrade {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            PermissionGrade::ClearToTrade => write!(f, "CLEAR TO TRADE"),
101            PermissionGrade::Caution => write!(f, "CAUTION / PROTECT"),
102            PermissionGrade::Veto => write!(f, "NO TRADE / VETO"),
103        }
104    }
105}
106
107impl From<PlaybookState> for PermissionGrade {
108    fn from(state: PlaybookState) -> Self {
109        match state {
110            PlaybookState::Preferred => PermissionGrade::ClearToTrade,
111            PlaybookState::Neutral => PermissionGrade::Caution,
112            PlaybookState::Suppressed | PlaybookState::NotAllowed => PermissionGrade::Veto,
113        }
114    }
115}
116
117impl From<PermissionGrade> for PlaybookState {
118    fn from(grade: PermissionGrade) -> Self {
119        match grade {
120            PermissionGrade::ClearToTrade => PlaybookState::Preferred,
121            PermissionGrade::Caution => PlaybookState::Neutral,
122            PermissionGrade::Veto => PlaybookState::Suppressed,
123        }
124    }
125}
126
127#[derive(Debug, Clone, PartialEq)]
128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129pub struct SubScore {
130    pub indicator: String,
131    pub score: f64, // -1.0 ..= 1.0
132    pub raw_value: f64,
133    pub reason: Option<String>,
134}
135
136#[derive(Debug, Clone, PartialEq)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub struct CompositeSignal {
139    pub direction: SignalDirection,
140    pub trigger: TriggerAction,
141    pub score: f64,
142    /// 0..1 — share of subscores agreeing with `direction`.
143    ///
144    /// Named `confidence` until 2026-09-08; it is computed as
145    /// `agreeing / total` and never was a win probability. Same reasoning
146    /// as `scoring::Agreement::agreement`.
147    pub agreement: f64,
148    pub heat_score: f64, // 0.0 ..= 1.0
149    pub permission: PermissionGrade,
150    pub regime: MarketRegime,
151    pub target_zone: Option<TargetZone>,
152    pub invalidation_zone: Option<InvalidationZone>,
153    pub setup_duration: Option<SetupDuration>,
154    pub sr_zones: Vec<SupportResistanceZone>,
155    pub risk_plan: Option<RiskPlan>,
156    pub reasons: Vec<String>,
157    pub explanation: String,
158    pub per_indicator: Vec<SubScore>,
159}