kraken_async_rs/clients/
rate_limited_kraken_client.rs

1//! A rate-limited [KrakenClient]
2use crate::clients::errors::ClientError;
3use crate::clients::http_response_types::ResultErrorResponse;
4use crate::clients::kraken_client::KrakenClient;
5use crate::crypto::nonce_provider::NonceProvider;
6use crate::rate_limiting::keyed_rate_limits::KeyedRateLimiter;
7use crate::rate_limiting::trading_rate_limits::KrakenTradingRateLimiter;
8use crate::request_types::*;
9use crate::response_types::*;
10use crate::secrets::secrets_provider::SecretsProvider;
11use async_rate_limit::limiters::{RateLimiter, VariableCostRateLimiter};
12use async_rate_limit::sliding_window::SlidingWindowRateLimiter;
13use async_rate_limit::token_bucket::{TokenBucketRateLimiter, TokenBucketState};
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::time::Duration;
17use time::OffsetDateTime;
18use tokio::sync::Mutex;
19
20/// A [KrakenClient] implementation that decorates a provided client, and applies rate limiting
21/// according to the Kraken API specs.
22///
23/// Loosely, this is:
24/// - public endpoints are limited to 1 call per second
25/// - private endpoints follow a token-bucket rate limiting scheme, with some endpoints having higher costs
26/// - trading endpoints implement the Advanced version of Kraken's rate limiting scheme
27///     - this includes tracking order lifetimes and applying penalties to rapid cancels and edits of orders
28///
29/// The exact rate limit values and replenishment schedule are determined by a user's
30/// verification tier. Default new methods assume an `Intermediate` verification, so `Pro` users will
31/// want to rely on methods that allow providing a custom verification tier if they want to take full
32/// advantage of their increased rate limits (e.g. `new_with_verification_tier`).
33///
34/// Calls made that violate the rate limiting policy are made to wait asynchronously, but no error handling
35/// is in place for receiving rate limit errors, these are to be handled/backed-off by the user.
36///
37/// Detailed documentation is available from several locations, including the [overview rate-limiting page],
38/// [api rate-limiting page] and [trading rate-limiting page]. It's worth noting that the token
39/// values used in this library are scaled to be 100x those of Kraken's documentation to keep them
40/// as integers using semaphore permits instead of floating-point math.
41///
42/// [`RateLimitedKrakenClient`]s are cloneable, which results in a new client that shares the same
43/// rate limiting state. This is useful for giving many services access to a client while ensuring
44/// that all will jointly respect the rate limits of the exchange.
45///
46/// *Warning: This is not meant to be a comprehensive solution to all rate limiting, but is a best-effort
47/// attempt to match the API's specifications. In some cases cloud providers, or server implementations
48/// may inject random errors to prevent coordinated attacks or abuse. As such, this cannot anticipate
49/// and mitigate all modes of failure.*
50///
51/// See examples/live_retrieving_recent_trades.rs for usage that relies on rate limiting preventing
52/// request failures due to rapidly requesting public trade history.
53///
54/// [overview rate-limiting page]: https://docs.kraken.com/rest/#section/Rate-Limits/Matching-Engine-Rate-Limits
55/// [api rate-limiting page]: https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits-#3
56/// [trading rate-limiting page]: https://support.kraken.com/hc/en-us/articles/360045239571-Trading-rate-limits
57#[derive(Debug, Clone)]
58pub struct RateLimitedKrakenClient<C>
59where
60    C: KrakenClient,
61{
62    core_client: C,
63    private_rate_limiter: TokenBucketRateLimiter,
64    public_rate_limiter: SlidingWindowRateLimiter,
65    trading_rate_limiter: KrakenTradingRateLimiter,
66    pair_rate_limiter: KeyedRateLimiter<String>,
67}
68
69impl<C> KrakenClient for RateLimitedKrakenClient<C>
70where
71    C: KrakenClient,
72{
73    fn new(
74        secrets_provider: Box<Arc<Mutex<dyn SecretsProvider>>>,
75        nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>>,
76    ) -> RateLimitedKrakenClient<C> {
77        RateLimitedKrakenClient {
78            core_client: C::new(secrets_provider, nonce_provider),
79            private_rate_limiter: Self::get_private_rate_limiter(VerificationTier::Intermediate),
80            public_rate_limiter: Self::get_public_rate_limiter(),
81            trading_rate_limiter: KrakenTradingRateLimiter::new(VerificationTier::Intermediate),
82            pair_rate_limiter: KeyedRateLimiter::new(),
83        }
84    }
85
86    fn new_with_url(
87        secrets_provider: Box<Arc<Mutex<dyn SecretsProvider>>>,
88        nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>>,
89        url: impl ToString,
90    ) -> Self {
91        RateLimitedKrakenClient {
92            core_client: C::new_with_url(secrets_provider, nonce_provider, url),
93            private_rate_limiter: Self::get_private_rate_limiter(VerificationTier::Intermediate),
94            public_rate_limiter: Self::get_public_rate_limiter(),
95            trading_rate_limiter: KrakenTradingRateLimiter::new(VerificationTier::Intermediate),
96            pair_rate_limiter: KeyedRateLimiter::new(),
97        }
98    }
99
100    fn new_with_tracing(
101        secrets_provider: Box<Arc<Mutex<dyn SecretsProvider>>>,
102        nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>>,
103        trace_inbound: bool,
104    ) -> Self {
105        RateLimitedKrakenClient {
106            core_client: C::new_with_tracing(secrets_provider, nonce_provider, trace_inbound),
107            private_rate_limiter: Self::get_private_rate_limiter(VerificationTier::Intermediate),
108            public_rate_limiter: Self::get_public_rate_limiter(),
109            trading_rate_limiter: KrakenTradingRateLimiter::new(VerificationTier::Intermediate),
110            pair_rate_limiter: KeyedRateLimiter::new(),
111        }
112    }
113
114    async fn set_user_agent(&mut self, user_agent: impl ToString) {
115        self.core_client.set_user_agent(user_agent).await;
116    }
117
118    async fn get_server_time(&mut self) -> Result<ResultErrorResponse<SystemTime>, ClientError> {
119        self.public_rate_limiter.wait_until_ready().await;
120        self.core_client.get_server_time().await
121    }
122
123    async fn get_system_status(
124        &mut self,
125    ) -> Result<ResultErrorResponse<SystemStatusInfo>, ClientError> {
126        self.public_rate_limiter.wait_until_ready().await;
127        self.core_client.get_system_status().await
128    }
129
130    async fn get_asset_info(
131        &mut self,
132        request: &AssetInfoRequest,
133    ) -> Result<ResultErrorResponse<HashMap<String, AssetInfo>>, ClientError> {
134        self.public_rate_limiter.wait_until_ready().await;
135        self.core_client.get_asset_info(request).await
136    }
137
138    async fn get_tradable_asset_pairs(
139        &mut self,
140        request: &TradableAssetPairsRequest,
141    ) -> Result<ResultErrorResponse<HashMap<String, TradableAssetPair>>, ClientError> {
142        self.public_rate_limiter.wait_until_ready().await;
143        self.core_client.get_tradable_asset_pairs(request).await
144    }
145
146    async fn get_ticker_information(
147        &mut self,
148        request: &TickerRequest,
149    ) -> Result<ResultErrorResponse<HashMap<String, RestTickerInfo>>, ClientError> {
150        self.public_rate_limiter.wait_until_ready().await;
151        self.core_client.get_ticker_information(request).await
152    }
153
154    async fn get_ohlc(
155        &mut self,
156        request: &OHLCRequest,
157    ) -> Result<ResultErrorResponse<OhlcResponse>, ClientError> {
158        self.pair_rate_limiter
159            .wait_until_ready(request.pair.clone())
160            .await;
161        self.core_client.get_ohlc(request).await
162    }
163
164    async fn get_orderbook(
165        &mut self,
166        request: &OrderbookRequest,
167    ) -> Result<ResultErrorResponse<HashMap<String, Orderbook>>, ClientError> {
168        self.public_rate_limiter.wait_until_ready().await;
169        self.core_client.get_orderbook(request).await
170    }
171
172    async fn get_recent_trades(
173        &mut self,
174        request: &RecentTradesRequest,
175    ) -> Result<ResultErrorResponse<RecentTrades>, ClientError> {
176        self.pair_rate_limiter
177            .wait_until_ready(request.pair.clone())
178            .await;
179        self.public_rate_limiter.wait_until_ready().await;
180        self.core_client.get_recent_trades(request).await
181    }
182
183    async fn get_recent_spreads(
184        &mut self,
185        request: &RecentSpreadsRequest,
186    ) -> Result<ResultErrorResponse<RecentSpreads>, ClientError> {
187        self.public_rate_limiter.wait_until_ready().await;
188        self.core_client.get_recent_spreads(request).await
189    }
190
191    async fn get_account_balance(
192        &mut self,
193    ) -> Result<ResultErrorResponse<AccountBalances>, ClientError> {
194        self.private_rate_limit(100).await;
195        self.core_client.get_account_balance().await
196    }
197
198    async fn get_extended_balances(
199        &mut self,
200    ) -> Result<ResultErrorResponse<ExtendedBalances>, ClientError> {
201        self.private_rate_limit(100).await;
202        self.core_client.get_extended_balances().await
203    }
204
205    async fn get_trade_balances(
206        &mut self,
207        request: &TradeBalanceRequest,
208    ) -> Result<ResultErrorResponse<TradeBalances>, ClientError> {
209        self.private_rate_limit(100).await;
210        self.core_client.get_trade_balances(request).await
211    }
212
213    async fn get_open_orders(
214        &mut self,
215        request: &OpenOrdersRequest,
216    ) -> Result<ResultErrorResponse<OpenOrders>, ClientError> {
217        self.private_rate_limit(100).await;
218        self.core_client.get_open_orders(request).await
219    }
220
221    async fn get_closed_orders(
222        &mut self,
223        request: &ClosedOrdersRequest,
224    ) -> Result<ResultErrorResponse<ClosedOrders>, ClientError> {
225        self.private_rate_limit(200).await;
226        self.core_client.get_closed_orders(request).await
227    }
228
229    async fn query_orders_info(
230        &mut self,
231        request: &OrderRequest,
232    ) -> Result<ResultErrorResponse<HashMap<String, Order>>, ClientError> {
233        self.private_rate_limit(100).await;
234        self.core_client.query_orders_info(request).await
235    }
236
237    async fn get_order_amends(
238        &mut self,
239        request: &OrderAmendsRequest,
240    ) -> Result<ResultErrorResponse<OrderAmends>, ClientError> {
241        self.private_rate_limiter.wait_with_cost(100).await;
242        self.core_client.get_order_amends(request).await
243    }
244
245    async fn get_trades_history(
246        &mut self,
247        request: &TradesHistoryRequest,
248    ) -> Result<ResultErrorResponse<TradesHistory>, ClientError> {
249        self.private_rate_limit(200).await;
250        self.core_client.get_trades_history(request).await
251    }
252
253    async fn query_trades_info(
254        &mut self,
255        request: &TradeInfoRequest,
256    ) -> Result<ResultErrorResponse<TradesInfo>, ClientError> {
257        self.private_rate_limit(100).await;
258        self.core_client.query_trades_info(request).await
259    }
260
261    async fn get_open_positions(
262        &mut self,
263        request: &OpenPositionsRequest,
264    ) -> Result<ResultErrorResponse<OpenPositions>, ClientError> {
265        self.private_rate_limit(100).await;
266        self.core_client.get_open_positions(request).await
267    }
268
269    async fn get_ledgers_info(
270        &mut self,
271        request: &LedgersInfoRequest,
272    ) -> Result<ResultErrorResponse<LedgerInfo>, ClientError> {
273        self.private_rate_limit(200).await;
274        self.core_client.get_ledgers_info(request).await
275    }
276
277    async fn query_ledgers(
278        &mut self,
279        request: &QueryLedgerRequest,
280    ) -> Result<ResultErrorResponse<QueryLedgerInfo>, ClientError> {
281        self.private_rate_limit(100).await;
282        self.core_client.query_ledgers(request).await
283    }
284
285    async fn get_trade_volume(
286        &mut self,
287        request: &TradeVolumeRequest,
288    ) -> Result<ResultErrorResponse<TradeVolume>, ClientError> {
289        self.private_rate_limit(100).await;
290        self.core_client.get_trade_volume(request).await
291    }
292
293    async fn request_export_report(
294        &mut self,
295        request: &ExportReportRequest,
296    ) -> Result<ResultErrorResponse<ExportReport>, ClientError> {
297        self.private_rate_limit(100).await;
298        self.core_client.request_export_report(request).await
299    }
300
301    async fn get_export_report_status(
302        &mut self,
303        request: &ExportReportStatusRequest,
304    ) -> Result<ResultErrorResponse<Vec<ExportReportStatus>>, ClientError> {
305        self.private_rate_limit(100).await;
306        self.core_client.get_export_report_status(request).await
307    }
308
309    async fn retrieve_export_report(
310        &mut self,
311        request: &RetrieveExportReportRequest,
312    ) -> Result<Vec<u8>, ClientError> {
313        self.private_rate_limit(100).await;
314        self.core_client.retrieve_export_report(request).await
315    }
316
317    async fn delete_export_report(
318        &mut self,
319        request: &DeleteExportRequest,
320    ) -> Result<ResultErrorResponse<DeleteExportReport>, ClientError> {
321        self.private_rate_limit(100).await;
322        self.core_client.delete_export_report(request).await
323    }
324
325    async fn add_order(
326        &mut self,
327        request: &AddOrderRequest,
328    ) -> Result<ResultErrorResponse<AddOrder>, ClientError> {
329        self.trading_rate_limiter.add_order().await;
330        let response = self.core_client.add_order(request).await;
331        self.notify_add_order(&response, request.user_ref, &request.client_order_id)
332            .await;
333
334        response
335    }
336
337    async fn add_order_batch(
338        &mut self,
339        request: &AddBatchedOrderRequest,
340    ) -> Result<ResultErrorResponse<AddOrderBatch>, ClientError> {
341        self.trading_rate_limiter.add_order_batch(request).await;
342        let response = self.core_client.add_order_batch(request).await;
343        self.notify_add_order_batched(&response, request).await;
344
345        response
346    }
347
348    async fn amend_order(
349        &mut self,
350        request: &AmendOrderRequest,
351    ) -> Result<ResultErrorResponse<AmendOrder>, ClientError> {
352        self.trading_rate_limiter
353            .amend_order(&request.tx_id, &request.client_order_id)
354            .await;
355        let response = self.core_client.amend_order(request).await;
356        self.notify_amend_order(&request.tx_id, &request.client_order_id.clone())
357            .await;
358
359        response
360    }
361
362    async fn edit_order(
363        &mut self,
364        request: &EditOrderRequest,
365    ) -> Result<ResultErrorResponse<OrderEdit>, ClientError> {
366        self.trading_rate_limiter.edit_order(request).await;
367        let response = self.core_client.edit_order(request).await;
368        self.notify_edit_order(&response, request.user_ref).await;
369        response
370    }
371
372    async fn cancel_order(
373        &mut self,
374        request: &CancelOrderRequest,
375    ) -> Result<ResultErrorResponse<CancelOrder>, ClientError> {
376        match &request.tx_id {
377            IntOrString::Int(i) => {
378                self.trading_rate_limiter.cancel_order_user_ref(i).await;
379            }
380            IntOrString::String(s) => {
381                self.trading_rate_limiter.cancel_order_tx_id(s).await;
382            }
383        }
384
385        self.core_client.cancel_order(request).await
386    }
387
388    async fn cancel_all_orders(&mut self) -> Result<ResultErrorResponse<CancelOrder>, ClientError> {
389        self.core_client.cancel_all_orders().await
390    }
391
392    async fn cancel_all_orders_after(
393        &mut self,
394        request: &CancelAllOrdersAfterRequest,
395    ) -> Result<ResultErrorResponse<CancelAllOrdersAfter>, ClientError> {
396        self.core_client.cancel_all_orders_after(request).await
397    }
398
399    /// Clients can request to cancel in batches using both ref-ids produced by Kraken (Strings), or
400    /// user-refs generated by the user (i64), which are known before the order is placed.
401    async fn cancel_order_batch(
402        &mut self,
403        request: &CancelBatchOrdersRequest,
404    ) -> Result<ResultErrorResponse<CancelOrder>, ClientError> {
405        for order in &request.orders {
406            match order {
407                IntOrString::Int(user_ref) => {
408                    self.trading_rate_limiter
409                        .cancel_order_user_ref(user_ref)
410                        .await
411                }
412                IntOrString::String(tx_id) => {
413                    self.trading_rate_limiter.cancel_order_tx_id(tx_id).await
414                }
415            }
416        }
417
418        self.core_client.cancel_order_batch(request).await
419    }
420
421    async fn get_deposit_methods(
422        &mut self,
423        request: &DepositMethodsRequest,
424    ) -> Result<ResultErrorResponse<Vec<DepositMethod>>, ClientError> {
425        self.private_rate_limit(100).await;
426        self.core_client.get_deposit_methods(request).await
427    }
428
429    async fn get_deposit_addresses(
430        &mut self,
431        request: &DepositAddressesRequest,
432    ) -> Result<ResultErrorResponse<Vec<DepositAddress>>, ClientError> {
433        self.private_rate_limit(100).await;
434        self.core_client.get_deposit_addresses(request).await
435    }
436
437    async fn get_status_of_recent_deposits(
438        &mut self,
439        request: &StatusOfDepositWithdrawRequest,
440    ) -> Result<ResultErrorResponse<DepositWithdrawResponse>, ClientError> {
441        self.private_rate_limit(100).await;
442        self.core_client
443            .get_status_of_recent_deposits(request)
444            .await
445    }
446
447    async fn get_withdrawal_methods(
448        &mut self,
449        request: &WithdrawalMethodsRequest,
450    ) -> Result<ResultErrorResponse<Vec<WithdrawMethod>>, ClientError> {
451        self.private_rate_limit(100).await;
452        self.core_client.get_withdrawal_methods(request).await
453    }
454
455    async fn get_withdrawal_addresses(
456        &mut self,
457        request: &WithdrawalAddressesRequest,
458    ) -> Result<ResultErrorResponse<Vec<WithdrawalAddress>>, ClientError> {
459        self.private_rate_limit(100).await;
460        self.core_client.get_withdrawal_addresses(request).await
461    }
462
463    async fn get_withdrawal_info(
464        &mut self,
465        request: &WithdrawalInfoRequest,
466    ) -> Result<ResultErrorResponse<Withdrawal>, ClientError> {
467        self.private_rate_limit(100).await;
468        self.core_client.get_withdrawal_info(request).await
469    }
470
471    async fn withdraw_funds(
472        &mut self,
473        request: &WithdrawFundsRequest,
474    ) -> Result<ResultErrorResponse<ConfirmationRefId>, ClientError> {
475        self.private_rate_limit(100).await;
476        self.core_client.withdraw_funds(request).await
477    }
478
479    async fn get_status_of_recent_withdrawals(
480        &mut self,
481        request: &StatusOfDepositWithdrawRequest,
482    ) -> Result<ResultErrorResponse<Vec<DepositWithdrawal>>, ClientError> {
483        self.private_rate_limit(100).await;
484        self.core_client
485            .get_status_of_recent_withdrawals(request)
486            .await
487    }
488
489    async fn request_withdrawal_cancellation(
490        &mut self,
491        request: &WithdrawCancelRequest,
492    ) -> Result<ResultErrorResponse<bool>, ClientError> {
493        self.private_rate_limit(100).await;
494        self.core_client
495            .request_withdrawal_cancellation(request)
496            .await
497    }
498
499    async fn request_wallet_transfer(
500        &mut self,
501        request: &WalletTransferRequest,
502    ) -> Result<ResultErrorResponse<ConfirmationRefId>, ClientError> {
503        self.private_rate_limit(100).await;
504        self.core_client.request_wallet_transfer(request).await
505    }
506
507    async fn create_sub_account(
508        &mut self,
509        request: &CreateSubAccountRequest,
510    ) -> Result<ResultErrorResponse<bool>, ClientError> {
511        self.private_rate_limit(100).await;
512        self.core_client.create_sub_account(request).await
513    }
514
515    async fn account_transfer(
516        &mut self,
517        request: &AccountTransferRequest,
518    ) -> Result<ResultErrorResponse<AccountTransfer>, ClientError> {
519        self.private_rate_limit(100).await;
520        self.core_client.account_transfer(request).await
521    }
522
523    async fn allocate_earn_funds(
524        &mut self,
525        request: &AllocateEarnFundsRequest,
526    ) -> Result<ResultErrorResponse<bool>, ClientError> {
527        self.private_rate_limit(100).await;
528        self.core_client.allocate_earn_funds(request).await
529    }
530
531    async fn deallocate_earn_funds(
532        &mut self,
533        request: &AllocateEarnFundsRequest,
534    ) -> Result<ResultErrorResponse<bool>, ClientError> {
535        self.private_rate_limit(100).await;
536        self.core_client.deallocate_earn_funds(request).await
537    }
538
539    async fn get_earn_allocation_status(
540        &mut self,
541        request: &EarnAllocationStatusRequest,
542    ) -> Result<ResultErrorResponse<AllocationStatus>, ClientError> {
543        self.private_rate_limit(100).await;
544        self.core_client.get_earn_allocation_status(request).await
545    }
546
547    async fn get_earn_deallocation_status(
548        &mut self,
549        request: &EarnAllocationStatusRequest,
550    ) -> Result<ResultErrorResponse<AllocationStatus>, ClientError> {
551        self.private_rate_limit(100).await;
552        self.core_client.get_earn_deallocation_status(request).await
553    }
554
555    async fn list_earn_strategies(
556        &mut self,
557        request: &ListEarnStrategiesRequest,
558    ) -> Result<ResultErrorResponse<EarnStrategies>, ClientError> {
559        self.private_rate_limit(100).await;
560        self.core_client.list_earn_strategies(request).await
561    }
562
563    async fn list_earn_allocations(
564        &mut self,
565        request: &ListEarnAllocationsRequest,
566    ) -> Result<ResultErrorResponse<EarnAllocations>, ClientError> {
567        self.private_rate_limit(100).await;
568        self.core_client.list_earn_allocations(request).await
569    }
570
571    async fn get_websockets_token(
572        &mut self,
573    ) -> Result<ResultErrorResponse<WebsocketToken>, ClientError> {
574        self.private_rate_limit(100).await;
575        self.core_client.get_websockets_token().await
576    }
577}
578
579impl<C> RateLimitedKrakenClient<C>
580where
581    C: KrakenClient,
582{
583    /// Notify the trading rate limiter that an order was created at this time, this timestamp is
584    /// used for determining the order's lifetime for edit and cancel penalties.
585    async fn notify_add_order(
586        &mut self,
587        order_response: &Result<ResultErrorResponse<AddOrder>, ClientError>,
588        user_ref: Option<i64>,
589        client_order_id: &Option<String>,
590    ) {
591        if let Ok(ResultErrorResponse {
592            result: Some(result),
593            ..
594        }) = order_response
595        {
596            for tx_id in &result.tx_id {
597                self.trading_rate_limiter
598                    .notify_add_order(
599                        tx_id.clone(),
600                        OffsetDateTime::now_utc().unix_timestamp(),
601                        user_ref,
602                        client_order_id,
603                    )
604                    .await;
605            }
606        }
607    }
608
609    /// Notify the trading rate limiter that an order was amended at this time, this timestamp is
610    /// used for determining the order's lifetime for amend, edit, and cancel penalties.
611    async fn notify_amend_order(
612        &mut self,
613        tx_id: &Option<String>,
614        client_order_id: &Option<String>,
615    ) {
616        self.trading_rate_limiter
617            .notify_amend_order(
618                tx_id,
619                OffsetDateTime::now_utc().unix_timestamp(),
620                client_order_id,
621            )
622            .await;
623    }
624
625    /// Notify the trading rate limiter of all orders created in this batch so it can determine
626    /// order lifetimes for edit and cancel penalties.
627    async fn notify_add_order_batched(
628        &mut self,
629        order_response: &Result<ResultErrorResponse<AddOrderBatch>, ClientError>,
630        request: &AddBatchedOrderRequest,
631    ) {
632        if let Ok(ResultErrorResponse {
633            result: Some(result),
634            ..
635        }) = order_response
636        {
637            for (order, request) in result.orders.iter().zip(request.orders.iter()) {
638                self.trading_rate_limiter
639                    .notify_add_order(
640                        order.tx_id.clone(),
641                        OffsetDateTime::now_utc().unix_timestamp(),
642                        request.user_ref,
643                        &request.client_order_id,
644                    )
645                    .await
646            }
647        }
648    }
649
650    /// Notify the trading rate limiter of the edited order, since the new order has a fresh order
651    /// lifetime.
652    async fn notify_edit_order(
653        &mut self,
654        order_response: &Result<ResultErrorResponse<OrderEdit>, ClientError>,
655        user_ref: Option<i64>,
656    ) {
657        if let Ok(ResultErrorResponse {
658            result: Some(result),
659            ..
660        }) = order_response
661        {
662            self.trading_rate_limiter
663                .notify_add_order(
664                    result.tx_id.clone(),
665                    OffsetDateTime::now_utc().unix_timestamp(),
666                    user_ref,
667                    &None,
668                )
669                .await
670        }
671    }
672}
673
674impl<C> RateLimitedKrakenClient<C>
675where
676    C: KrakenClient,
677{
678    /// Create a new rate limited client that delegates calls to any type that implements [KrakenClient].
679    pub fn new_with_client(
680        client: C,
681        verification: VerificationTier,
682    ) -> RateLimitedKrakenClient<C> {
683        RateLimitedKrakenClient {
684            core_client: client,
685            private_rate_limiter: Self::get_private_rate_limiter(verification),
686            public_rate_limiter: Self::get_public_rate_limiter(),
687            trading_rate_limiter: KrakenTradingRateLimiter::new(verification),
688            pair_rate_limiter: KeyedRateLimiter::new(),
689        }
690    }
691
692    /// Create a new rate-limited client using the provided [SecretsProvider] and [NonceProvider]
693    pub fn new_with_verification_tier(
694        secrets_provider: Box<Arc<Mutex<dyn SecretsProvider>>>,
695        nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>>,
696        verification: VerificationTier,
697    ) -> Self {
698        RateLimitedKrakenClient {
699            core_client: C::new(secrets_provider, nonce_provider),
700            private_rate_limiter: Self::get_private_rate_limiter(verification),
701            public_rate_limiter: Self::get_public_rate_limiter(),
702            trading_rate_limiter: KrakenTradingRateLimiter::new(verification),
703            pair_rate_limiter: KeyedRateLimiter::new(),
704        }
705    }
706
707    /// Create a new client, specifying the user's verification tier and the base URL.
708    pub fn new_with_verification_tier_and_url(
709        secrets_provider: Box<Arc<Mutex<dyn SecretsProvider>>>,
710        nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>>,
711        url: String,
712        verification: VerificationTier,
713    ) -> Self {
714        RateLimitedKrakenClient {
715            core_client: C::new_with_url(secrets_provider, nonce_provider, url),
716            private_rate_limiter: Self::get_private_rate_limiter(verification),
717            public_rate_limiter: Self::get_public_rate_limiter(),
718            trading_rate_limiter: KrakenTradingRateLimiter::new(verification),
719            pair_rate_limiter: KeyedRateLimiter::new(),
720        }
721    }
722
723    /// Get a private endpoint rate limiter, depending on the user's verification level.
724    ///
725    /// This implements a more involved scheme.
726    pub fn get_private_rate_limiter(user_verification: VerificationTier) -> TokenBucketRateLimiter {
727        // tokens are scaled 100x from Kraken's floating-point method to keep as integers
728        match user_verification {
729            VerificationTier::Intermediate => {
730                let token_bucket_state = TokenBucketState::new(2000, 50, Duration::from_secs(1));
731                TokenBucketRateLimiter::new(Arc::new(Mutex::new(token_bucket_state)))
732            }
733            VerificationTier::Pro => {
734                let token_bucket_state = TokenBucketState::new(2000, 100, Duration::from_secs(1));
735                TokenBucketRateLimiter::new(Arc::new(Mutex::new(token_bucket_state)))
736            }
737        }
738    }
739
740    /// Get a public rate limiter, which limits calls to 1 per second.
741    pub fn get_public_rate_limiter() -> SlidingWindowRateLimiter {
742        SlidingWindowRateLimiter::new(Duration::from_secs(1), 1)
743    }
744
745    async fn private_rate_limit(&mut self, cost: usize) {
746        self.private_rate_limiter.wait_with_cost(cost).await
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use crate::clients::core_kraken_client::CoreKrakenClient;
753    use crate::clients::kraken_client::endpoints::KRAKEN_BASE_URL;
754    use crate::clients::kraken_client::KrakenClient;
755    use crate::clients::rate_limited_kraken_client::RateLimitedKrakenClient;
756    use crate::crypto::nonce_provider::{IncreasingNonceProvider, NonceProvider};
757    use crate::request_types::{
758        AccountTransferRequest, AddBatchedOrderRequest, AddOrderRequest, AllocateEarnFundsRequest,
759        AmendOrderRequest, AssetInfoRequestBuilder, BatchedOrderRequest, CancelBatchOrdersRequest,
760        CancelOrderRequest, CandlestickInterval, ClosedOrdersRequestBuilder,
761        CreateSubAccountRequest, DeleteExportRequest, DeleteExportType, DepositAddressesRequest,
762        DepositMethodsRequest, EarnAllocationStatusRequest, EditOrderRequest, ExportReportRequest,
763        ExportReportStatusRequest, IntOrString, LedgersInfoRequest, ListEarnAllocationsRequest,
764        ListEarnStrategiesRequest, OHLCRequest, OpenOrdersRequest, OpenPositionsRequest,
765        OrderFlags, OrderRequest, OrderbookRequest, QueryLedgerRequest, RecentSpreadsRequest,
766        RecentTradesRequest, ReportFormatType, ReportType, RetrieveExportReportRequest,
767        StatusOfDepositWithdrawRequest, StringCSV, TickerRequest, TradableAssetPairsRequest,
768        TradeBalanceRequest, TradeInfoRequest, TradeVolumeRequest, TradesHistoryRequest,
769        WalletTransferRequest, WithdrawCancelRequest, WithdrawFundsRequest,
770        WithdrawalAddressesRequest, WithdrawalInfoRequest, WithdrawalMethodsRequest,
771    };
772    use crate::response_types::VerificationTier::{Intermediate, Pro};
773    use crate::response_types::{AddOrder, BuySell, OrderFlag, OrderType, VerificationTier};
774    use crate::secrets::secrets_provider::StaticSecretsProvider;
775    use crate::test_data::public_response_json::get_server_time_json;
776    use crate::test_data::TestRateLimitedClient;
777    use crate::test_data::{
778        get_null_secrets_provider, get_rate_limit_test_client, get_rate_limit_test_client_err,
779    };
780    use crate::test_rate_limited_endpoint;
781    use rust_decimal_macros::dec;
782    use std::sync::Arc;
783    use std::time::Duration;
784    use tokio::sync::Mutex;
785    use tokio::time::pause;
786    use tokio::time::Instant;
787    use wiremock::matchers::{header, method, path};
788    use wiremock::{Mock, MockServer, ResponseTemplate};
789
790    #[test]
791    fn client_creates() {
792        let secrets_provider = StaticSecretsProvider::new("", "");
793        let nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>> =
794            Box::new(Arc::new(Mutex::new(IncreasingNonceProvider::new())));
795        let client: RateLimitedKrakenClient<CoreKrakenClient> = RateLimitedKrakenClient::new(
796            Box::new(Arc::new(Mutex::new(secrets_provider))),
797            nonce_provider,
798        );
799
800        assert_eq!(client.core_client.api_url, KRAKEN_BASE_URL);
801    }
802
803    #[tokio::test]
804    async fn client_user_agent() {
805        let secrets_provider = get_null_secrets_provider();
806        let nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>> =
807            Box::new(Arc::new(Mutex::new(IncreasingNonceProvider::new())));
808        let mock_server = MockServer::start().await;
809        let mut client: RateLimitedKrakenClient<CoreKrakenClient> =
810            RateLimitedKrakenClient::new_with_url(
811                secrets_provider,
812                nonce_provider,
813                mock_server.uri(),
814            );
815
816        Mock::given(method("GET"))
817            .and(path("/0/public/Time"))
818            .and(header("user-agent", "KrakenAsyncRsClient"))
819            .respond_with(ResponseTemplate::new(200).set_body_json(get_server_time_json()))
820            .expect(1)
821            .mount(&mock_server)
822            .await;
823
824        let _resp = client.get_server_time().await;
825        mock_server.verify().await;
826
827        client.set_user_agent("Strategy#1".to_string()).await;
828
829        Mock::given(method("GET"))
830            .and(path("/0/public/Time"))
831            .and(header("user-agent", "Strategy#1"))
832            .respond_with(ResponseTemplate::new(200).set_body_json(get_server_time_json()))
833            .expect(1)
834            .mount(&mock_server)
835            .await;
836
837        let _resp = client.get_server_time().await;
838        mock_server.verify().await;
839    }
840
841    #[tokio::test]
842    async fn test_system_public_endpoints() {
843        pause();
844        let n_calls = 7;
845
846        // n calls are expected to take just over ~n-1 seconds to complete
847        test_rate_limited_endpoint!(get_server_time, n_calls, n_calls - 1, n_calls, Intermediate);
848
849        test_rate_limited_endpoint!(
850            get_system_status,
851            n_calls,
852            n_calls - 1,
853            n_calls,
854            Intermediate
855        );
856    }
857
858    #[tokio::test]
859    async fn test_get_asset_info() {
860        pause();
861        let n_calls = 7;
862
863        let pairs = StringCSV::new(vec![
864            "XBT".to_string(),
865            "ETH".to_string(),
866            "ZUSD".to_string(),
867        ]);
868        let request = AssetInfoRequestBuilder::new()
869            .asset(pairs)
870            .asset_class("currency".into())
871            .build();
872
873        // n calls are expected to take just over ~n-1 seconds to complete
874        test_rate_limited_endpoint!(
875            get_asset_info,
876            n_calls,
877            n_calls - 1,
878            n_calls,
879            Intermediate,
880            &request
881        );
882    }
883
884    #[tokio::test]
885    async fn test_get_tradable_asset_pairs() {
886        pause();
887        let n_calls = 7;
888
889        let pairs = StringCSV::new(vec!["ETHUSD".to_string()]);
890        let request = TradableAssetPairsRequest::builder().pair(pairs).build();
891
892        // n calls are expected to take just over ~n-1 seconds to complete
893        test_rate_limited_endpoint!(
894            get_tradable_asset_pairs,
895            n_calls,
896            n_calls - 1,
897            n_calls,
898            Intermediate,
899            &request
900        );
901    }
902
903    #[tokio::test]
904    async fn test_get_ticker_information() {
905        pause();
906        let n_calls = 7;
907
908        let pairs = StringCSV::new(vec![
909            "BTCUSD".to_string(),
910            "ETHUSD".to_string(),
911            "USDCUSD".to_string(),
912        ]);
913        let request = TickerRequest::builder().pair(pairs).build();
914
915        // n calls are expected to take just over ~n-1 seconds to complete
916        test_rate_limited_endpoint!(
917            get_ticker_information,
918            n_calls,
919            n_calls - 1,
920            n_calls,
921            Intermediate,
922            &request
923        );
924    }
925
926    #[tokio::test]
927    async fn test_get_ohlc_and_recent_trades() {
928        pause();
929        let n_calls = 7;
930
931        let ohlc_request = OHLCRequest::builder("XETHZUSD".to_string())
932            .interval(CandlestickInterval::Hour)
933            .build();
934
935        let trades_request = RecentTradesRequest::builder("XXBTZUSD".to_string())
936            .count(10)
937            .build();
938
939        let secrets_provider = get_null_secrets_provider();
940        let nonce_provider: Box<Arc<Mutex<dyn NonceProvider>>> =
941            Box::new(Arc::new(Mutex::new(IncreasingNonceProvider::new())));
942
943        let mut client: TestRateLimitedClient = RateLimitedKrakenClient::new_with_verification_tier(
944            secrets_provider,
945            nonce_provider,
946            Pro,
947        );
948
949        let start = Instant::now();
950
951        // calling both in parallel should be fine, since they request different pairs
952        for _ in 0..n_calls {
953            let _ = client.get_ohlc(&ohlc_request).await;
954            let _ = client.get_recent_trades(&trades_request).await;
955        }
956
957        let end = Instant::now();
958        let elapsed = end - start;
959
960        println!("{:?}", elapsed);
961
962        assert!(elapsed > Duration::from_secs(n_calls - 1));
963        assert!(elapsed < Duration::from_secs(n_calls));
964    }
965
966    #[tokio::test]
967    async fn test_get_orderbook() {
968        pause();
969        let n_calls = 7;
970
971        let request = OrderbookRequest::builder("XXBTZUSD".to_string())
972            .count(10)
973            .build();
974
975        // n calls are expected to take just over ~n-1 seconds to complete
976        test_rate_limited_endpoint!(
977            get_orderbook,
978            n_calls,
979            n_calls - 1,
980            n_calls,
981            Intermediate,
982            &request
983        );
984    }
985
986    #[tokio::test]
987    async fn test_get_recent_trades() {
988        pause();
989        let n_calls = 7;
990
991        let request = RecentTradesRequest::builder("XXBTZUSD".to_string())
992            .count(10)
993            .build();
994
995        // n calls are expected to take just over ~n-1 seconds to complete
996        test_rate_limited_endpoint!(
997            get_recent_trades,
998            n_calls,
999            n_calls - 1,
1000            n_calls,
1001            Intermediate,
1002            &request
1003        );
1004    }
1005
1006    #[tokio::test]
1007    async fn test_get_recent_spreads() {
1008        pause();
1009        let n_calls = 7;
1010
1011        let request = RecentSpreadsRequest::builder("XXBTZUSD".to_string())
1012            .since(0)
1013            .build();
1014        // n calls are expected to take just over ~n-1 seconds to complete
1015        test_rate_limited_endpoint!(
1016            get_recent_spreads,
1017            n_calls,
1018            n_calls - 1,
1019            n_calls,
1020            Intermediate,
1021            &request
1022        );
1023    }
1024
1025    #[tokio::test]
1026    async fn test_get_account_balance() {
1027        pause();
1028
1029        // 22 calls costs 2200, requiring 4s to replenish @ 50/s
1030        test_rate_limited_endpoint!(get_account_balance, 22, 4, 5, Intermediate);
1031    }
1032
1033    #[tokio::test]
1034    async fn test_get_extended_balance() {
1035        pause();
1036
1037        // 22 calls costs 2200, requiring 2s to replenish @ 100/s
1038        test_rate_limited_endpoint!(get_extended_balances, 22, 2, 3, Pro);
1039    }
1040
1041    #[tokio::test]
1042    async fn test_get_trade_balances() {
1043        pause();
1044
1045        let request = TradeBalanceRequest::builder()
1046            .asset("XXBTZUSD".to_string())
1047            .build();
1048
1049        // 26 calls costs 2600, requiring 6s to replenish @ 100/s
1050        test_rate_limited_endpoint!(get_trade_balances, 26, 6, 7, Pro, &request);
1051    }
1052
1053    #[tokio::test]
1054    async fn test_get_open_orders() {
1055        pause();
1056
1057        let request = OpenOrdersRequest::builder().trades(true).build();
1058
1059        // 23 calls costs 2300, requiring 6s to replenish @ 50/s
1060        test_rate_limited_endpoint!(get_open_orders, 23, 6, 7, Intermediate, &request);
1061    }
1062
1063    #[tokio::test]
1064    async fn test_get_closed_orders() {
1065        pause();
1066
1067        let request = ClosedOrdersRequestBuilder::new()
1068            .trades(true)
1069            .start(12340000)
1070            .build();
1071
1072        // 13 calls costs 2600, requiring 6s to replenish @ 100/s
1073        test_rate_limited_endpoint!(get_closed_orders, 13, 6, 7, Pro, &request);
1074    }
1075
1076    #[tokio::test]
1077    async fn test_query_orders_info() {
1078        pause();
1079
1080        let tx_ids = StringCSV::new(vec!["uuid_1".to_string()]);
1081
1082        let request = OrderRequest::builder(tx_ids)
1083            .trades(true)
1084            .consolidate_taker(false)
1085            .build();
1086
1087        // 26 calls costs 2600, requiring 12s to replenish @ 50/s
1088        test_rate_limited_endpoint!(query_orders_info, 26, 12, 13, Intermediate, &request);
1089    }
1090
1091    #[tokio::test]
1092    async fn test_get_trades_history() {
1093        pause();
1094
1095        let request = TradesHistoryRequest::builder()
1096            .start(0)
1097            .end(1234)
1098            .trades(true)
1099            .consolidate_taker(false)
1100            .build();
1101
1102        // 14 calls costs 2800, requiring 8s to replenish @ 100/s
1103        test_rate_limited_endpoint!(get_trades_history, 14, 8, 9, Pro, &request);
1104    }
1105
1106    #[tokio::test]
1107    async fn test_query_trades_info() {
1108        pause();
1109
1110        let tx_ids = StringCSV::new(vec!["some-unique-id".to_string()]);
1111
1112        let request = TradeInfoRequest::builder(tx_ids).trades(true).build();
1113
1114        // 25 calls costs 2500, requiring 10s to replenish @ 50/s
1115        test_rate_limited_endpoint!(query_trades_info, 25, 10, 11, Intermediate, &request);
1116    }
1117
1118    #[tokio::test]
1119    async fn test_get_open_positions() {
1120        pause();
1121
1122        let request = OpenPositionsRequest::builder()
1123            .do_calcs(true)
1124            .consolidation("market".to_string())
1125            .build();
1126
1127        // 25 calls costs 2500, requiring 5s to replenish @ 100/s
1128        test_rate_limited_endpoint!(get_open_positions, 25, 5, 6, Pro, &request);
1129    }
1130
1131    #[tokio::test]
1132    async fn test_get_ledgers_info() {
1133        pause();
1134
1135        let request = LedgersInfoRequest::builder()
1136            .start(0)
1137            .asset(StringCSV(vec!["all".into()]))
1138            .build();
1139
1140        // 12 calls costs 2400, requiring 8s to replenish @ 50/s
1141        test_rate_limited_endpoint!(get_ledgers_info, 12, 8, 9, Intermediate, &request);
1142    }
1143
1144    #[tokio::test]
1145    async fn test_query_ledgers() {
1146        pause();
1147
1148        let request = QueryLedgerRequest::builder(StringCSV(vec!["51AHCZ-XXZ64-YW34UP".into()]))
1149            .trades(true)
1150            .build();
1151
1152        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1153        test_rate_limited_endpoint!(query_ledgers, 24, 4, 5, Pro, &request);
1154    }
1155
1156    #[tokio::test]
1157    async fn test_get_trade_volume() {
1158        pause();
1159
1160        let request = TradeVolumeRequest::builder()
1161            .pair(StringCSV(vec!["XXBTZUSD".to_string()]))
1162            .build();
1163
1164        // 24 calls costs 2400, requiring 8s to replenish @ 100/s
1165        test_rate_limited_endpoint!(get_trade_volume, 24, 8, 9, Intermediate, &request);
1166    }
1167
1168    #[tokio::test]
1169    async fn test_request_export_report() {
1170        pause();
1171
1172        let request = ExportReportRequest::builder(ReportType::Ledgers, "TestExport".to_string())
1173            .format(ReportFormatType::Csv)
1174            .build();
1175
1176        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1177        test_rate_limited_endpoint!(request_export_report, 24, 4, 5, Pro, &request);
1178    }
1179
1180    #[tokio::test]
1181    async fn test_get_export_report_status() {
1182        pause();
1183
1184        let request = ExportReportStatusRequest::builder(ReportType::Trades).build();
1185
1186        // 27 calls costs 2700, requiring 14s to replenish @ 50/s
1187        test_rate_limited_endpoint!(get_export_report_status, 27, 14, 15, Intermediate, &request);
1188    }
1189
1190    #[tokio::test]
1191    async fn test_retrieve_export_report() {
1192        pause();
1193
1194        let request =
1195            RetrieveExportReportRequest::builder("HI1M0S-BCRBJ-P01V9R".to_string()).build();
1196
1197        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1198        test_rate_limited_endpoint!(retrieve_export_report, 24, 4, 5, Pro, &request);
1199    }
1200
1201    #[tokio::test]
1202    async fn test_delete_export_report() {
1203        pause();
1204
1205        let request =
1206            DeleteExportRequest::builder("54E7".to_string(), DeleteExportType::Delete).build();
1207
1208        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1209        test_rate_limited_endpoint!(delete_export_report, 24, 8, 9, Intermediate, &request);
1210    }
1211
1212    #[tokio::test]
1213    async fn test_adding_order_limits() {
1214        pause();
1215        let mut client = get_rate_limit_test_client(Pro);
1216        let mut client_err = get_rate_limit_test_client_err(Pro);
1217
1218        let start = Instant::now();
1219
1220        let request = get_add_order_request();
1221
1222        // the first 180 orders exhaust all tokens, the remaining 15 require 4s of waiting
1223        //  since the replenishment rate is 375 tokens/s * 4s = 1500
1224        for _ in 0..(180 + 15) {
1225            let _ = client.add_order(&request).await;
1226            let _ = client_err.add_order(&request).await;
1227        }
1228
1229        let end = Instant::now();
1230        let elapsed = end - start;
1231        println!("{:?}", elapsed);
1232
1233        assert!(elapsed > Duration::from_secs(4));
1234        assert!(elapsed < Duration::from_secs(5));
1235    }
1236
1237    #[tokio::test]
1238    async fn test_amend_order_max_penalty() {
1239        pause();
1240        let verification = Intermediate;
1241        let mut client = get_rate_limit_test_client(verification);
1242
1243        let orders = max_out_rate_limits(&mut client, verification).await;
1244
1245        let amend_start = Instant::now();
1246
1247        // 4 instant amends costs 400 each, for 1600 total, 1600 / 234 = ~6.83 (requires 7s wait)
1248        for i in 0..4 {
1249            let amend_request = get_amend_for_order(&orders, i);
1250            let _ = client.amend_order(&amend_request).await;
1251        }
1252
1253        let amend_elapsed = amend_start.elapsed();
1254        println!("{:?}", amend_elapsed);
1255
1256        assert!(amend_elapsed > Duration::from_secs(7));
1257        assert!(amend_elapsed < Duration::from_secs(8));
1258    }
1259
1260    fn get_amend_for_order(orders: &Vec<AddOrder>, i: usize) -> AmendOrderRequest {
1261        AmendOrderRequest::builder()
1262            .tx_id(orders.get(i).unwrap().tx_id.first().unwrap().clone()) // TODO: cleanup
1263            .build()
1264    }
1265
1266    #[tokio::test]
1267    async fn test_add_order_batch_limits() {
1268        pause();
1269        let mut client = get_rate_limit_test_client(Pro);
1270        let mut client_err = get_rate_limit_test_client_err(Pro);
1271
1272        let start = Instant::now();
1273
1274        let request = get_batched_order_request(16);
1275
1276        // batched order of 16 should cost (1 + n / 2) * 100 = 900 each, so 21 * 900 = 18,900
1277        // replenishing the 900 after the pro limit should take 3s
1278        for _ in 0..21 {
1279            let _ = client.add_order_batch(&request).await;
1280            let _ = client_err.add_order_batch(&request).await;
1281        }
1282
1283        let end = Instant::now();
1284        let elapsed = end - start;
1285        println!("{:?}", elapsed);
1286
1287        assert!(elapsed > Duration::from_secs(3));
1288        assert!(elapsed < Duration::from_secs(4));
1289    }
1290
1291    #[tokio::test]
1292    async fn test_edit_order_max_penalty() {
1293        pause();
1294        let verification = Pro;
1295        let mut client = get_rate_limit_test_client(verification);
1296        let mut client_err = get_rate_limit_test_client_err(Pro);
1297
1298        let orders = max_out_rate_limits(&mut client, verification).await;
1299
1300        let edit_start = Instant::now();
1301
1302        // 6 instant edits costs 700 each, for 4200 total, 4200 / 375 = ~11.23 (requires 12s wait)
1303        for i in 0..6 {
1304            let edit_request = edit_from_order(orders.get(i).unwrap());
1305            let _ = client.edit_order(&edit_request).await;
1306        }
1307
1308        // initiating more edits for the error client has no effect, since each err return did not add
1309        //  an order id / lifetime
1310        for i in 0..12 {
1311            let edit_request = edit_from_order(orders.get(i).unwrap());
1312            let _ = client_err.edit_order(&edit_request).await;
1313        }
1314
1315        let edit_end = Instant::now();
1316        let edit_elapsed = edit_end - edit_start;
1317        println!("{:?}", edit_elapsed);
1318
1319        assert!(edit_elapsed > Duration::from_secs(12));
1320        assert!(edit_elapsed < Duration::from_secs(13));
1321    }
1322
1323    #[tokio::test]
1324    async fn test_cancel_order_max_penalty() {
1325        pause();
1326        let verification = Intermediate;
1327        let mut client = get_rate_limit_test_client(verification);
1328        let mut client_err = get_rate_limit_test_client_err(Pro);
1329
1330        let orders = max_out_rate_limits(&mut client, verification).await;
1331
1332        let edit_start = Instant::now();
1333
1334        // 4 instant cancels costs 800 each, for 3200 total, 3200 / 234 = ~13.67 (requires 14s wait)
1335        for i in 0..4 {
1336            let cancel_request = cancel_from_order(orders.get(i).unwrap());
1337            let _ = client.cancel_order(&cancel_request).await;
1338            let _ = client_err.cancel_order(&cancel_request).await;
1339        }
1340
1341        // initiating more cancels for the error client has no effect, since each err return did not add
1342        //  an order id / lifetime
1343        for i in 0..12 {
1344            let cancel_request = cancel_from_order(orders.get(i).unwrap());
1345            let _ = client_err.cancel_order(&cancel_request).await;
1346        }
1347
1348        let edit_end = Instant::now();
1349        let edit_elapsed = edit_end - edit_start;
1350        println!("{:?}", edit_elapsed);
1351
1352        assert!(edit_elapsed > Duration::from_secs(14));
1353        assert!(edit_elapsed < Duration::from_secs(15));
1354    }
1355
1356    #[tokio::test]
1357    async fn test_cancel_order_batch_with_max_penalty() {
1358        pause();
1359        let verification = Intermediate;
1360        let mut client = get_rate_limit_test_client(verification);
1361        let mut client_err = get_rate_limit_test_client_err(Pro);
1362
1363        let mut orders = max_out_rate_limits(&mut client, verification).await;
1364
1365        let edit_start = Instant::now();
1366
1367        let mut order_ids = Vec::new();
1368        for i in 0..4 {
1369            let id = IntOrString::String(orders.get(i).unwrap().tx_id.first().unwrap().clone());
1370            order_ids.push(id);
1371        }
1372
1373        let user_ref_request = get_add_order_request_user_ref();
1374        orders.push(
1375            client
1376                .add_order(&user_ref_request)
1377                .await
1378                .unwrap()
1379                .result
1380                .unwrap(),
1381        );
1382        order_ids.push(IntOrString::Int(user_ref_request.user_ref.unwrap()));
1383
1384        let batch_cancel_request = CancelBatchOrdersRequest {
1385            orders: order_ids,
1386            client_order_ids: None,
1387        };
1388
1389        // 1 additional order w/ user ref costs 100, 5 instant cancels cost 800 each, for 4100 total,
1390        // making 4100 / 234 = ~17.52 (requires 18s wait)
1391        let _ = client.cancel_order_batch(&batch_cancel_request).await;
1392
1393        // failures don't add anything to wait
1394        for _ in 0..5 {
1395            let _ = client_err.cancel_order_batch(&batch_cancel_request).await;
1396        }
1397
1398        let edit_end = Instant::now();
1399        let edit_elapsed = edit_end - edit_start;
1400        println!("{:?}", edit_elapsed);
1401
1402        assert!(edit_elapsed > Duration::from_secs(18));
1403        assert!(edit_elapsed < Duration::from_secs(19));
1404    }
1405
1406    /// Depending on the verification tier, submit enough orders to empty the rate limit bucket and
1407    /// return the created orders. Also checks that it has not exceeded the limits (executes in < 10ms).
1408    async fn max_out_rate_limits(
1409        client: &mut TestRateLimitedClient,
1410        verification_tier: VerificationTier,
1411    ) -> Vec<AddOrder> {
1412        let start = Instant::now();
1413
1414        let request = get_add_order_request();
1415
1416        let n_orders = match verification_tier {
1417            Intermediate => 125,
1418            Pro => 180,
1419        };
1420
1421        // the first 180 orders exhaust all tokens
1422        let mut orders = Vec::new();
1423        for _ in 0..n_orders {
1424            let order = client.add_order(&request).await.unwrap().result.unwrap();
1425            orders.push(order);
1426        }
1427
1428        let end = Instant::now();
1429        let elapsed = end - start;
1430        println!("{:?}", elapsed);
1431
1432        assert!(elapsed >= Duration::from_secs(0));
1433        assert!(elapsed < Duration::from_millis(10));
1434        orders
1435    }
1436
1437    fn get_add_order_request() -> AddOrderRequest {
1438        let order_flags =
1439            OrderFlags::new(vec![OrderFlag::NoMarketPriceProtection, OrderFlag::Post]);
1440
1441        AddOrderRequest::builder(
1442            OrderType::Market,
1443            BuySell::Buy,
1444            dec!(5.0),
1445            "USDCUSD".to_string(),
1446        )
1447        .order_flags(order_flags)
1448        .price(dec!(0.90))
1449        .build()
1450    }
1451
1452    fn get_add_order_request_user_ref() -> AddOrderRequest {
1453        let order_flags =
1454            OrderFlags::new(vec![OrderFlag::NoMarketPriceProtection, OrderFlag::Post]);
1455
1456        AddOrderRequest::builder(
1457            OrderType::Market,
1458            BuySell::Buy,
1459            dec!(5.0),
1460            "USDCUSD".to_string(),
1461        )
1462        .user_ref(42)
1463        .order_flags(order_flags)
1464        .price(dec!(0.90))
1465        .build()
1466    }
1467
1468    fn get_batched_order_request(n_orders: u64) -> AddBatchedOrderRequest {
1469        let mut orders = Vec::new();
1470
1471        for _ in 0..n_orders {
1472            let order = BatchedOrderRequest::builder(OrderType::Limit, BuySell::Buy, dec!(5.1))
1473                .price(dec!(0.9))
1474                .start_time("0".to_string())
1475                .expire_time("+5".to_string())
1476                .build();
1477
1478            orders.push(order);
1479        }
1480
1481        AddBatchedOrderRequest::builder(orders, "USDCUSD".to_string()).build()
1482    }
1483
1484    fn edit_from_order(order: &AddOrder) -> EditOrderRequest {
1485        let edit_request = EditOrderRequest {
1486            user_ref: None,
1487            tx_id: order.tx_id.first().unwrap().clone(),
1488            volume: dec!(0),
1489            display_volume: None,
1490            pair: "".to_string(),
1491            price: None,
1492            price_2: None,
1493            order_flags: None,
1494            deadline: None,
1495            cancel_response: None,
1496            validate: None,
1497        };
1498        edit_request
1499    }
1500
1501    fn cancel_from_order(order: &AddOrder) -> CancelOrderRequest {
1502        CancelOrderRequest {
1503            tx_id: IntOrString::String(order.tx_id.first().unwrap().clone()),
1504            client_order_id: None,
1505        }
1506    }
1507
1508    #[tokio::test]
1509    async fn test_get_deposit_methods() {
1510        pause();
1511
1512        let request = DepositMethodsRequest::builder("ETH".to_string()).build();
1513
1514        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1515        test_rate_limited_endpoint!(get_deposit_methods, 24, 4, 5, Pro, &request);
1516    }
1517
1518    #[tokio::test]
1519    async fn test_get_deposit_addresses() {
1520        pause();
1521
1522        let request = DepositAddressesRequest::builder("BTC".to_string(), "Bitcoin".to_string())
1523            .is_new(true)
1524            .build();
1525
1526        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1527        test_rate_limited_endpoint!(get_deposit_addresses, 24, 8, 9, Intermediate, &request);
1528    }
1529
1530    #[tokio::test]
1531    async fn test_get_status_of_recent_deposits() {
1532        pause();
1533
1534        let request = StatusOfDepositWithdrawRequest::builder()
1535            .asset_class("currency".to_string())
1536            .build();
1537
1538        // 26 calls costs 2600, requiring 6s to replenish @ 100/s
1539        test_rate_limited_endpoint!(get_status_of_recent_deposits, 26, 6, 7, Pro, &request);
1540    }
1541
1542    #[tokio::test]
1543    async fn test_get_withdrawal_methods() {
1544        pause();
1545
1546        let request = WithdrawalMethodsRequest::builder()
1547            .asset_class("currency".to_string())
1548            .build();
1549
1550        // 26 calls costs 2600, requiring 12s to replenish @ 50/s
1551        test_rate_limited_endpoint!(get_withdrawal_methods, 26, 12, 13, Intermediate, &request);
1552    }
1553
1554    #[tokio::test]
1555    async fn test_get_withdrawal_addresses() {
1556        pause();
1557
1558        let request = WithdrawalAddressesRequest::builder()
1559            .asset_class("currency".to_string())
1560            .build();
1561
1562        // 25 calls costs 2500, requiring 5s to replenish @ 100/s
1563        test_rate_limited_endpoint!(get_withdrawal_addresses, 25, 5, 6, Pro, &request);
1564    }
1565
1566    #[tokio::test]
1567    async fn test_get_withdrawal_info() {
1568        pause();
1569
1570        let request = WithdrawalInfoRequest::builder(
1571            "XBT".to_string(),
1572            "Greenlisted Address".to_string(),
1573            dec!(0.1),
1574        )
1575        .build();
1576
1577        // 25 calls costs 2500, requiring 5s to replenish @ 100/s
1578        test_rate_limited_endpoint!(get_withdrawal_info, 25, 5, 6, Pro, &request);
1579    }
1580
1581    #[tokio::test]
1582    async fn test_withdraw_funds() {
1583        pause();
1584
1585        let request = WithdrawFundsRequest::builder(
1586            "XBT".to_string(),
1587            "Greenlisted Address".to_string(),
1588            dec!(0.1),
1589        )
1590        .max_fee(dec!(0.00001))
1591        .build();
1592
1593        // 25 calls costs 2500, requiring 10s to replenish @ 50/s
1594        test_rate_limited_endpoint!(withdraw_funds, 25, 10, 11, Intermediate, &request);
1595    }
1596
1597    #[tokio::test]
1598    async fn test_get_status_of_recent_withdrawals() {
1599        pause();
1600
1601        let request = StatusOfDepositWithdrawRequest::builder()
1602            .asset_class("currency".to_string())
1603            .build();
1604
1605        // 25 calls costs 2500, requiring 5s to replenish @ 100/s
1606        test_rate_limited_endpoint!(get_status_of_recent_withdrawals, 25, 5, 6, Pro, &request);
1607    }
1608
1609    #[tokio::test]
1610    async fn test_request_withdrawal_cancellation() {
1611        pause();
1612
1613        let request = WithdrawCancelRequest::builder("XBT".to_string(), "uuid".to_string()).build();
1614
1615        // 27 calls costs 2700, requiring 14s to replenish @ 50/s
1616        test_rate_limited_endpoint!(
1617            request_withdrawal_cancellation,
1618            27,
1619            14,
1620            15,
1621            Intermediate,
1622            &request
1623        );
1624    }
1625
1626    #[tokio::test]
1627    async fn test_request_wallet_transfer() {
1628        pause();
1629
1630        let request = WalletTransferRequest::builder(
1631            "XBT".to_string(),
1632            "Account One".to_string(),
1633            "Account Two".to_string(),
1634            dec!(0.25),
1635        )
1636        .build();
1637
1638        // 27 calls costs 2700, requiring 7s to replenish @ 100/s
1639        test_rate_limited_endpoint!(request_wallet_transfer, 27, 7, 8, Pro, &request);
1640    }
1641
1642    #[tokio::test]
1643    async fn test_create_sub_account() {
1644        pause();
1645
1646        let request =
1647            CreateSubAccountRequest::builder("username".to_string(), "user@mail.com".to_string())
1648                .build();
1649
1650        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1651        test_rate_limited_endpoint!(create_sub_account, 24, 4, 5, Pro, &request);
1652    }
1653
1654    #[tokio::test]
1655    async fn test_account_transfer() {
1656        pause();
1657
1658        let request = AccountTransferRequest::builder(
1659            "BTC".to_string(),
1660            dec!(1031.2008),
1661            "SourceAccount".to_string(),
1662            "DestAccount".to_string(),
1663        )
1664        .build();
1665
1666        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1667        test_rate_limited_endpoint!(account_transfer, 24, 8, 9, Intermediate, &request);
1668    }
1669
1670    #[tokio::test]
1671    async fn test_allocate_earn_funds() {
1672        pause();
1673
1674        let request =
1675            AllocateEarnFundsRequest::builder(dec!(10.123), "W38S2C-Y1E0R-DUFM2T".to_string())
1676                .build();
1677
1678        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1679        test_rate_limited_endpoint!(allocate_earn_funds, 24, 4, 5, Pro, &request);
1680    }
1681
1682    #[tokio::test]
1683    async fn test_deallocate_earn_funds() {
1684        pause();
1685
1686        let request =
1687            AllocateEarnFundsRequest::builder(dec!(10.123), "W38S2C-Y1E0R-DUFM2T".to_string())
1688                .build();
1689
1690        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1691        test_rate_limited_endpoint!(deallocate_earn_funds, 24, 8, 9, Intermediate, &request);
1692    }
1693
1694    #[tokio::test]
1695    async fn test_get_allocation_status() {
1696        pause();
1697
1698        let request =
1699            EarnAllocationStatusRequest::builder("W38S2C-Y1E0R-DUFM2T".to_string()).build();
1700
1701        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1702        test_rate_limited_endpoint!(get_earn_allocation_status, 24, 8, 9, Intermediate, &request);
1703    }
1704
1705    #[tokio::test]
1706    async fn test_get_deallocation_status() {
1707        pause();
1708
1709        let request =
1710            EarnAllocationStatusRequest::builder("W38S2C-Y1E0R-DUFM2T".to_string()).build();
1711
1712        // 24 calls costs 2400, requiring 8s to replenish @ 50/s
1713        test_rate_limited_endpoint!(
1714            get_earn_deallocation_status,
1715            24,
1716            8,
1717            9,
1718            Intermediate,
1719            &request
1720        );
1721    }
1722
1723    #[tokio::test]
1724    async fn test_list_earn_strategies() {
1725        pause();
1726
1727        let request = ListEarnStrategiesRequest::builder()
1728            .limit(64)
1729            .ascending(true)
1730            .build();
1731
1732        // 24 calls costs 2400, requiring 4s to replenish @ 100/s
1733        test_rate_limited_endpoint!(list_earn_strategies, 24, 4, 5, Pro, &request);
1734    }
1735
1736    #[tokio::test]
1737    async fn test_list_earn_allocations() {
1738        pause();
1739
1740        let request = ListEarnAllocationsRequest::builder()
1741            .ascending(true)
1742            .hide_zero_allocations(true)
1743            .build();
1744
1745        // 29 calls costs 2900, requiring 18s to replenish @ 500/s
1746        test_rate_limited_endpoint!(list_earn_allocations, 29, 18, 19, Intermediate, &request);
1747    }
1748
1749    #[tokio::test]
1750    async fn test_get_websockets_token() {
1751        pause();
1752
1753        // 23 calls costs 2300, requiring 3s to replenish @ 100/s
1754        test_rate_limited_endpoint!(get_websockets_token, 23, 3, 4, Pro);
1755    }
1756}