Skip to main content

marketdata_core/models/
common.rs

1//! Common types shared across market data models
2
3use serde::{Deserialize, Serialize};
4
5/// Common response metadata for all API responses
6#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
7#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
8#[cfg_attr(feature = "js", napi_derive::napi(object))]
9pub struct ResponseMeta {
10    /// Trading date (YYYY-MM-DD)
11    pub date: String,
12
13    /// Security type (e.g., "EQUITY", "ODDLOT")
14    #[serde(rename = "type")]
15    pub data_type: Option<String>,
16
17    /// Exchange code (e.g., "TWSE", "TPEx")
18    pub exchange: Option<String>,
19
20    /// Market (e.g., "TSE", "OTC")
21    pub market: Option<String>,
22
23    /// Stock symbol
24    pub symbol: String,
25}
26
27/// Price level for order book (bid/ask)
28#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
29#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
30#[cfg_attr(feature = "js", napi_derive::napi(object))]
31pub struct PriceLevel {
32    /// Price at this level
33    pub price: f64,
34
35    /// Size (volume) at this level
36    pub size: i64,
37}
38
39/// Trade execution info (used in quote.lastTrade, quote.lastTrial)
40#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
41#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
42#[cfg_attr(feature = "js", napi_derive::napi(object))]
43pub struct TradeInfo {
44    /// Best bid price at trade time
45    pub bid: Option<f64>,
46
47    /// Best ask price at trade time
48    pub ask: Option<f64>,
49
50    /// Trade price
51    pub price: f64,
52
53    /// Trade size
54    pub size: i64,
55
56    /// Trade timestamp (Unix milliseconds)
57    pub time: i64,
58}
59
60/// Total trading statistics
61#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
62#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
63#[cfg_attr(feature = "js", napi_derive::napi(object))]
64pub struct TotalStats {
65    /// Total trade value. Absent on FutOpt aggregates — server sends
66    /// `{time, totalBidMatch, totalAskMatch, tradeVolume}` with no
67    /// `tradeValue`, so a missing field must not fail deserialization.
68    #[serde(rename = "tradeValue", default)]
69    pub trade_value: f64,
70
71    /// Total trade volume
72    #[serde(rename = "tradeVolume", default)]
73    pub trade_volume: i64,
74
75    /// Volume traded at bid
76    #[serde(rename = "tradeVolumeAtBid")]
77    pub trade_volume_at_bid: Option<i64>,
78
79    /// Volume traded at ask
80    #[serde(rename = "tradeVolumeAtAsk")]
81    pub trade_volume_at_ask: Option<i64>,
82
83    /// Number of transactions
84    pub transaction: Option<i64>,
85
86    /// Timestamp
87    pub time: Option<i64>,
88}
89
90/// Trading halt status
91#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
92#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
93#[cfg_attr(feature = "js", napi_derive::napi(object))]
94pub struct TradingHalt {
95    /// Whether trading is halted
96    #[serde(rename = "isHalted")]
97    pub is_halted: bool,
98
99    /// Halt timestamp
100    pub time: Option<i64>,
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_price_level_deserialization() {
109        let json = r#"{"price": 100.5, "size": 1000}"#;
110        let level: PriceLevel = serde_json::from_str(json).unwrap();
111        assert_eq!(level.price, 100.5);
112        assert_eq!(level.size, 1000);
113    }
114
115    #[test]
116    fn test_trade_info_deserialization() {
117        let json = r#"{"bid": 100.0, "ask": 100.5, "price": 100.5, "size": 500, "time": 1704067200000}"#;
118        let info: TradeInfo = serde_json::from_str(json).unwrap();
119        assert_eq!(info.price, 100.5);
120        assert_eq!(info.size, 500);
121        assert_eq!(info.time, 1704067200000);
122    }
123
124    #[test]
125    fn test_response_meta_deserialization() {
126        let json = r#"{
127            "date": "2024-01-15",
128            "type": "EQUITY",
129            "exchange": "TWSE",
130            "market": "TSE",
131            "symbol": "2330"
132        }"#;
133        let meta: ResponseMeta = serde_json::from_str(json).unwrap();
134        assert_eq!(meta.date, "2024-01-15");
135        assert_eq!(meta.symbol, "2330");
136        assert_eq!(meta.data_type.as_deref(), Some("EQUITY"));
137    }
138}