quantwave_backtest/
live_bridge.rs1use crate::{Bar, StrategySignal};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum LiveBridgeError {
14 #[error("bridge not connected")]
15 NotConnected,
16
17 #[error("bridge publish failed: {0}")]
18 PublishFailed(String),
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct LiveSignalEvent {
24 pub ts: DateTime<Utc>,
25 pub symbol: Option<String>,
26 pub exposure: f64,
27 pub metadata: Option<std::collections::HashMap<String, f64>>,
28}
29
30impl LiveSignalEvent {
31 pub fn from_bar_signal(bar: &Bar, symbol: Option<String>, signal: &StrategySignal) -> Self {
32 Self {
33 ts: bar.ts,
34 symbol,
35 exposure: signal.exposure,
36 metadata: signal.metadata.clone(),
37 }
38 }
39}
40
41pub trait LiveBridge: Send {
43 fn connect(&mut self) -> Result<(), LiveBridgeError>;
44 fn publish(&mut self, event: &LiveSignalEvent) -> Result<(), LiveBridgeError>;
45 fn disconnect(&mut self) -> Result<(), LiveBridgeError>;
46}
47
48#[derive(Debug, Default)]
50pub struct RecordingLiveBridge {
51 connected: bool,
52 pub events: Vec<LiveSignalEvent>,
53}
54
55impl RecordingLiveBridge {
56 pub fn new() -> Self {
57 Self::default()
58 }
59}
60
61impl LiveBridge for RecordingLiveBridge {
62 fn connect(&mut self) -> Result<(), LiveBridgeError> {
63 self.connected = true;
64 Ok(())
65 }
66
67 fn publish(&mut self, event: &LiveSignalEvent) -> Result<(), LiveBridgeError> {
68 if !self.connected {
69 return Err(LiveBridgeError::NotConnected);
70 }
71 self.events.push(event.clone());
72 Ok(())
73 }
74
75 fn disconnect(&mut self) -> Result<(), LiveBridgeError> {
76 self.connected = false;
77 Ok(())
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_recording_live_bridge_records_events() {
87 let mut bridge = RecordingLiveBridge::new();
88 bridge.connect().unwrap();
89 let bar = Bar {
90 ts: Utc::now(),
91 close: 101.5,
92 };
93 let signal = StrategySignal {
94 exposure: 1.0,
95 metadata: None,
96 };
97 let event = LiveSignalEvent::from_bar_signal(&bar, Some("SPY".into()), &signal);
98 bridge.publish(&event).unwrap();
99 assert_eq!(bridge.events.len(), 1);
100 assert_eq!(bridge.events[0].exposure, 1.0);
101 bridge.disconnect().unwrap();
102 }
103
104 #[test]
105 fn test_live_bridge_not_connected_errors() {
106 let mut bridge = RecordingLiveBridge::new();
107 let event = LiveSignalEvent {
108 ts: Utc::now(),
109 symbol: None,
110 exposure: 0.0,
111 metadata: None,
112 };
113 assert!(matches!(
114 bridge.publish(&event),
115 Err(LiveBridgeError::NotConnected)
116 ));
117 }
118}