1use 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 #[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 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 #[getter]
101 fn get_ws_public_url(&self) -> Option<String> {
102 self.ws_public_url.clone()
103 }
104
105 #[getter]
107 fn get_ws_private_url(&self) -> Option<String> {
108 self.ws_private_url.clone()
109 }
110
111 #[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 #[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 #[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 #[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}