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 _clock: Rc<RefCell<dyn Clock>>,
157 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
158 let kraken_config = config
159 .as_any()
160 .downcast_ref::<KrakenExecutionClientConfig>()
161 .ok_or_else(|| {
162 anyhow::anyhow!(
163 "Invalid config type for KrakenExecutionClientFactory. Expected KrakenExecutionClientConfig, was {config:?}",
164 )
165 })?
166 .clone();
167
168 kraken_config.validate()?;
169
170 let oms_type = OmsType::Netting;
171 let account_type = match kraken_config.product_type {
172 KrakenProductType::Spot => kraken_config.spot_account_type,
173 KrakenProductType::Futures => AccountType::Margin,
174 };
175
176 let client_id = ClientId::from(name);
177 let core = ExecutionClientCore::new(
178 trader_id,
179 client_id,
180 *KRAKEN_VENUE,
181 oms_type,
182 kraken_config.account_id,
183 account_type,
184 None, cache,
186 );
187
188 match kraken_config.product_type {
189 KrakenProductType::Spot => {
190 let client = KrakenSpotExecutionClient::new(core, kraken_config)?;
191 Ok(Box::new(client))
192 }
193 KrakenProductType::Futures => {
194 let client = KrakenFuturesExecutionClient::new(core, kraken_config)?;
195 Ok(Box::new(client))
196 }
197 }
198 }
199
200 fn name(&self) -> &'static str {
201 KRAKEN
202 }
203
204 fn config_type(&self) -> &'static str {
205 "KrakenExecutionClientConfig"
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use std::{cell::RefCell, rc::Rc};
212
213 use nautilus_common::{
214 cache::Cache,
215 clock::VirtualClock,
216 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
217 live::runner::set_data_event_sender,
218 messages::DataEvent,
219 };
220 use rstest::rstest;
221
222 use super::*;
223 use crate::common::enums::{KrakenEnvironment, KrakenProductType};
224
225 fn setup_test_env() {
226 let (sender, _receiver) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
227 set_data_event_sender(sender);
228 }
229
230 #[rstest]
231 fn test_kraken_data_client_factory_creation() {
232 let factory = KrakenDataClientFactory::new();
233 assert_eq!(factory.name(), KRAKEN);
234 assert_eq!(factory.config_type(), "KrakenDataClientConfig");
235 }
236
237 #[rstest]
238 fn test_kraken_data_client_factory_default() {
239 let factory = KrakenDataClientFactory::new();
240 assert_eq!(factory.name(), KRAKEN);
241 }
242
243 #[rstest]
244 fn test_kraken_data_client_config_implements_client_config() {
245 let config = KrakenDataClientConfig {
246 product_type: KrakenProductType::Spot,
247 ..Default::default()
248 };
249
250 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
251 let downcasted = boxed_config
252 .as_any()
253 .downcast_ref::<KrakenDataClientConfig>();
254
255 assert!(downcasted.is_some());
256 }
257
258 #[rstest]
259 fn test_kraken_data_client_factory_creates_client() {
260 setup_test_env();
261
262 let factory = KrakenDataClientFactory::new();
263 let config = KrakenDataClientConfig {
264 product_type: KrakenProductType::Spot,
265 ..Default::default()
266 };
267
268 let cache = Rc::new(RefCell::new(Cache::default()));
269 let clock = Rc::new(RefCell::new(VirtualClock::new()));
270
271 let result = factory.create("KRAKEN-TEST", &config, cache.into(), clock);
272 assert!(result.is_ok());
273
274 let client = result.unwrap();
275 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
276 }
277
278 #[rstest]
279 fn test_kraken_execution_client_factory_creates_spot_client_with_netting_oms() {
280 let factory = KrakenExecutionClientFactory::new();
281 let config = KrakenExecutionClientConfig {
282 product_type: KrakenProductType::Spot,
283 ..Default::default()
284 };
285 let cache = Rc::new(RefCell::new(Cache::default()));
286
287 let result = factory.create(
288 TraderId::from("TRADER-001"),
289 "KRAKEN-TEST",
290 &config,
291 cache.into(),
292 Rc::new(RefCell::new(VirtualClock::new())),
293 );
294 assert!(result.is_ok());
295
296 let client = result.unwrap();
297 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
298 assert_eq!(client.account_id(), config.account_id);
299 assert_eq!(client.oms_type(), OmsType::Netting);
300 }
301
302 #[rstest]
303 fn test_kraken_execution_client_factory_creates_futures_client_with_netting_oms() {
304 let factory = KrakenExecutionClientFactory::new();
305 let config = KrakenExecutionClientConfig {
306 product_type: KrakenProductType::Futures,
307 ..Default::default()
308 };
309 let cache = Rc::new(RefCell::new(Cache::default()));
310
311 let result = factory.create(
312 TraderId::from("TRADER-001"),
313 "KRAKEN-TEST",
314 &config,
315 cache.into(),
316 Rc::new(RefCell::new(VirtualClock::new())),
317 );
318 assert!(result.is_ok());
319
320 let client = result.unwrap();
321 assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
322 assert_eq!(client.account_id(), config.account_id);
323 assert_eq!(client.oms_type(), OmsType::Netting);
324 }
325
326 #[rstest]
327 fn test_kraken_execution_client_factory_rejects_leverage_on_cash_account() {
328 let factory = KrakenExecutionClientFactory::new();
329 let config = KrakenExecutionClientConfig {
330 product_type: KrakenProductType::Spot,
331 spot_account_type: AccountType::Cash,
332 default_leverage: Some(3),
333 ..Default::default()
334 };
335 let cache = Rc::new(RefCell::new(Cache::default()));
336
337 let result = factory.create(
338 TraderId::from("TRADER-001"),
339 "KRAKEN-TEST",
340 &config,
341 cache.into(),
342 Rc::new(RefCell::new(VirtualClock::new())),
343 );
344 let err = match result {
345 Ok(_) => panic!("expected validation error, factory returned Ok"),
346 Err(e) => e.to_string(),
347 };
348 assert!(
349 err.contains("default_leverage requires spot_account_type=Margin"),
350 "unexpected error: {err}"
351 );
352 }
353
354 #[rstest]
355 fn test_kraken_data_client_factory_rejects_spot_demo() {
356 setup_test_env();
357
358 let factory = KrakenDataClientFactory::new();
359 let config = KrakenDataClientConfig {
360 product_type: KrakenProductType::Spot,
361 environment: KrakenEnvironment::Demo,
362 ..Default::default()
363 };
364
365 let cache = Rc::new(RefCell::new(Cache::default()));
366 let clock = Rc::new(RefCell::new(VirtualClock::new()));
367
368 let result = factory.create("KRAKEN-TEST", &config, cache.into(), clock);
369 let err = match result {
370 Ok(_) => panic!("expected validation error, factory returned Ok"),
371 Err(e) => e.to_string(),
372 };
373 assert!(
374 err.contains("Kraken Spot does not support the demo environment"),
375 "unexpected error: {err}"
376 );
377 }
378
379 #[rstest]
380 fn test_kraken_execution_client_factory_accepts_leverage_on_margin_account() {
381 let factory = KrakenExecutionClientFactory::new();
382 let config = KrakenExecutionClientConfig {
383 product_type: KrakenProductType::Spot,
384 spot_account_type: AccountType::Margin,
385 default_leverage: Some(3),
386 ..Default::default()
387 };
388 let cache = Rc::new(RefCell::new(Cache::default()));
389
390 let result = factory.create(
391 TraderId::from("TRADER-001"),
392 "KRAKEN-TEST",
393 &config,
394 cache.into(),
395 Rc::new(RefCell::new(VirtualClock::new())),
396 );
397 assert!(result.is_ok());
398 }
399}