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