1use indexmap::IndexMap;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::enums::{
23 KrakenAssetClass, KrakenOrderSide, KrakenOrderType, KrakenPairStatus, KrakenSystemStatus,
24};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AssetPairInfo {
30 pub altname: Ustr,
31 pub wsname: Option<Ustr>,
32 pub aclass_base: KrakenAssetClass,
33 pub base: Ustr,
34 pub aclass_quote: KrakenAssetClass,
35 pub quote: Ustr,
36 pub cost_decimals: u8,
37 pub pair_decimals: u8,
38 pub lot_decimals: u8,
39 pub lot_multiplier: i32,
40 #[serde(default)]
41 pub leverage_buy: Vec<i32>,
42 #[serde(default)]
43 pub leverage_sell: Vec<i32>,
44 #[serde(default)]
45 pub fees: Vec<(i32, f64)>,
46 #[serde(default)]
47 pub fees_maker: Vec<(i32, f64)>,
48 pub fee_volume_currency: Option<Ustr>,
49 pub margin_call: Option<i32>,
50 pub margin_stop: Option<i32>,
51 pub ordermin: Option<String>,
52 pub costmin: Option<String>,
53 pub tick_size: Option<String>,
54 pub status: Option<KrakenPairStatus>,
55 #[serde(default)]
56 pub long_position_limit: Option<i64>,
57 #[serde(default)]
58 pub short_position_limit: Option<i64>,
59}
60
61pub type AssetPairsResponse = IndexMap<String, AssetPairInfo>;
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TickerInfo {
67 #[serde(rename = "a")]
68 pub ask: Vec<String>,
69 #[serde(rename = "b")]
70 pub bid: Vec<String>,
71 #[serde(rename = "c")]
72 pub last: Vec<String>,
73 #[serde(rename = "v")]
74 pub volume: Vec<String>,
75 #[serde(rename = "p")]
76 pub vwap: Vec<String>,
77 #[serde(rename = "t")]
78 pub trades: Vec<i64>,
79 #[serde(rename = "l")]
80 pub low: Vec<String>,
81 #[serde(rename = "h")]
82 pub high: Vec<String>,
83 #[serde(rename = "o")]
84 pub open: String,
85}
86
87pub type TickerResponse = IndexMap<String, TickerInfo>;
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct OhlcData {
93 pub time: i64,
94 pub open: String,
95 pub high: String,
96 pub low: String,
97 pub close: String,
98 pub vwap: String,
99 pub volume: String,
100 pub count: i64,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct OhlcResponse {
105 pub last: i64,
106 #[serde(flatten)]
107 pub data: IndexMap<String, Vec<Vec<serde_json::Value>>>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct TradeData {
114 pub price: String,
115 pub volume: String,
116 pub time: f64,
117 pub side: KrakenOrderSide,
118 pub order_type: KrakenOrderType,
119 pub misc: String,
120 #[serde(default)]
121 pub trade_id: Option<i64>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct TradesResponse {
126 pub last: String,
127 #[serde(flatten)]
128 pub data: IndexMap<String, Vec<Vec<serde_json::Value>>>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct OrderBookLevel {
135 pub price: String,
136 pub volume: String,
137 pub timestamp: i64,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct OrderBookData {
142 pub asks: Vec<Vec<serde_json::Value>>,
143 pub bids: Vec<Vec<serde_json::Value>>,
144}
145
146pub type OrderBookResponse = IndexMap<String, OrderBookData>;
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct SystemStatus {
152 pub status: KrakenSystemStatus,
153 pub timestamp: String,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct ServerTime {
160 pub unixtime: i64,
161 pub rfc1123: String,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct WebSocketToken {
168 pub token: String,
169 pub expires: i32,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct FuturesMarginLevel {
176 pub contracts: i64,
177 #[serde(rename = "initialMargin")]
178 pub initial_margin: f64,
179 #[serde(rename = "maintenanceMargin")]
180 pub maintenance_margin: f64,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct FuturesInstrument {
185 pub symbol: String,
186 #[serde(rename = "type")]
187 pub instrument_type: String,
188 pub underlying: String,
189 #[serde(rename = "tickSize")]
190 pub tick_size: f64,
191 #[serde(rename = "contractSize")]
192 pub contract_size: f64,
193 pub tradeable: bool,
194 #[serde(rename = "impactMidSize")]
195 pub impact_mid_size: f64,
196 #[serde(rename = "maxPositionSize")]
197 pub max_position_size: f64,
198 #[serde(rename = "openingDate")]
199 pub opening_date: String,
200 #[serde(rename = "marginLevels")]
201 pub margin_levels: Vec<FuturesMarginLevel>,
202 #[serde(rename = "fundingRateCoefficient", default)]
203 pub funding_rate_coefficient: Option<i32>,
204 #[serde(rename = "maxRelativeFundingRate", default)]
205 pub max_relative_funding_rate: Option<f64>,
206 #[serde(default)]
207 pub isin: Option<String>,
208 #[serde(rename = "contractValueTradePrecision")]
209 pub contract_value_trade_precision: i32,
210 #[serde(rename = "postOnly")]
211 pub post_only: bool,
212 #[serde(rename = "feeScheduleUid")]
213 pub fee_schedule_uid: String,
214 pub mtf: bool,
215 pub base: String,
216 pub quote: String,
217 pub pair: String,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FuturesInstrumentsResponse {
222 pub result: String,
223 pub instruments: Vec<FuturesInstrument>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct FuturesTicker {
228 pub symbol: String,
229 pub last: f64,
230 #[serde(rename = "lastTime")]
231 pub last_time: String,
232 pub tag: String,
233 pub pair: String,
234 #[serde(rename = "markPrice")]
235 pub mark_price: f64,
236 pub bid: f64,
237 #[serde(rename = "bidSize")]
238 pub bid_size: f64,
239 pub ask: f64,
240 #[serde(rename = "askSize")]
241 pub ask_size: f64,
242 #[serde(rename = "vol24h")]
243 pub vol_24h: f64,
244 #[serde(rename = "volumeQuote")]
245 pub volume_quote: f64,
246 #[serde(rename = "openInterest")]
247 pub open_interest: f64,
248 #[serde(rename = "open24h")]
249 pub open_24h: f64,
250 #[serde(rename = "high24h")]
251 pub high_24h: f64,
252 #[serde(rename = "low24h")]
253 pub low_24h: f64,
254 #[serde(rename = "lastSize")]
255 pub last_size: f64,
256 #[serde(rename = "fundingRate", default)]
257 pub funding_rate: Option<f64>,
258 #[serde(rename = "fundingRatePrediction", default)]
259 pub funding_rate_prediction: Option<f64>,
260 pub suspended: bool,
261 #[serde(rename = "indexPrice")]
262 pub index_price: f64,
263 #[serde(rename = "postOnly")]
264 pub post_only: bool,
265 #[serde(rename = "change24h")]
266 pub change_24h: f64,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct FuturesTickersResponse {
271 pub result: String,
272 #[serde(rename = "serverTime")]
273 pub server_time: String,
274 pub tickers: Vec<FuturesTicker>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct FuturesCandle {
279 pub time: i64,
280 pub open: String,
281 pub high: String,
282 pub low: String,
283 pub close: String,
284 pub volume: String,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct FuturesCandlesResponse {
289 pub candles: Vec<FuturesCandle>,
290}
291
292#[cfg(test)]
293mod tests {
294 use rstest::rstest;
295
296 use super::*;
297 use crate::http::client::KrakenResponse;
298
299 fn load_test_data(filename: &str) -> String {
300 let path = format!("test_data/{filename}");
301 std::fs::read_to_string(&path)
302 .unwrap_or_else(|e| panic!("Failed to load test data from {path}: {e}"))
303 }
304
305 #[rstest]
306 fn test_parse_server_time() {
307 let data = load_test_data("http_server_time.json");
308 let response: KrakenResponse<ServerTime> =
309 serde_json::from_str(&data).expect("Failed to parse server time");
310
311 assert!(response.error.is_empty());
312 let result = response.result.expect("Missing result");
313 assert!(result.unixtime > 0);
314 assert!(!result.rfc1123.is_empty());
315 }
316
317 #[rstest]
318 fn test_parse_system_status() {
319 let data = load_test_data("http_system_status.json");
320 let response: KrakenResponse<SystemStatus> =
321 serde_json::from_str(&data).expect("Failed to parse system status");
322
323 assert!(response.error.is_empty());
324 let result = response.result.expect("Missing result");
325 assert!(!result.timestamp.is_empty());
326 }
327
328 #[rstest]
329 fn test_parse_asset_pairs() {
330 let data = load_test_data("http_asset_pairs.json");
331 let response: KrakenResponse<AssetPairsResponse> =
332 serde_json::from_str(&data).expect("Failed to parse asset pairs");
333
334 assert!(response.error.is_empty());
335 let result = response.result.expect("Missing result");
336 assert!(!result.is_empty());
337
338 let pair = result.get("XBTUSDT").expect("XBTUSDT pair not found");
339 assert_eq!(pair.altname.as_str(), "XBTUSDT");
340 assert_eq!(pair.base.as_str(), "XXBT");
341 assert_eq!(pair.quote.as_str(), "USDT");
342 assert!(pair.wsname.is_some());
343 }
344
345 #[rstest]
346 fn test_parse_ticker() {
347 let data = load_test_data("http_ticker.json");
348 let response: KrakenResponse<TickerResponse> =
349 serde_json::from_str(&data).expect("Failed to parse ticker");
350
351 assert!(response.error.is_empty());
352 let result = response.result.expect("Missing result");
353 assert!(!result.is_empty());
354
355 let ticker = result.get("XBTUSDT").expect("XBTUSDT ticker not found");
356 assert_eq!(ticker.ask.len(), 3);
357 assert_eq!(ticker.bid.len(), 3);
358 assert_eq!(ticker.last.len(), 2);
359 }
360
361 #[rstest]
362 fn test_parse_ohlc() {
363 let data = load_test_data("http_ohlc.json");
364 let response: KrakenResponse<serde_json::Value> =
365 serde_json::from_str(&data).expect("Failed to parse OHLC");
366
367 assert!(response.error.is_empty());
368 assert!(response.result.is_some());
369 }
370
371 #[rstest]
372 fn test_parse_order_book() {
373 let data = load_test_data("http_order_book.json");
374 let response: KrakenResponse<OrderBookResponse> =
375 serde_json::from_str(&data).expect("Failed to parse order book");
376
377 assert!(response.error.is_empty());
378 let result = response.result.expect("Missing result");
379 assert!(!result.is_empty());
380
381 let book = result.get("XBTUSDT").expect("XBTUSDT order book not found");
382 assert!(!book.asks.is_empty());
383 assert!(!book.bids.is_empty());
384 }
385
386 #[rstest]
387 fn test_parse_trades() {
388 let data = load_test_data("http_trades.json");
389 let response: KrakenResponse<TradesResponse> =
390 serde_json::from_str(&data).expect("Failed to parse trades");
391
392 assert!(response.error.is_empty());
393 let result = response.result.expect("Missing result");
394 assert!(!result.data.is_empty());
395 }
396}