kraken_async_rs/wss/messages/
user_data_messages.rs

1use crate::crypto::secrets::Token;
2use crate::request_types::{TimeInForce, TriggerType};
3use crate::response_types::{BuySell, OrderStatusV2, OrderType, PositionStatusV2};
4use crate::wss::{
5    BookSubscriptionResponse, OhlcSubscriptionResponse, TickerSubscriptionResponse,
6    TradeSubscriptionResponse,
7};
8use crate::wss::{ConditionalParams, FeePreference, PriceType};
9use rust_decimal::Decimal;
10use serde::{Deserialize, Serialize};
11use serde_with::skip_serializing_none;
12
13#[derive(Debug, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum ExecutionResponseType {
16    Snapshot,
17    Update,
18}
19
20#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
21pub enum MakerTaker {
22    #[serde(rename = "m")]
23    Maker,
24    #[serde(rename = "t")]
25    Taker,
26}
27
28/// Type of ledger entry in user's ledger
29#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Copy)]
30#[serde(rename_all = "snake_case")]
31pub enum LedgerEntryTypeV2 {
32    Trade,
33    Credit,
34    Deposit,
35    Withdrawal,
36    Transfer,
37    Margin,
38    Rollover,
39    Settled,
40    Adjustment,
41    Staking,
42    Sale,
43    Reserve,
44    Conversion,
45    Dividend,
46    Reward,
47    CreatorFee,
48}
49
50#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Copy)]
51#[serde(rename_all = "lowercase")]
52pub enum LedgerEntrySubType {
53    SpotFromFutures,
54    SpotToFutures,
55    StakingFromSpot,
56    SpotFromStaking,
57    StakingToSpot,
58    SpotToStaking,
59}
60
61#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Copy)]
62#[serde(rename_all = "kebab-case")]
63pub enum LedgerCategory {
64    Deposit,
65    Withdrawal,
66    Trade,
67    MarginTrade,
68    MarginSettled,
69    MarginConversion,
70    Conversion,
71    Credit,
72    MarginRollover,
73    StakingRewards,
74    Instant,
75    EquityTrade,
76    Airdrop,
77    EquityDividend,
78    RewardBonus,
79    Nft,
80    BlockTrade,
81}
82
83#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
84#[serde(rename_all = "lowercase")]
85pub enum WalletType {
86    Spot,
87    Earn,
88}
89
90#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
91#[serde(rename_all = "lowercase")]
92pub enum WalletId {
93    Main,
94    Flex,
95    Bonded,
96}
97
98#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
99#[serde(rename_all = "snake_case")]
100pub enum ExecutionType {
101    PendingNew,
102    New,
103    Trade,
104    Filled,
105    Canceled,
106    Expired,
107    Amended,
108    Restated,
109    Status,
110}
111
112#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
113#[serde(rename_all = "lowercase")]
114pub enum TriggerStatus {
115    Triggered,
116    Untriggered,
117}
118
119#[derive(Debug, Serialize, Clone)]
120pub struct SubscriptionRequest<T> {
121    pub method: String,
122    pub params: T,
123    pub req_id: Option<i64>,
124}
125
126#[skip_serializing_none]
127#[derive(Debug, Serialize, Clone)]
128pub struct ExecutionSubscription {
129    pub channel: String,
130    pub token: Token,
131    #[serde(rename = "snap_trades")]
132    pub snapshot_trades: Option<bool>,
133    #[serde(rename = "snap_orders")]
134    pub snapshot_orders: Option<bool>,
135    pub rate_counter: Option<bool>,
136}
137
138impl ExecutionSubscription {
139    pub fn new(token: Token) -> Self {
140        ExecutionSubscription {
141            channel: "executions".to_string(),
142            token,
143            snapshot_trades: None,
144            snapshot_orders: None,
145            rate_counter: None,
146        }
147    }
148}
149
150#[derive(Debug, Deserialize, PartialEq)]
151#[serde(deny_unknown_fields)]
152pub struct InstrumentSubscriptionResult {
153    pub snapshot: Option<bool>,
154    pub warnings: Option<Vec<String>>,
155}
156
157#[derive(Debug, Deserialize, PartialEq)]
158pub struct ExecutionsSubscriptionResult {
159    #[serde(rename = "maxratecount")]
160    pub max_rate_count: Option<i64>,
161    pub snapshot: Option<bool>,
162    pub warnings: Option<Vec<String>>,
163}
164
165#[derive(Debug, Deserialize, PartialEq)]
166pub struct BalanceSubscriptionResult {
167    pub snapshot: Option<bool>,
168    pub warnings: Option<Vec<String>>,
169}
170
171#[derive(Debug, Deserialize, PartialEq)]
172#[serde(tag = "channel")]
173pub enum SubscriptionResult {
174    #[serde(rename = "level3")]
175    L3(BookSubscriptionResponse),
176    #[serde(rename = "book")]
177    Book(BookSubscriptionResponse),
178    #[serde(rename = "ticker")]
179    Ticker(TickerSubscriptionResponse),
180    #[serde(rename = "ohlc")]
181    Ohlc(OhlcSubscriptionResponse),
182    #[serde(rename = "trade")]
183    Trade(TradeSubscriptionResponse),
184    #[serde(rename = "executions")]
185    Execution(ExecutionsSubscriptionResult),
186    #[serde(rename = "balances")]
187    Balance(BalanceSubscriptionResult),
188    #[serde(rename = "instrument")]
189    Instrument(InstrumentSubscriptionResult),
190}
191
192#[derive(Debug, Deserialize)]
193pub struct ExecutionResponse {
194    pub channel: String,
195    #[serde(rename = "type")]
196    pub execution_response_type: ExecutionResponseType,
197}
198
199#[derive(Debug, Deserialize, PartialEq)]
200pub struct Fee {
201    pub asset: String,
202    #[serde(rename = "qty")]
203    pub quantity: Decimal,
204}
205
206#[derive(Debug, Deserialize, PartialEq)]
207pub struct TriggerDescription {
208    pub reference: TriggerType,
209    pub price: Decimal,
210    pub price_type: PriceType,
211    pub actual_price: Option<Decimal>,
212    pub peak_price: Option<Decimal>,
213    pub last_price: Option<Decimal>,
214    pub status: TriggerStatus,
215    pub timestamp: Option<String>,
216}
217
218#[derive(Debug, Deserialize, PartialEq)]
219pub struct ExecutionResult {
220    pub amended: Option<bool>,
221    #[serde(rename = "exec_type")]
222    pub execution_type: ExecutionType,
223    #[serde(rename = "cash_order_qty")]
224    pub cash_order_quantity: Option<Decimal>,
225    #[serde(rename = "cl_ord_id")]
226    pub client_order_id: Option<String>,
227    pub contingent: Option<ConditionalParams>,
228    pub cost: Option<Decimal>,
229    #[serde(rename = "exec_id")]
230    pub execution_id: Option<String>,
231    pub fees: Option<Vec<Fee>>,
232    #[serde(rename = "liquidity_ind")]
233    pub liquidity_indicator: Option<MakerTaker>,
234    pub last_price: Option<Decimal>,
235    #[serde(rename = "last_qty")]
236    pub last_quantity: Option<Decimal>,
237    #[serde(rename = "avg_price")]
238    pub average_price: Option<Decimal>,
239    pub reason: Option<String>,
240    #[serde(rename = "cum_cost")]
241    pub cumulative_cost: Option<Decimal>,
242    #[serde(rename = "cum_qty")]
243    pub cumulative_quantity: Option<Decimal>,
244    #[serde(rename = "display_qty")]
245    pub display_quantity: Option<Decimal>,
246    pub effective_time: Option<String>,
247    pub expire_time: Option<String>,
248    pub ext_ord_id: Option<String>,
249    pub ext_exec_id: Option<String>,
250    #[serde(rename = "fee_ccy_pref")]
251    pub fee_preference: Option<FeePreference>,
252    #[serde(rename = "fee_usd_equiv")]
253    pub fee_usd_equivalent: Option<Decimal>,
254    pub limit_price: Option<Decimal>,
255    pub limit_price_type: Option<PriceType>,
256    pub liquidated: Option<bool>,
257    pub margin: Option<bool>,
258    pub margin_borrow: Option<bool>,
259    #[serde(rename = "no_mpp")]
260    pub no_market_price_protection: Option<bool>,
261    #[serde(rename = "ord_ref_id")]
262    pub order_ref_id: Option<i64>,
263    pub order_id: String,
264    #[serde(rename = "order_qty")]
265    pub order_quantity: Option<Decimal>,
266    pub order_type: Option<OrderType>,
267    pub order_status: OrderStatusV2,
268    #[serde(rename = "order_userref")]
269    pub order_user_ref: Option<i64>,
270    pub post_only: Option<bool>,
271    pub position_status: Option<PositionStatusV2>,
272    pub reduce_only: Option<bool>,
273    pub sender_sub_id: Option<String>,
274    pub side: Option<BuySell>,
275    pub symbol: Option<String>,
276    pub time_in_force: Option<TimeInForce>,
277    pub timestamp: String,
278    pub trade_id: Option<i64>,
279    pub triggers: Option<TriggerDescription>,
280}
281
282#[skip_serializing_none]
283#[derive(Debug, Serialize, Clone)]
284pub struct BalancesSubscription {
285    pub channel: String,
286    pub token: Token,
287    pub snapshot: Option<bool>,
288}
289
290impl BalancesSubscription {
291    pub fn new(token: Token) -> Self {
292        BalancesSubscription {
293            channel: "balances".to_string(),
294            token,
295            snapshot: None,
296        }
297    }
298}
299
300#[derive(Debug, Deserialize, PartialEq)]
301pub struct Wallet {
302    pub balance: Decimal,
303    #[serde(rename = "type")]
304    pub wallet_type: WalletType,
305    pub id: WalletId,
306}
307
308#[derive(Debug, Deserialize, PartialEq)]
309#[serde(untagged)]
310pub enum BalanceResponse {
311    Update(Vec<LedgerUpdate>),
312    Snapshot(Vec<Balance>),
313}
314
315#[derive(Debug, Deserialize, PartialEq)]
316pub struct Balance {
317    pub asset: String,
318    pub balance: Decimal,
319    pub wallets: Vec<Wallet>,
320}
321
322#[derive(Debug, Deserialize, PartialEq)]
323pub struct LedgerUpdate {
324    pub asset: String,
325    pub amount: Decimal,
326    pub balance: Decimal,
327    pub fee: Decimal,
328    pub ledger_id: String,
329    pub ref_id: String,
330    pub timestamp: String,
331    pub asset_class: String,
332    #[serde(rename = "type")]
333    pub ledger_type: LedgerEntryTypeV2,
334    pub sub_type: Option<LedgerEntrySubType>,
335    pub category: LedgerCategory,
336    pub wallet_type: WalletType,
337    pub wallet_id: WalletId,
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use rust_decimal_macros::dec;
344
345    #[test]
346    fn test_deserializing_execution_trade() {
347        let message = r#"{"order_id":"O7IBL5-O2V6X-EEXY4U","exec_id":"TJE7HC-DKBTI-5BFVKE","exec_type":"trade","ext_ord_id":"some-uuid","ext_exec_id":"another-uuid","trade_id":365573,"symbol":"KAR/USD","side":"buy","last_qty":105.02014889,"last_price":0.121,"liquidity_ind":"t","cost":12.70744,"order_status":"filled","order_type":"limit","timestamp":"2024-05-18T05:41:33.480251Z","fee_usd_equiv":0.05083,"fees":[{"asset":"USD","qty":0.05083}]}"#;
348        let expected = ExecutionResult {
349            amended: None,
350            execution_type: ExecutionType::Trade,
351            cash_order_quantity: None,
352            contingent: None,
353            cost: Some(dec!(12.70744)),
354            execution_id: Some("TJE7HC-DKBTI-5BFVKE".to_string()),
355            fees: Some(vec![Fee {
356                asset: "USD".to_string(),
357                quantity: dec!(0.05083),
358            }]),
359            liquidity_indicator: Some(MakerTaker::Taker),
360            last_price: Some(dec!(0.121)),
361            last_quantity: Some(dec!(105.02014889)),
362            average_price: None,
363            reason: None,
364            cumulative_cost: None,
365            cumulative_quantity: None,
366            display_quantity: None,
367            effective_time: None,
368            expire_time: None,
369            ext_ord_id: Some("some-uuid".to_string()),
370            ext_exec_id: Some("another-uuid".to_string()),
371            fee_preference: None,
372            fee_usd_equivalent: Some(dec!(0.05083)),
373            limit_price: None,
374            limit_price_type: None,
375            liquidated: None,
376            margin: None,
377            margin_borrow: None,
378            no_market_price_protection: None,
379            order_ref_id: None,
380            order_id: "O7IBL5-O2V6X-EEXY4U".to_string(),
381            order_quantity: None,
382            order_type: Some(OrderType::Limit),
383            order_status: OrderStatusV2::Filled,
384            order_user_ref: None,
385            post_only: None,
386            position_status: None,
387            reduce_only: None,
388            sender_sub_id: None,
389            side: Some(BuySell::Buy),
390            symbol: Some("KAR/USD".to_string()),
391            time_in_force: None,
392            timestamp: "2024-05-18T05:41:33.480251Z".to_string(),
393            trade_id: Some(365573),
394            triggers: None,
395            client_order_id: None,
396        };
397        let parsed: ExecutionResult = serde_json::from_str(message).unwrap();
398
399        assert_eq!(expected, parsed);
400    }
401
402    #[test]
403    fn test_deserializing_execution_new_update() {
404        let message = r#"{"timestamp":"2024-05-18T11:00:37.240691Z","order_status":"new","exec_type":"new","order_userref":0,"order_id":"OLADEP-E5D5S-IKEHMF"}"#;
405        let expected = ExecutionResult {
406            amended: None,
407            execution_type: ExecutionType::New,
408            cash_order_quantity: None,
409            contingent: None,
410            cost: None,
411            execution_id: None,
412            fees: None,
413            liquidity_indicator: None,
414            last_price: None,
415            last_quantity: None,
416            average_price: None,
417            reason: None,
418            cumulative_cost: None,
419            cumulative_quantity: None,
420            display_quantity: None,
421            effective_time: None,
422            expire_time: None,
423            ext_ord_id: None,
424            ext_exec_id: None,
425            fee_preference: None,
426            fee_usd_equivalent: None,
427            limit_price: None,
428            limit_price_type: None,
429            liquidated: None,
430            margin: None,
431            margin_borrow: None,
432            no_market_price_protection: None,
433            order_ref_id: None,
434            order_id: "OLADEP-E5D5S-IKEHMF".to_string(),
435            order_quantity: None,
436            order_type: None,
437            order_status: OrderStatusV2::New,
438            order_user_ref: Some(0),
439            post_only: None,
440            position_status: None,
441            reduce_only: None,
442            sender_sub_id: None,
443            side: None,
444            symbol: None,
445            time_in_force: None,
446            timestamp: "2024-05-18T11:00:37.240691Z".to_string(),
447            trade_id: None,
448            triggers: None,
449            client_order_id: None,
450        };
451        let parsed: ExecutionResult = serde_json::from_str(message).unwrap();
452
453        assert_eq!(expected, parsed);
454    }
455}