rusty_feeder/exchange/upbit/data/
subscription.rs

1/*
2 * Upbit WebSocket subscription message formats
3 */
4
5use serde::Serialize;
6use smallvec::SmallVec;
7use smartstring::alias::String;
8
9/// Module for custom serialization of SmallVec
10mod smallvec_ser {
11    use serde::{Deserialize, Deserializer, Serialize, Serializer};
12    use smallvec::SmallVec;
13
14    /// Serialize a SmallVec as a regular Vec
15    pub fn serialize<S, T>(small_vec: &SmallVec<[T; 8]>, serializer: S) -> Result<S::Ok, S::Error>
16    where
17        S: Serializer,
18        T: Serialize,
19    {
20        small_vec.as_slice().serialize(serializer)
21    }
22
23    /// Deserialize into a SmallVec
24    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<SmallVec<[T; 8]>, D::Error>
25    where
26        D: Deserializer<'de>,
27        T: Deserialize<'de>,
28    {
29        let items = Vec::<T>::deserialize(deserializer)?;
30        Ok(SmallVec::from_vec(items))
31    }
32}
33
34/// WebSocket subscription request for trades
35#[derive(Debug, Clone, Serialize)]
36pub struct TradeSubscription {
37    /// Client-generated ticket ID
38    pub ticket: String,
39
40    /// Type of data (always "trade" for this struct)
41    #[serde(rename = "type")]
42    pub data_type: String,
43
44    /// Market codes to subscribe to
45    #[serde(with = "smallvec_ser")]
46    pub codes: SmallVec<[String; 8]>,
47
48    /// Whether to only get snapshot data
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub is_only_snapshot: Option<bool>,
51
52    /// Whether to only get realtime data
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub is_only_realtime: Option<bool>,
55}
56
57/// WebSocket subscription request for orderbooks
58#[derive(Debug, Clone, Serialize)]
59pub struct OrderbookSubscription {
60    /// Client-generated ticket ID
61    pub ticket: String,
62
63    /// Type of data (always "orderbook" for this struct)
64    #[serde(rename = "type")]
65    pub data_type: String,
66
67    /// Market codes to subscribe to
68    pub codes: SmallVec<[String; 8]>,
69
70    /// Aggregation level for orderbook (optional)
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub level: Option<f64>,
73
74    /// Whether to only get snapshot data
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub is_only_snapshot: Option<bool>,
77
78    /// Whether to only get realtime data
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub is_only_realtime: Option<bool>,
81}
82
83/// WebSocket subscription request for tickers
84#[derive(Debug, Clone, Serialize)]
85pub struct TickerSubscription {
86    /// Client-generated ticket ID
87    pub ticket: String,
88
89    /// Type of data (always "ticker" for this struct)
90    #[serde(rename = "type")]
91    pub data_type: String,
92
93    /// Market codes to subscribe to
94    pub codes: SmallVec<[String; 8]>,
95
96    /// Whether to only get snapshot data
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub is_only_snapshot: Option<bool>,
99
100    /// Whether to only get realtime data
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub is_only_realtime: Option<bool>,
103}
104
105/// WebSocket subscription format
106#[derive(Debug, Clone, Serialize)]
107pub struct FormatRequest {
108    /// Format of responses (DEFAULT or SIMPLE)
109    pub format: String,
110}
111
112/// Helper function to create a full subscription payload for trades
113#[inline(always)]
114pub fn create_trade_subscription(
115    ticket: &str,
116    symbols: SmallVec<[String; 8]>,
117    is_only_snapshot: Option<bool>,
118    is_only_realtime: Option<bool>,
119    use_simple_format: bool,
120) -> Vec<simd_json::OwnedValue> {
121    let mut payload = SmallVec::<[simd_json::OwnedValue; 3]>::new();
122
123    // Add ticket field
124    payload.push(simd_json::json!({
125        "ticket": ticket,
126    }));
127
128    // Add type field with trade subscription
129    payload.push(simd_json::json!({
130        "type": "trade",
131        "codes": symbols.to_vec(),
132        "is_only_snapshot": is_only_snapshot,
133        "is_only_realtime": is_only_realtime,
134    }));
135
136    // Add format field
137    payload.push(simd_json::json!({
138        "format": if use_simple_format { "SIMPLE" } else { "DEFAULT" },
139    }));
140
141    payload.into_vec()
142}
143
144/// Helper function to create a full subscription payload for orderbooks
145#[inline(always)]
146pub fn create_orderbook_subscription(
147    ticket: &str,
148    symbols: SmallVec<[String; 8]>,
149    level: Option<f64>,
150    is_only_snapshot: Option<bool>,
151    is_only_realtime: Option<bool>,
152    use_simple_format: bool,
153) -> Vec<simd_json::OwnedValue> {
154    let mut payload = SmallVec::<[simd_json::OwnedValue; 3]>::new();
155
156    // Add ticket field
157    payload.push(simd_json::json!({
158        "ticket": ticket,
159    }));
160
161    // Add type field with orderbook subscription
162    let mut type_field = simd_json::json!({
163        "type": "orderbook",
164        "codes": symbols.to_vec(),
165        "is_only_snapshot": is_only_snapshot,
166        "is_only_realtime": is_only_realtime,
167    });
168
169    // Add level if provided (for orderbook aggregation)
170    if let Some(level_value) = level {
171        type_field["level"] = simd_json::json!(level_value);
172    }
173
174    payload.push(type_field);
175
176    // Add format field
177    payload.push(simd_json::json!({
178        "format": if use_simple_format { "SIMPLE" } else { "DEFAULT" },
179    }));
180
181    payload.into_vec()
182}