opendeviationbar-streaming 13.79.0

Real-time streaming engine for open deviation bar processing
Documentation
//! GapEvent: notification of trade-ID discontinuity (Issue #257).

use std::fmt;
use std::sync::Arc;

/// Emitted when a trade-ID gap is detected in the WebSocket stream.
///
/// Consumers (Python sidecar, FlowSurface SSE) receive these via
/// `StreamManager::take_gap_event_receiver()` and can trigger
/// on-demand fill via `fill_gap()`.
#[derive(Debug, Clone)]
pub struct GapEvent {
    /// Symbol where the gap was detected (e.g., "BTCUSDT").
    pub symbol: Arc<str>,
    /// The trade ID we expected (last_known + 1).
    pub expected_tid: i64,
    /// The trade ID that actually arrived from WebSocket.
    pub actual_tid: i64,
    /// Number of missing trades (actual - expected).
    pub gap_size: i64,
    /// Millisecond timestamp when the gap was detected.
    pub detected_at_ms: i64,
    /// Whether `puck_fill()` handled this gap inline.
    /// `true` = already filled by the engine; `false` = consumer should act.
    pub filled_inline: bool,
}

impl fmt::Display for GapEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "GapEvent({}: expected={}, actual={}, gap={}, filled={})",
            self.symbol, self.expected_tid, self.actual_tid, self.gap_size, self.filled_inline
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_gap_event_display() {
        let event = GapEvent {
            symbol: Arc::from("BTCUSDT"),
            expected_tid: 101,
            actual_tid: 110,
            gap_size: 9,
            detected_at_ms: 1700000000000,
            filled_inline: false,
        };
        let display = format!("{event}");
        assert!(display.contains("BTCUSDT"));
        assert!(display.contains("expected=101"));
        assert!(display.contains("actual=110"));
        assert!(display.contains("gap=9"));
    }

    #[test]
    fn test_gap_event_clone() {
        let event = GapEvent {
            symbol: Arc::from("ETHUSDT"),
            expected_tid: 50,
            actual_tid: 100,
            gap_size: 50,
            detected_at_ms: 1700000000000,
            filled_inline: true,
        };
        let cloned = event.clone();
        assert_eq!(&*cloned.symbol, "ETHUSDT");
        assert!(cloned.filled_inline);
    }
}