1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Serialize, Deserialize)]
5pub struct Greeks {
6 #[serde(skip_serializing_if = "Option::is_none")]
8 pub delta: Option<f64>,
9 #[serde(skip_serializing_if = "Option::is_none")]
11 pub gamma: Option<f64>,
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub vega: Option<f64>,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub theta: Option<f64>,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub rho: Option<f64>,
21}
22
23#[derive(Clone, Serialize, Deserialize)]
25pub struct TickerStats {
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub volume: Option<f64>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub volume_usd: Option<f64>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub price_change: Option<f64>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub high: Option<f64>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub low: Option<f64>,
41}
42
43#[derive(Clone, Serialize, Deserialize)]
45pub struct TickerData {
46 pub instrument_name: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub last_price: Option<f64>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub mark_price: Option<f64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub best_bid_price: Option<f64>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub best_ask_price: Option<f64>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub best_bid_amount: Option<f64>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub best_ask_amount: Option<f64>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub volume: Option<f64>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub volume_usd: Option<f64>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub open_interest: Option<f64>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub high: Option<f64>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub low: Option<f64>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub price_change: Option<f64>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub price_change_percentage: Option<f64>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub bid_iv: Option<f64>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub ask_iv: Option<f64>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub mark_iv: Option<f64>,
96 pub timestamp: u64,
98 pub state: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub settlement_price: Option<f64>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub stats: Option<TickerStats>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub greeks: Option<Greeks>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub index_price: Option<f64>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub min_price: Option<f64>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub max_price: Option<f64>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub interest_rate: Option<f64>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub underlying_price: Option<f64>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub underlying_index: Option<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub estimated_delivery_price: Option<f64>,
130}
131
132crate::impl_json_display!(TickerData);
133crate::impl_json_debug_pretty!(TickerData);
134
135crate::impl_json_display!(TickerStats);
136crate::impl_json_debug_pretty!(TickerStats);
137
138crate::impl_json_display!(Greeks);
139crate::impl_json_debug_pretty!(Greeks);
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use serde_json;
145
146 #[test]
147 fn test_ticker_data_serialization() {
148 let json_str = r#"{
149 "timestamp": 1757433676689,
150 "state": "open",
151 "stats": {
152 "high": 0.0002,
153 "low": 0.0001,
154 "price_change": 100.0,
155 "volume": 70.7,
156 "volume_usd": 974.07
157 },
158 "greeks": {
159 "delta": -0.01874,
160 "gamma": 2.0e-5,
161 "vega": 2.16672,
162 "theta": -15.99927,
163 "rho": -0.03815
164 },
165 "index_price": 110881.2,
166 "instrument_name": "BTC-10SEP25-106000-P",
167 "last_price": 0.0002,
168 "settlement_price": 2.533e-4,
169 "min_price": 0.0001,
170 "max_price": 0.0175,
171 "open_interest": 18.6,
172 "mark_price": 0.0001,
173 "best_bid_price": 0.0001,
174 "best_ask_price": 0.0002,
175 "interest_rate": 0.0,
176 "mark_iv": 49.22,
177 "bid_iv": 46.67,
178 "ask_iv": 51.77,
179 "underlying_price": 110714.7602,
180 "underlying_index": "SYN.BTC-10SEP25",
181 "estimated_delivery_price": 110881.2,
182 "best_ask_amount": 4.1,
183 "best_bid_amount": 2.2
184 }"#;
185
186 let ticker_data: TickerData =
188 serde_json::from_str(json_str).expect("Failed to deserialize ticker data");
189
190 assert_eq!(ticker_data.instrument_name, "BTC-10SEP25-106000-P");
192 assert_eq!(ticker_data.timestamp, 1757433676689);
193 assert_eq!(ticker_data.state, "open");
194 assert_eq!(ticker_data.last_price, Some(0.0002));
195 assert_eq!(ticker_data.mark_price, Some(0.0001));
196 assert_eq!(ticker_data.best_bid_price, Some(0.0001));
197 assert_eq!(ticker_data.best_ask_price, Some(0.0002));
198 assert_eq!(ticker_data.best_bid_amount, Some(2.2));
199 assert_eq!(ticker_data.best_ask_amount, Some(4.1));
200 assert_eq!(ticker_data.open_interest, Some(18.6));
201 assert_eq!(ticker_data.settlement_price, Some(2.533e-4));
202 assert_eq!(ticker_data.min_price, Some(0.0001));
203 assert_eq!(ticker_data.max_price, Some(0.0175));
204 assert_eq!(ticker_data.interest_rate, Some(0.0));
205 assert_eq!(ticker_data.mark_iv, Some(49.22));
206 assert_eq!(ticker_data.bid_iv, Some(46.67));
207 assert_eq!(ticker_data.ask_iv, Some(51.77));
208 assert_eq!(ticker_data.underlying_price, Some(110714.7602));
209 assert_eq!(
210 ticker_data.underlying_index,
211 Some("SYN.BTC-10SEP25".to_string())
212 );
213 assert_eq!(ticker_data.estimated_delivery_price, Some(110881.2));
214 assert_eq!(ticker_data.index_price, Some(110881.2));
215
216 let stats = ticker_data.stats.as_ref().expect("Stats should be present");
218 assert_eq!(stats.high, Some(0.0002));
219 assert_eq!(stats.low, Some(0.0001));
220 assert_eq!(stats.price_change, Some(100.0));
221 assert_eq!(stats.volume, Some(70.7));
222 assert_eq!(stats.volume_usd, Some(974.07));
223
224 let greeks = ticker_data
226 .greeks
227 .as_ref()
228 .expect("Greeks should be present");
229 assert_eq!(greeks.delta, Some(-0.01874));
230 assert_eq!(greeks.gamma, Some(2.0e-5));
231 assert_eq!(greeks.vega, Some(2.16672));
232 assert_eq!(greeks.theta, Some(-15.99927));
233 assert_eq!(greeks.rho, Some(-0.03815));
234
235 let serialized =
237 serde_json::to_string(&ticker_data).expect("Failed to serialize ticker data");
238
239 let _: TickerData = serde_json::from_str(&serialized)
241 .expect("Failed to deserialize serialized ticker data");
242 }
243}