Skip to main content

quantwave_backtest/
live_bridge.rs

1//! Live execution bridge contract (quantwave-cr6v.16 / quantwave-53tj).
2//!
3//! Nautilus Trader is **LGPL-3.0** — not embedded in quantwave-backtest.
4//! This module defines a clean-room event export trait for a future adapter crate.
5
6use crate::{Bar, StrategySignal};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Errors from live bridge adapters.
12#[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/// One exportable decision event for external live engines.
22#[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
41/// Trait implemented by future live adapters (e.g. a separate `quantwave-nautilus` crate).
42pub 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/// In-memory stub for tests and notebooks — records events, no network I/O.
49#[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}