Skip to main content

nautilus_kraken/python/
config.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//! Python bindings for Kraken configuration.
17
18use nautilus_core::{python::to_pyvalue_err, string::secret::SecretString};
19use nautilus_model::{enums::AccountType, identifiers::AccountId};
20use nautilus_network::websocket::TransportBackend;
21use pyo3::prelude::*;
22
23use crate::{
24    common::enums::{KrakenEnvironment, KrakenProductType},
25    config::{KrakenDataClientConfig, KrakenExecutionClientConfig},
26};
27
28#[pymethods]
29#[pyo3_stub_gen::derive::gen_stub_pymethods]
30impl KrakenDataClientConfig {
31    /// Configuration for the Kraken data client.
32    #[new]
33    #[pyo3(signature = (
34        product_type = None,
35        environment = None,
36        api_key = None,
37        api_secret = None,
38        base_url = None,
39        ws_public_url = None,
40        ws_private_url = None,
41        ws_l3_url = None,
42        validate_l3_checksum = None,
43        proxy_url = None,
44        timeout_secs = None,
45        heartbeat_interval_secs = None,
46        ws_idle_timeout_ms = None,
47        max_requests_per_second = None,
48        transport_backend = None,
49        validate_l2_checksum = None,
50    ))]
51    #[expect(clippy::too_many_arguments)]
52    fn py_new(
53        product_type: Option<KrakenProductType>,
54        environment: Option<KrakenEnvironment>,
55        api_key: Option<String>,
56        api_secret: Option<String>,
57        base_url: Option<String>,
58        ws_public_url: Option<String>,
59        ws_private_url: Option<String>,
60        ws_l3_url: Option<String>,
61        validate_l3_checksum: Option<bool>,
62        proxy_url: Option<String>,
63        timeout_secs: Option<u64>,
64        heartbeat_interval_secs: Option<u64>,
65        ws_idle_timeout_ms: Option<u64>,
66        max_requests_per_second: Option<u32>,
67        transport_backend: Option<TransportBackend>,
68        // Stays the last positional parameter: Python callers bind `proxy_url` and the arguments
69        // after it by position.
70        validate_l2_checksum: Option<bool>,
71    ) -> Self {
72        let defaults = Self::default();
73        Self {
74            api_key: api_key.map(SecretString::from),
75            api_secret: api_secret.map(SecretString::from),
76            product_type: product_type.unwrap_or(defaults.product_type),
77            environment: environment.unwrap_or(defaults.environment),
78            base_url,
79            ws_public_url,
80            ws_private_url,
81            ws_l3_url,
82            validate_l3_checksum: validate_l3_checksum.unwrap_or(defaults.validate_l3_checksum),
83            validate_l2_checksum: validate_l2_checksum.unwrap_or(defaults.validate_l2_checksum),
84            proxy_url: proxy_url.map(SecretString::from),
85            timeout_secs: timeout_secs.unwrap_or(defaults.timeout_secs),
86            heartbeat_interval_secs: heartbeat_interval_secs
87                .unwrap_or(defaults.heartbeat_interval_secs),
88            ws_idle_timeout_ms: ws_idle_timeout_ms.unwrap_or(defaults.ws_idle_timeout_ms),
89            max_requests_per_second,
90            transport_backend: transport_backend.unwrap_or(defaults.transport_backend),
91        }
92    }
93
94    #[getter]
95    const fn has_proxy_url(&self) -> bool {
96        self.proxy_url.is_some()
97    }
98
99    /// Returns the configured public WebSocket URL override.
100    #[getter]
101    fn get_ws_public_url(&self) -> Option<String> {
102        self.ws_public_url.clone()
103    }
104
105    /// Returns the configured private WebSocket URL override.
106    #[getter]
107    fn get_ws_private_url(&self) -> Option<String> {
108        self.ws_private_url.clone()
109    }
110
111    /// Returns the configured L3 WebSocket URL override.
112    #[getter]
113    fn get_ws_l3_url(&self) -> Option<String> {
114        self.ws_l3_url.clone()
115    }
116
117    fn __repr__(&self) -> String {
118        stringify!(KrakenDataClientConfig).to_string()
119    }
120}
121
122#[pymethods]
123#[pyo3_stub_gen::derive::gen_stub_pymethods]
124impl KrakenExecutionClientConfig {
125    /// Configuration for the Kraken execution client.
126    #[new]
127    #[pyo3(signature = (
128        account_id,
129        api_key,
130        api_secret,
131        product_type = None,
132        environment = None,
133        base_url = None,
134        ws_url = None,
135        proxy_url = None,
136        timeout_secs = None,
137        heartbeat_interval_secs = None,
138        auth_timeout_secs = None,
139        max_requests_per_second = None,
140        max_retries = None,
141        spot_account_type = None,
142        default_leverage = None,
143        use_spot_position_reports = None,
144        spot_positions_quote_currency = None,
145        margin_balance_asset = None,
146        use_ws_trade = None,
147        ws_request_timeout_secs = None,
148        transport_backend = None,
149    ))]
150    #[expect(clippy::too_many_arguments)]
151    fn py_new(
152        account_id: AccountId,
153        api_key: String,
154        api_secret: String,
155        product_type: Option<KrakenProductType>,
156        environment: Option<KrakenEnvironment>,
157        base_url: Option<String>,
158        ws_url: Option<String>,
159        proxy_url: Option<String>,
160        timeout_secs: Option<u64>,
161        heartbeat_interval_secs: Option<u64>,
162        auth_timeout_secs: Option<u64>,
163        max_requests_per_second: Option<u32>,
164        max_retries: Option<u32>,
165        spot_account_type: Option<AccountType>,
166        default_leverage: Option<u16>,
167        use_spot_position_reports: Option<bool>,
168        spot_positions_quote_currency: Option<String>,
169        margin_balance_asset: Option<String>,
170        use_ws_trade: Option<bool>,
171        ws_request_timeout_secs: Option<u64>,
172        transport_backend: Option<TransportBackend>,
173    ) -> PyResult<Self> {
174        let defaults = Self::default();
175        let spot_account_type = spot_account_type.unwrap_or(defaults.spot_account_type);
176        if default_leverage.is_some() && spot_account_type == AccountType::Cash {
177            return Err(to_pyvalue_err(
178                "default_leverage requires spot_account_type=Margin",
179            ));
180        }
181        Ok(Self {
182            account_id,
183            api_key: api_key.into(),
184            api_secret: api_secret.into(),
185            product_type: product_type.unwrap_or(defaults.product_type),
186            environment: environment.unwrap_or(defaults.environment),
187            base_url,
188            ws_url,
189            proxy_url: proxy_url.map(SecretString::from),
190            timeout_secs: timeout_secs.unwrap_or(defaults.timeout_secs),
191            heartbeat_interval_secs: heartbeat_interval_secs
192                .unwrap_or(defaults.heartbeat_interval_secs),
193            auth_timeout_secs,
194            max_requests_per_second,
195            max_retries: max_retries.unwrap_or(defaults.max_retries),
196            transport_backend: transport_backend.unwrap_or(defaults.transport_backend),
197            spot_account_type,
198            default_leverage,
199            use_spot_position_reports: use_spot_position_reports
200                .unwrap_or(defaults.use_spot_position_reports),
201            spot_positions_quote_currency: spot_positions_quote_currency
202                .unwrap_or(defaults.spot_positions_quote_currency),
203            margin_balance_asset,
204            use_ws_trade: use_ws_trade.unwrap_or(defaults.use_ws_trade),
205            ws_request_timeout_secs: ws_request_timeout_secs
206                .unwrap_or(defaults.ws_request_timeout_secs),
207        })
208    }
209
210    #[getter]
211    const fn has_proxy_url(&self) -> bool {
212        self.proxy_url.is_some()
213    }
214
215    /// Returns the configured WebSocket URL override.
216    #[getter]
217    fn get_ws_url(&self) -> Option<String> {
218        self.ws_url.clone()
219    }
220
221    fn __repr__(&self) -> String {
222        stringify!(KrakenExecutionClientConfig).to_string()
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use pyo3::{
229        Python,
230        types::{PyAnyMethods, PyTypeMethods},
231    };
232    use rstest::rstest;
233
234    use super::*;
235
236    /// The checksum option is the last positional parameter, so a positional call binds the proxy
237    /// URL to `proxy_url`.
238    #[rstest]
239    fn data_config_keeps_the_proxy_url_position() {
240        Python::initialize();
241        Python::attach(|py| {
242            let cls = py.get_type::<KrakenDataClientConfig>();
243            let none = py.None();
244            let args = (
245                none.clone_ref(py),
246                none.clone_ref(py),
247                none.clone_ref(py),
248                none.clone_ref(py),
249                none.clone_ref(py),
250                none.clone_ref(py),
251                none.clone_ref(py),
252                none.clone_ref(py),
253                true,
254                "http://proxy.example:8080",
255            );
256
257            let config = cls
258                .call1(args)
259                .unwrap_or_else(|e| panic!("construct {}: {e}", cls.name().unwrap()))
260                .extract::<KrakenDataClientConfig>()
261                .expect("extract KrakenDataClientConfig");
262
263            assert!(config.has_proxy_url());
264            assert!(config.validate_l3_checksum);
265            assert_eq!(
266                config.validate_l2_checksum,
267                KrakenDataClientConfig::default().validate_l2_checksum
268            );
269        });
270    }
271}