1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum SignalType {
10 Buy,
12 Sell,
14 Hold,
17 Close,
19}
20
21impl std::fmt::Display for SignalType {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::Buy => write!(f, "BUY"),
25 Self::Sell => write!(f, "SELL"),
26 Self::Hold => write!(f, "HOLD"),
27 Self::Close => write!(f, "CLOSE"),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Signal {
38 pub id: String,
40 pub symbol: String,
42 pub kind: SignalType,
44 pub confidence: f64,
48 pub timestamp: DateTime<Utc>,
50 pub source: String,
53 #[serde(default)]
55 pub metadata: serde_json::Value,
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn signal_type_display() {
64 assert_eq!(SignalType::Buy.to_string(), "BUY");
65 assert_eq!(SignalType::Sell.to_string(), "SELL");
66 assert_eq!(SignalType::Hold.to_string(), "HOLD");
67 assert_eq!(SignalType::Close.to_string(), "CLOSE");
68 }
69
70 #[test]
71 fn signal_serde_roundtrip() {
72 let s = Signal {
73 id: "sig-1".into(),
74 symbol: "BTCUSDT".into(),
75 kind: SignalType::Buy,
76 confidence: 0.8,
77 timestamp: chrono::Utc::now(),
78 source: "ema-cross".into(),
79 metadata: serde_json::json!({"fast": 9, "slow": 21}),
80 };
81 let json = serde_json::to_string(&s).unwrap();
82 let back: Signal = serde_json::from_str(&json).unwrap();
83 assert_eq!(back.id, s.id);
84 assert!(matches!(back.kind, SignalType::Buy));
85 assert_eq!(back.metadata["fast"], 9);
86 }
87}