Skip to main content

nautilus_kraken/http/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! HTTP/REST client implementations for Kraken APIs.
17//!
18//! This module provides HTTP clients for interacting with Kraken's REST endpoints:
19//!
20//! - [`spot`]: Kraken Spot REST API
21//! - [`futures`]: Kraken Futures REST API
22
23use chrono::{DateTime, Utc};
24
25pub mod error;
26pub mod futures;
27pub mod models;
28pub mod spot;
29
30/// Applies a count `limit` to `items` returned oldest-first by Kraken.
31///
32/// Kraken's historical endpoints return their page in ascending (oldest-first)
33/// order. When the caller anchors the window with `start`, the oldest `limit`
34/// items from that anchor are the intended result, so the head of the page is
35/// kept. Otherwise a count-only request (`start` is `None`) means "the most
36/// recent `limit` items", so the tail is kept rather than the head.
37pub(crate) fn apply_count_limit<T>(
38    items: &mut Vec<T>,
39    start: Option<DateTime<Utc>>,
40    limit: Option<u64>,
41) {
42    if let Some(limit) = limit {
43        let limit = limit as usize;
44        if items.len() > limit {
45            if start.is_some() {
46                items.truncate(limit);
47            } else {
48                items.drain(..items.len() - limit);
49            }
50        }
51    }
52}
53
54// Re-exports
55pub use error::KrakenHttpError;
56pub use futures::{
57    client::{
58        KRAKEN_FUTURES_DEFAULT_RATE_LIMIT_PER_SECOND, KrakenFuturesHttpClient,
59        KrakenFuturesRawHttpClient,
60    },
61    query::*,
62};
63pub use spot::{
64    client::{
65        KRAKEN_SPOT_DEFAULT_RATE_LIMIT_PER_SECOND, KrakenSpotHttpClient, KrakenSpotRawHttpClient,
66    },
67    query::*,
68};
69
70#[cfg(test)]
71mod tests {
72    use nautilus_core::UnixNanos;
73    use nautilus_model::{
74        data::{Bar, BarType},
75        types::{Price, Quantity},
76    };
77    use rstest::rstest;
78
79    use super::*;
80
81    /// Builds `n` bars in ascending (oldest-first) order, as Kraken returns them,
82    /// with `ts_event` set to the 1-based index so ordering is easy to assert.
83    fn ascending_bars(n: u64) -> Vec<Bar> {
84        let bar_type = BarType::from("ETH/USD.KRAKEN-1-MINUTE-LAST-EXTERNAL");
85        let price = Price::from("100.0");
86        let volume = Quantity::from("1");
87        (1..=n)
88            .map(|i| {
89                Bar::new(
90                    bar_type,
91                    price,
92                    price,
93                    price,
94                    price,
95                    volume,
96                    UnixNanos::from(i),
97                    UnixNanos::default(),
98                )
99            })
100            .collect()
101    }
102
103    #[rstest]
104    fn test_apply_count_limit_no_limit_keeps_all() {
105        let mut bars = ascending_bars(5);
106        apply_count_limit(&mut bars, None, None);
107        assert_eq!(bars.len(), 5);
108    }
109
110    #[rstest]
111    fn test_apply_count_limit_count_only_keeps_most_recent() {
112        let mut bars = ascending_bars(10);
113        apply_count_limit(&mut bars, None, Some(3));
114        let ts: Vec<u64> = bars.iter().map(|b| b.ts_event.as_u64()).collect();
115        assert_eq!(ts, vec![8, 9, 10]);
116    }
117
118    #[rstest]
119    fn test_apply_count_limit_with_start_keeps_oldest() {
120        let mut bars = ascending_bars(10);
121        let start = DateTime::<Utc>::from_timestamp(0, 0);
122        apply_count_limit(&mut bars, start, Some(3));
123        let ts: Vec<u64> = bars.iter().map(|b| b.ts_event.as_u64()).collect();
124        assert_eq!(ts, vec![1, 2, 3]);
125    }
126
127    #[rstest]
128    fn test_apply_count_limit_fewer_bars_than_limit() {
129        let mut bars = ascending_bars(2);
130        apply_count_limit(&mut bars, None, Some(5));
131        assert_eq!(bars.len(), 2);
132    }
133}