Skip to main content

hyper_agent_core/
signal.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4pub enum Side {
5    Buy,
6    Sell,
7}
8
9impl std::fmt::Display for Side {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        match self {
12            Side::Buy => write!(f, "buy"),
13            Side::Sell => write!(f, "sell"),
14        }
15    }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum SignalSource {
20    Playbook { strategy_id: String, regime: String },
21    Manual,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum SignalAction {
26    Open {
27        side: Side,
28        size: f64,
29        price: Option<f64>,
30    },
31    Close {
32        side: Side,
33        size: f64,
34    },
35    CloseAll {
36        reason: String,
37    },
38    SetStopLoss {
39        trigger_price: f64,
40    },
41    SetTakeProfit {
42        trigger_price: f64,
43    },
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct TradeSignal {
48    pub id: String,
49    pub timestamp: u64,
50    pub source: SignalSource,
51    pub symbol: String,
52    pub action: SignalAction,
53    pub reason: String,
54}
55
56impl TradeSignal {
57    pub fn manual(symbol: String, action: SignalAction, reason: String) -> Self {
58        Self {
59            id: uuid::Uuid::new_v4().to_string(),
60            timestamp: std::time::SystemTime::now()
61                .duration_since(std::time::UNIX_EPOCH)
62                .unwrap_or_default()
63                .as_secs(),
64            source: SignalSource::Manual,
65            symbol,
66            action,
67            reason,
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn side_display() {
78        assert_eq!(Side::Buy.to_string(), "buy");
79        assert_eq!(Side::Sell.to_string(), "sell");
80    }
81
82    #[test]
83    fn side_serde_roundtrip() {
84        let json = serde_json::to_string(&Side::Buy).unwrap();
85        let back: Side = serde_json::from_str(&json).unwrap();
86        assert_eq!(back, Side::Buy);
87    }
88
89    #[test]
90    fn trade_signal_manual_constructor() {
91        let sig = TradeSignal::manual(
92            "BTC-PERP".into(),
93            SignalAction::Open {
94                side: Side::Buy,
95                size: 0.01,
96                price: Some(65000.0),
97            },
98            "manual order".into(),
99        );
100        assert_eq!(sig.symbol, "BTC-PERP");
101        assert!(matches!(sig.source, SignalSource::Manual));
102        assert!(!sig.id.is_empty());
103        assert!(sig.timestamp > 0);
104    }
105
106    #[test]
107    fn trade_signal_serde_roundtrip() {
108        let sig = TradeSignal::manual(
109            "ETH-PERP".into(),
110            SignalAction::SetStopLoss {
111                trigger_price: 3000.0,
112            },
113            "SL set".into(),
114        );
115        let json = serde_json::to_string(&sig).unwrap();
116        let back: TradeSignal = serde_json::from_str(&json).unwrap();
117        assert_eq!(back.symbol, "ETH-PERP");
118    }
119}