rusty_feeder/exchange/bybit/futures/data/
trade.rs

1//! Bybit Futures trade data structures
2//!
3//! This module defines types for handling Bybit Futures trade data via WebSocket,
4//! optimized for high-frequency trading scenarios with nanosecond precision.
5
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use smartstring::alias::String;
9
10/// Bybit Futures trade message response from WebSocket
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct TradeResponse {
13    /// Topic being subscribed to (e.g., "publicTrade.BTCUSDT")
14    pub topic: String,
15
16    /// Type of message (e.g., "snapshot" or "delta")
17    #[serde(rename = "type")]
18    pub message_type: String,
19
20    /// Timestamp in milliseconds (from Bybit server)
21    pub ts: u64,
22
23    /// Trade data
24    pub data: Vec<TradeData>,
25}
26
27/// Trade data from Bybit Futures
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct TradeData {
30    /// Trading symbol (e.g., "BTCUSDT")
31    pub symbol: String,
32
33    /// Trade tick direction (e.g., "PlusTick", "MinusTick")
34    #[serde(rename = "tickDirection")]
35    pub tick_direction: String,
36
37    /// Price of the trade
38    pub price: String,
39
40    /// Quantity/size of the trade
41    pub size: String,
42
43    /// Trade timestamp in milliseconds
44    pub timestamp: String,
45
46    /// Trade ID
47    #[serde(rename = "tradeId")]
48    pub trade_id: String,
49
50    /// Whether the trade was a buy or sell (from taker perspective)
51    pub side: String,
52}
53
54/// Implementation of TradeResponse
55impl TradeResponse {
56    /// Convert to Rust Decimal for price
57    pub fn price_decimal(&self) -> Option<Decimal> {
58        self.data.first().and_then(|trade| trade.price.parse().ok())
59    }
60
61    /// Convert to Rust Decimal for size/quantity
62    pub fn size_decimal(&self) -> Option<Decimal> {
63        self.data.first().and_then(|trade| trade.size.parse().ok())
64    }
65
66    /// Get the symbol from the trade data
67    pub fn symbol(&self) -> Option<&str> {
68        self.data.first().map(|trade| trade.symbol.as_str())
69    }
70
71    /// Get the trade timestamp in nanoseconds
72    pub const fn timestamp_ns(&self) -> u64 {
73        // Convert from ms to ns
74        self.ts * 1_000_000
75    }
76
77    /// Is this a buy trade? (true for buy, false for sell)
78    pub fn is_buy(&self) -> Option<bool> {
79        self.data
80            .first()
81            .map(|trade| trade.side.to_lowercase() == "buy")
82    }
83}
84
85/// Ping request for keeping WebSocket connection alive
86#[derive(Debug, Serialize, Deserialize)]
87pub struct PingRequest {
88    /// Operation type (ping)
89    pub op: String,
90
91    /// Request arguments
92    pub args: Vec<String>,
93}
94
95impl Default for PingRequest {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl PingRequest {
102    /// Create a new ping request
103    #[must_use]
104    pub fn new() -> Self {
105        Self {
106            op: "ping".into(),
107            args: vec![],
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_ping_request_serialization() {
118        let ping = PingRequest::new();
119        let json = simd_json::to_string(&ping).unwrap();
120        let json = String::from(&json);
121        assert_eq!(json, r#"{"op":"ping","args":[]}"#);
122    }
123}