hyper_agent_notify/
emitter.rs1use serde::{Deserialize, Serialize};
2use std::sync::{Arc, Mutex};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case", tag = "type")]
6pub enum TradingEvent {
7 EngineStarted {
8 id: String,
9 },
10 EngineStopped {
11 id: String,
12 reason: String,
13 },
14 TickCompleted {
15 symbol: String,
16 regime: String,
17 },
18 SignalGenerated {
19 signal_id: String,
20 symbol: String,
21 action: String,
22 },
23 OrderExecuted {
24 order_id: String,
25 market: String,
26 side: String,
27 price: f64,
28 size: f64,
29 },
30 OrderBlocked {
31 market: String,
32 reason: String,
33 },
34 RiskAlert {
35 message: String,
36 },
37 CircuitBreakerTripped,
38 AgentAdjustment {
39 changes: String,
40 },
41}
42
43pub trait TradingEventEmitter: Send + Sync {
44 fn emit(&self, event: TradingEvent);
45}
46
47pub struct BroadcastEmitter {
49 tx: tokio::sync::broadcast::Sender<TradingEvent>,
50}
51
52impl BroadcastEmitter {
53 pub fn new(tx: tokio::sync::broadcast::Sender<TradingEvent>) -> Self {
54 Self { tx }
55 }
56}
57
58impl TradingEventEmitter for BroadcastEmitter {
59 fn emit(&self, event: TradingEvent) {
60 let _ = self.tx.send(event);
61 }
62}
63
64pub struct LogEmitter;
66
67impl TradingEventEmitter for LogEmitter {
68 fn emit(&self, event: TradingEvent) {
69 tracing::info!(event = ?event, "trading event");
70 }
71}
72
73pub struct RecordingEmitter {
75 events: Arc<Mutex<Vec<TradingEvent>>>,
76}
77
78impl RecordingEmitter {
79 pub fn new() -> Self {
80 Self {
81 events: Arc::new(Mutex::new(Vec::new())),
82 }
83 }
84
85 pub fn events(&self) -> Vec<TradingEvent> {
86 self.events.lock().unwrap().clone()
87 }
88
89 pub fn len(&self) -> usize {
90 self.events.lock().unwrap().len()
91 }
92}
93
94impl Default for RecordingEmitter {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl TradingEventEmitter for RecordingEmitter {
101 fn emit(&self, event: TradingEvent) {
102 self.events.lock().unwrap().push(event);
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn recording_emitter_captures_events() {
112 let emitter = RecordingEmitter::new();
113 emitter.emit(TradingEvent::EngineStarted { id: "test".into() });
114 emitter.emit(TradingEvent::TickCompleted {
115 symbol: "BTC".into(),
116 regime: "bull".into(),
117 });
118 assert_eq!(emitter.len(), 2);
119 }
120
121 #[test]
122 fn trading_event_serde_roundtrip() {
123 let event = TradingEvent::OrderExecuted {
124 order_id: "o1".into(),
125 market: "BTC-PERP".into(),
126 side: "buy".into(),
127 price: 65000.0,
128 size: 0.01,
129 };
130 let json = serde_json::to_string(&event).unwrap();
131 let back: TradingEvent = serde_json::from_str(&json).unwrap();
132 assert!(matches!(back, TradingEvent::OrderExecuted { .. }));
133 }
134
135 #[test]
136 fn log_emitter_does_not_panic() {
137 let emitter = LogEmitter;
138 emitter.emit(TradingEvent::CircuitBreakerTripped);
139 }
140}