1use indexmap::IndexMap;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::enums::{
23 KrakenAssetClass, KrakenOrderSide, KrakenOrderStatus, KrakenOrderType, KrakenPairStatus,
24 KrakenSpotTrigger, KrakenSystemStatus,
25};
26
27#[derive(Debug, Clone, serde::Deserialize)]
29pub struct KrakenResponse<T> {
30 pub error: Vec<String>,
31 pub result: Option<T>,
32}
33
34pub type BalanceResponse = IndexMap<String, String>;
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AssetPairInfo {
44 pub altname: Ustr,
45 pub wsname: Option<Ustr>,
46 pub aclass_base: KrakenAssetClass,
47 pub base: Ustr,
48 pub aclass_quote: KrakenAssetClass,
49 pub quote: Ustr,
50 pub cost_decimals: u8,
51 pub pair_decimals: u8,
52 pub lot_decimals: u8,
53 pub lot_multiplier: i32,
54 #[serde(default)]
55 pub leverage_buy: Vec<i32>,
56 #[serde(default)]
57 pub leverage_sell: Vec<i32>,
58 #[serde(default)]
59 pub fees: Vec<(i32, f64)>,
60 #[serde(default)]
61 pub fees_maker: Vec<(i32, f64)>,
62 pub fee_volume_currency: Option<Ustr>,
63 pub margin_call: Option<i32>,
64 pub margin_stop: Option<i32>,
65 pub ordermin: Option<String>,
66 pub costmin: Option<String>,
67 pub tick_size: Option<String>,
68 pub status: Option<KrakenPairStatus>,
69 #[serde(default)]
70 pub long_position_limit: Option<i64>,
71 #[serde(default)]
72 pub short_position_limit: Option<i64>,
73}
74
75pub type AssetPairsResponse = IndexMap<String, AssetPairInfo>;
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TickerInfo {
81 #[serde(rename = "a")]
82 pub ask: Vec<String>,
83 #[serde(rename = "b")]
84 pub bid: Vec<String>,
85 #[serde(rename = "c")]
86 pub last: Vec<String>,
87 #[serde(rename = "v")]
88 pub volume: Vec<String>,
89 #[serde(rename = "p")]
90 pub vwap: Vec<String>,
91 #[serde(rename = "t")]
92 pub trades: Vec<i64>,
93 #[serde(rename = "l")]
94 pub low: Vec<String>,
95 #[serde(rename = "h")]
96 pub high: Vec<String>,
97 #[serde(rename = "o")]
98 pub open: String,
99}
100
101pub type TickerResponse = IndexMap<String, TickerInfo>;
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct OhlcData {
107 pub time: i64,
108 pub open: String,
109 pub high: String,
110 pub low: String,
111 pub close: String,
112 pub vwap: String,
113 pub volume: String,
114 pub count: i64,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct OhlcResponse {
119 pub last: i64,
120 #[serde(flatten)]
121 pub data: IndexMap<String, Vec<Vec<serde_json::Value>>>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct TradeData {
128 pub price: String,
129 pub volume: String,
130 pub time: f64,
131 pub side: KrakenOrderSide,
132 pub order_type: KrakenOrderType,
133 pub misc: String,
134 #[serde(default)]
135 pub trade_id: Option<i64>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct TradesResponse {
140 pub last: String,
141 #[serde(flatten)]
142 pub data: IndexMap<String, Vec<Vec<serde_json::Value>>>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct OrderBookLevel {
149 pub price: String,
150 pub volume: String,
151 pub timestamp: i64,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct OrderBookData {
156 pub asks: Vec<Vec<serde_json::Value>>,
157 pub bids: Vec<Vec<serde_json::Value>>,
158}
159
160pub type OrderBookResponse = IndexMap<String, OrderBookData>;
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct SystemStatus {
166 pub status: KrakenSystemStatus,
167 pub timestamp: String,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ServerTime {
174 pub unixtime: i64,
175 pub rfc1123: String,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct WebSocketToken {
182 pub token: String,
183 pub expires: i32,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct OrderDescription {
191 pub pair: String,
192 #[serde(rename = "type")]
193 pub order_side: KrakenOrderSide,
194 pub ordertype: KrakenOrderType,
195 pub price: String,
196 pub price2: String,
197 pub leverage: String,
198 pub order: String,
199 pub close: Option<String>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct AddOrderDescription {
205 #[serde(default)]
206 pub order: Option<String>,
207 #[serde(default)]
208 pub close: Option<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct SpotOrder {
213 pub refid: Option<String>,
214 pub userref: Option<i64>,
215 pub status: KrakenOrderStatus,
216 pub opentm: f64,
217 pub starttm: Option<f64>,
218 pub expiretm: Option<f64>,
219 pub descr: OrderDescription,
220 pub vol: String,
221 pub vol_exec: String,
222 pub cost: String,
223 pub fee: String,
224 pub price: String,
225 pub stopprice: Option<String>,
226 pub limitprice: Option<String>,
227 pub trigger: Option<KrakenSpotTrigger>,
228 pub misc: String,
229 pub oflags: String,
230 #[serde(default)]
231 pub trades: Option<Vec<String>>,
232 #[serde(default)]
233 pub closetm: Option<f64>,
234 #[serde(default)]
235 pub reason: Option<String>,
236 #[serde(default)]
237 pub ratecount: Option<i32>,
238 #[serde(default)]
239 pub cl_ord_id: Option<String>,
240 #[serde(default)]
241 pub amended: Option<bool>,
242 #[serde(default)]
244 pub avg_price: Option<String>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct SpotOpenOrdersResult {
249 pub open: IndexMap<String, SpotOrder>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct SpotClosedOrdersResult {
254 pub closed: IndexMap<String, SpotOrder>,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct SpotTrade {
259 pub ordertxid: String,
260 pub postxid: String,
261 pub pair: String,
262 pub time: f64,
263 #[serde(rename = "type")]
264 pub trade_type: KrakenOrderSide,
265 pub ordertype: KrakenOrderType,
266 pub price: String,
267 pub cost: String,
268 pub fee: String,
269 pub vol: String,
270 pub margin: String,
271 pub leverage: Option<String>,
272 pub misc: String,
273 #[serde(default)]
274 pub trade_id: Option<i64>,
275 #[serde(default)]
276 pub maker: Option<bool>,
277 #[serde(default)]
278 pub ledgers: Option<Vec<String>>,
279 #[serde(default)]
280 pub posstatus: Option<String>,
281 #[serde(default)]
282 pub cprice: Option<String>,
283 #[serde(default)]
284 pub ccost: Option<String>,
285 #[serde(default)]
286 pub cfee: Option<String>,
287 #[serde(default)]
288 pub cvol: Option<String>,
289 #[serde(default)]
290 pub cmargin: Option<String>,
291 #[serde(default)]
292 pub net: Option<String>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct SpotTradesHistoryResult {
297 pub trades: IndexMap<String, SpotTrade>,
298 pub count: i32,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct SpotAddOrderResponse {
305 pub descr: Option<AddOrderDescription>,
306 #[serde(default)]
307 pub txid: Vec<String>,
308 #[serde(default)]
309 pub cl_ord_id: Option<String>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct SpotCancelOrderResponse {
314 pub count: i32,
315 #[serde(default)]
316 pub pending: Option<bool>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct SpotCancelOrderBatchResponse {
321 pub count: i32,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct SpotEditOrderResponse {
326 pub descr: Option<AddOrderDescription>,
327 pub txid: Option<String>,
328 #[serde(default)]
329 pub originaltxid: Option<String>,
330 #[serde(default)]
331 pub volume: Option<String>,
332 #[serde(default)]
333 pub price: Option<String>,
334 #[serde(default)]
335 pub price2: Option<String>,
336 #[serde(default)]
337 pub orders_cancelled: Option<i32>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
342pub struct SpotAmendOrderResponse {
343 pub amend_id: String,
345}
346
347#[cfg(test)]
348mod tests {
349 use rstest::rstest;
350
351 use super::*;
352
353 fn load_test_data(filename: &str) -> String {
354 let path = format!("test_data/{filename}");
355 std::fs::read_to_string(&path)
356 .unwrap_or_else(|e| panic!("Failed to load test data from {path}: {e}"))
357 }
358
359 #[rstest]
360 fn test_parse_server_time() {
361 let data = load_test_data("http_server_time.json");
362 let response: KrakenResponse<ServerTime> =
363 serde_json::from_str(&data).expect("Failed to parse server time");
364
365 assert!(response.error.is_empty());
366 let result = response.result.expect("Missing result");
367 assert!(result.unixtime > 0);
368 assert!(!result.rfc1123.is_empty());
369 }
370
371 #[rstest]
372 fn test_parse_system_status() {
373 let data = load_test_data("http_system_status.json");
374 let response: KrakenResponse<SystemStatus> =
375 serde_json::from_str(&data).expect("Failed to parse system status");
376
377 assert!(response.error.is_empty());
378 let result = response.result.expect("Missing result");
379 assert!(!result.timestamp.is_empty());
380 }
381
382 #[rstest]
383 fn test_parse_asset_pairs() {
384 let data = load_test_data("http_asset_pairs.json");
385 let response: KrakenResponse<AssetPairsResponse> =
386 serde_json::from_str(&data).expect("Failed to parse asset pairs");
387
388 assert!(response.error.is_empty());
389 let result = response.result.expect("Missing result");
390 assert!(!result.is_empty());
391
392 let pair = result.get("XBTUSDT").expect("XBTUSDT pair not found");
393 assert_eq!(pair.altname.as_str(), "XBTUSDT");
394 assert_eq!(pair.base.as_str(), "XXBT");
395 assert_eq!(pair.quote.as_str(), "USDT");
396 assert!(pair.wsname.is_some());
397 }
398
399 #[rstest]
400 fn test_parse_ticker() {
401 let data = load_test_data("http_ticker.json");
402 let response: KrakenResponse<TickerResponse> =
403 serde_json::from_str(&data).expect("Failed to parse ticker");
404
405 assert!(response.error.is_empty());
406 let result = response.result.expect("Missing result");
407 assert!(!result.is_empty());
408
409 let ticker = result.get("XBTUSDT").expect("XBTUSDT ticker not found");
410 assert_eq!(ticker.ask.len(), 3);
411 assert_eq!(ticker.bid.len(), 3);
412 assert_eq!(ticker.last.len(), 2);
413 }
414
415 #[rstest]
416 fn test_parse_ohlc() {
417 let data = load_test_data("http_ohlc.json");
418 let response: KrakenResponse<serde_json::Value> =
419 serde_json::from_str(&data).expect("Failed to parse OHLC");
420
421 assert!(response.error.is_empty());
422 assert!(response.result.is_some());
423 }
424
425 #[rstest]
426 fn test_parse_order_book() {
427 let data = load_test_data("http_order_book.json");
428 let response: KrakenResponse<OrderBookResponse> =
429 serde_json::from_str(&data).expect("Failed to parse order book");
430
431 assert!(response.error.is_empty());
432 let result = response.result.expect("Missing result");
433 assert!(!result.is_empty());
434
435 let book = result.get("XBTUSDT").expect("XBTUSDT order book not found");
436 assert!(!book.asks.is_empty());
437 assert!(!book.bids.is_empty());
438 }
439
440 #[rstest]
441 fn test_parse_trades() {
442 let data = load_test_data("http_trades.json");
443 let response: KrakenResponse<TradesResponse> =
444 serde_json::from_str(&data).expect("Failed to parse trades");
445
446 assert!(response.error.is_empty());
447 let result = response.result.expect("Missing result");
448 assert!(!result.data.is_empty());
449 }
450}