1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::{ClientId, TraderId},
30};
31
32use crate::{
33 common::{
34 consts::{KRAKEN, KRAKEN_VENUE},
35 enums::KrakenProductType,
36 },
37 config::{KrakenDataClientConfig, KrakenExecutionClientConfig},
38 data::{KrakenFuturesDataClient, KrakenSpotDataClient},
39 execution::{KrakenFuturesExecutionClient, KrakenSpotExecutionClient},
40};
41
42impl ClientConfig for KrakenDataClientConfig {
43 fn as_any(&self) -> &dyn Any {
44 self
45 }
46}
47
48#[derive(Debug, Clone)]
50#[cfg_attr(
51 feature = "python",
52 pyo3::pyclass(module = "nautilus_trader.adapters.kraken", from_py_object)
53)]
54#[cfg_attr(
55 feature = "python",
56 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.kraken")
57)]
58pub struct KrakenDataClientFactory;
59
60impl KrakenDataClientFactory {
61 #[must_use]
63 pub const fn new() -> Self {
64 Self
65 }
66}
67
68impl Default for KrakenDataClientFactory {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl DataClientFactory for KrakenDataClientFactory {
75 fn create(
76 &self,
77 name: &str,
78 config: &dyn ClientConfig,
79 _cache: CacheView,
80 _clock: Rc<RefCell<dyn Clock>>,
81 ) -> anyhow::Result<Box<dyn DataClient>> {
82 let kraken_config = config
83 .as_any()
84 .downcast_ref::<KrakenDataClientConfig>()
85 .ok_or_else(|| {
86 anyhow::anyhow!(
87 "Invalid config type for KrakenDataClientFactory. Expected KrakenDataClientConfig, was {config:?}",
88 )
89 })?
90 .clone();
91
92 kraken_config.validate()?;
93
94 let client_id = ClientId::from(name);
95
96 match kraken_config.product_type {
97 KrakenProductType::Spot => {
98 let client = KrakenSpotDataClient::new(client_id, kraken_config)?;
99 Ok(Box::new(client))
100 }
101 KrakenProductType::Futures => {
102 let client = KrakenFuturesDataClient::new(client_id, kraken_config)?;
103 Ok(Box::new(client))
104 }
105 }
106 }
107
108 fn name(&self) -> &'static str {
109 KRAKEN
110 }
111
112 fn config_type(&self) -> &'static str {
113 "KrakenDataClientConfig"
114 }
115}
116
117impl ClientConfig for KrakenExecutionClientConfig {
118 fn as_any(&self) -> &dyn Any {
119 self
120 }
121}
122
123#[derive(Debug, Clone)]
125#[cfg_attr(
126 feature = "python",
127 pyo3::pyclass(module = "nautilus_trader.adapters.kraken", from_py_object)
128)]
129#[cfg_attr(
130 feature = "python",
131 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.kraken")
132)]
133pub struct KrakenExecutionClientFactory;
134
135impl KrakenExecutionClientFactory {
136 #[must_use]
138 pub const fn new() -> Self {
139 Self
140 }
141}
142
143impl Default for KrakenExecutionClientFactory {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl ExecutionClientFactory for KrakenExecutionClientFactory {
150 fn create(
151 &self,
152 trader_id: TraderId,
153 name: &str,
154 config: &dyn ClientConfig,
155 cache: CacheView,
156 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
157 let kraken_config = config
158 .as_any()
159 .downcast_ref::<KrakenExecutionClientConfig>()
160 .ok_or_else(|| {
161 anyhow::anyhow!(
162 "Invalid config type for KrakenExecutionClientFactory. Expected KrakenExecutionClientConfig, was {config:?}",
163 )
164 })?
165 .clone();
166
167 kraken_config.validate()?;
168
169 let oms_type = OmsType::Netting;
170 let account_type = match kraken_config.product_type {
171 KrakenProductType::Spot => kraken_config.spot_account_type,
172 KrakenProductType::Futures => AccountType::Margin,
173 };
174
175 let client_id = ClientId::from(name);
176 let core = ExecutionClientCore::new(
177 trader_id,
178 client_id,
179 *KRAKEN_VENUE,
180 oms_type,
181 kraken_config.account_id,
182 account_type,
183 None, cache,
185 );
186
187 match kraken_config.product_type {
188 KrakenProductType::Spot => {
189 let client = KrakenSpotExecutionClient::new(core, kraken_config)?;
190 Ok(Box::new(client))
191 }
192 KrakenProductType::Futures => {
193 let client = KrakenFuturesExecutionClient::new(core, kraken_config)?;
194 Ok(Box::new(client))
195 }
196 }
197 }
198
199 fn name(&self) -> &'static str {
200 KRAKEN
201 }
202
203 fn config_type(&self) -> &'static str {
204 "KrakenExecutionClientConfig"
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::{cell::RefCell, rc::Rc};
211
212 use nautilus_common::{
213 cache::Cache,
214 clock::TestClock,
215 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
216 live::runner::set_data_event_sender,
217 messages::DataEvent,
218 };
219 use rstest::rstest;
220
221 use super::*;
222 use crate::common::enums::{KrakenEnvironment, KrakenProductType};
223
224 fn setup_test_env() {
225 let (sender, _receiver) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
226 set_data_event_sender(sender);
227 }
228
229 #[rstest]
230 fn test_kraken_data_client_factory_creation() {
231 let factory = KrakenDataClientFactory::new();
232 assert_eq!(factory.name(), KRAKEN);
233 assert_eq!(factory.config_type(), "KrakenDataClientConfig");
234 }
235
236 #[rstest]
237 fn test_kraken_data_client_factory_default() {
238 let factory = KrakenDataClientFactory::new();
239 assert_eq!(factory.name(), KRAKEN);
240 }
241
242 #[rstest]
243 fn test_kraken_data_client_config_implements_client_config() {
244 let config = KrakenDataClientConfig {
245 product_type: KrakenProductType::Spot,
246 ..Default::default()
247 };
248
249 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
250 let downcasted = boxed_config
251 .as_any()
252 .downcast_ref::<KrakenDataClientConfig>();
253
254 assert!(downcasted.is_some());
255 }
256
257 #[rstest]
258 fn test_kraken_data_client_factory_creates_client() {
259 setup_test_env();
260
261 let factory = KrakenDataClientFactory::new();
262 let config = KrakenDataClientConfig {
263 product_type: KrakenProductType::Spot,
264 ..Default::default()
265 };
266
267 let cache = Rc::new(RefCell::new(Cache::default()));
268 let clock = Rc::new(RefCell::new(TestClock::new()));
269
270 let result = factory.create("KRAKEN-TEST", &config, cache.into(), clock);
271 assert!(result.is_ok());
272
273 let client = result.unwrap();
274 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
275 }
276
277 #[rstest]
278 fn test_kraken_execution_client_factory_creates_spot_client_with_netting_oms() {
279 let factory = KrakenExecutionClientFactory::new();
280 let config = KrakenExecutionClientConfig {
281 product_type: KrakenProductType::Spot,
282 ..Default::default()
283 };
284 let cache = Rc::new(RefCell::new(Cache::default()));
285
286 let result = factory.create(
287 TraderId::from("TRADER-001"),
288 "KRAKEN-TEST",
289 &config,
290 cache.into(),
291 );
292 assert!(result.is_ok());
293
294 let client = result.unwrap();
295 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
296 assert_eq!(client.account_id(), config.account_id);
297 assert_eq!(client.oms_type(), OmsType::Netting);
298 }
299
300 #[rstest]
301 fn test_kraken_execution_client_factory_creates_futures_client_with_netting_oms() {
302 let factory = KrakenExecutionClientFactory::new();
303 let config = KrakenExecutionClientConfig {
304 product_type: KrakenProductType::Futures,
305 ..Default::default()
306 };
307 let cache = Rc::new(RefCell::new(Cache::default()));
308
309 let result = factory.create(
310 TraderId::from("TRADER-001"),
311 "KRAKEN-TEST",
312 &config,
313 cache.into(),
314 );
315 assert!(result.is_ok());
316
317 let client = result.unwrap();
318 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
319 assert_eq!(client.account_id(), config.account_id);
320 assert_eq!(client.oms_type(), OmsType::Netting);
321 }
322
323 #[rstest]
324 fn test_kraken_execution_client_factory_rejects_leverage_on_cash_account() {
325 let factory = KrakenExecutionClientFactory::new();
326 let config = KrakenExecutionClientConfig {
327 product_type: KrakenProductType::Spot,
328 spot_account_type: AccountType::Cash,
329 default_leverage: Some(3),
330 ..Default::default()
331 };
332 let cache = Rc::new(RefCell::new(Cache::default()));
333
334 let result = factory.create(
335 TraderId::from("TRADER-001"),
336 "KRAKEN-TEST",
337 &config,
338 cache.into(),
339 );
340 let err = match result {
341 Ok(_) => panic!("expected validation error, factory returned Ok"),
342 Err(e) => e.to_string(),
343 };
344 assert!(
345 err.contains("default_leverage requires spot_account_type=Margin"),
346 "unexpected error: {err}"
347 );
348 }
349
350 #[rstest]
351 fn test_kraken_data_client_factory_rejects_spot_demo() {
352 setup_test_env();
353
354 let factory = KrakenDataClientFactory::new();
355 let config = KrakenDataClientConfig {
356 product_type: KrakenProductType::Spot,
357 environment: KrakenEnvironment::Demo,
358 ..Default::default()
359 };
360
361 let cache = Rc::new(RefCell::new(Cache::default()));
362 let clock = Rc::new(RefCell::new(TestClock::new()));
363
364 let result = factory.create("KRAKEN-TEST", &config, cache.into(), clock);
365 let err = match result {
366 Ok(_) => panic!("expected validation error, factory returned Ok"),
367 Err(e) => e.to_string(),
368 };
369 assert!(
370 err.contains("Kraken Spot does not support the demo environment"),
371 "unexpected error: {err}"
372 );
373 }
374
375 #[rstest]
376 fn test_kraken_execution_client_factory_accepts_leverage_on_margin_account() {
377 let factory = KrakenExecutionClientFactory::new();
378 let config = KrakenExecutionClientConfig {
379 product_type: KrakenProductType::Spot,
380 spot_account_type: AccountType::Margin,
381 default_leverage: Some(3),
382 ..Default::default()
383 };
384 let cache = Rc::new(RefCell::new(Cache::default()));
385
386 let result = factory.create(
387 TraderId::from("TRADER-001"),
388 "KRAKEN-TEST",
389 &config,
390 cache.into(),
391 );
392 assert!(result.is_ok());
393 }
394}