Skip to main content

nautilus_kraken/
factories.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//! Factory functions for creating Kraken clients and components.
17
18use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21    cache::Cache,
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::{consts::KRAKEN_VENUE, enums::KrakenProductType},
34    config::{KrakenDataClientConfig, KrakenExecClientConfig},
35    data::{KrakenFuturesDataClient, KrakenSpotDataClient},
36    execution::{KrakenFuturesExecutionClient, KrakenSpotExecutionClient},
37};
38
39impl ClientConfig for KrakenDataClientConfig {
40    fn as_any(&self) -> &dyn Any {
41        self
42    }
43}
44
45/// Factory for creating Kraken data clients.
46#[derive(Debug, Clone)]
47#[cfg_attr(
48    feature = "python",
49    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.kraken", from_py_object)
50)]
51#[cfg_attr(
52    feature = "python",
53    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.kraken")
54)]
55pub struct KrakenDataClientFactory;
56
57impl KrakenDataClientFactory {
58    /// Creates a new [`KrakenDataClientFactory`] instance.
59    #[must_use]
60    pub const fn new() -> Self {
61        Self
62    }
63}
64
65impl Default for KrakenDataClientFactory {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl DataClientFactory for KrakenDataClientFactory {
72    fn create(
73        &self,
74        name: &str,
75        config: &dyn ClientConfig,
76        _cache: Rc<RefCell<Cache>>,
77        _clock: Rc<RefCell<dyn Clock>>,
78    ) -> anyhow::Result<Box<dyn DataClient>> {
79        let kraken_config = config
80            .as_any()
81            .downcast_ref::<KrakenDataClientConfig>()
82            .ok_or_else(|| {
83                anyhow::anyhow!(
84                    "Invalid config type for KrakenDataClientFactory. Expected KrakenDataClientConfig, was {config:?}",
85                )
86            })?
87            .clone();
88
89        let client_id = ClientId::from(name);
90
91        match kraken_config.product_type {
92            KrakenProductType::Spot => {
93                let client = KrakenSpotDataClient::new(client_id, kraken_config)?;
94                Ok(Box::new(client))
95            }
96            KrakenProductType::Futures => {
97                let client = KrakenFuturesDataClient::new(client_id, kraken_config)?;
98                Ok(Box::new(client))
99            }
100        }
101    }
102
103    fn name(&self) -> &'static str {
104        "KRAKEN"
105    }
106
107    fn config_type(&self) -> &'static str {
108        "KrakenDataClientConfig"
109    }
110}
111
112impl ClientConfig for KrakenExecClientConfig {
113    fn as_any(&self) -> &dyn Any {
114        self
115    }
116}
117
118/// Factory for creating Kraken execution clients.
119#[derive(Debug, Clone)]
120#[cfg_attr(
121    feature = "python",
122    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.kraken", from_py_object)
123)]
124#[cfg_attr(
125    feature = "python",
126    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.kraken")
127)]
128pub struct KrakenExecutionClientFactory;
129
130impl KrakenExecutionClientFactory {
131    /// Creates a new [`KrakenExecutionClientFactory`] instance.
132    #[must_use]
133    pub const fn new() -> Self {
134        Self
135    }
136}
137
138impl Default for KrakenExecutionClientFactory {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl ExecutionClientFactory for KrakenExecutionClientFactory {
145    fn create(
146        &self,
147        name: &str,
148        config: &dyn ClientConfig,
149        cache: Rc<RefCell<Cache>>,
150    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
151        let kraken_config = config
152            .as_any()
153            .downcast_ref::<KrakenExecClientConfig>()
154            .ok_or_else(|| {
155                anyhow::anyhow!(
156                    "Invalid config type for KrakenExecutionClientFactory. Expected KrakenExecClientConfig, was {config:?}",
157                )
158            })?
159            .clone();
160
161        let oms_type = OmsType::Netting;
162        let account_type = match kraken_config.product_type {
163            KrakenProductType::Spot => AccountType::Cash,
164            KrakenProductType::Futures => AccountType::Margin,
165        };
166
167        let client_id = ClientId::from(name);
168        let core = ExecutionClientCore::new(
169            kraken_config.trader_id,
170            client_id,
171            *KRAKEN_VENUE,
172            oms_type,
173            kraken_config.account_id,
174            account_type,
175            None, // base_currency
176            cache,
177        );
178
179        match kraken_config.product_type {
180            KrakenProductType::Spot => {
181                let client = KrakenSpotExecutionClient::new(core, kraken_config)?;
182                Ok(Box::new(client))
183            }
184            KrakenProductType::Futures => {
185                let client = KrakenFuturesExecutionClient::new(core, kraken_config)?;
186                Ok(Box::new(client))
187            }
188        }
189    }
190
191    fn name(&self) -> &'static str {
192        "KRAKEN"
193    }
194
195    fn config_type(&self) -> &'static str {
196        "KrakenExecClientConfig"
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use std::{cell::RefCell, rc::Rc};
203
204    use nautilus_common::{
205        cache::Cache,
206        clock::TestClock,
207        factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
208        live::runner::set_data_event_sender,
209        messages::DataEvent,
210    };
211    use rstest::rstest;
212
213    use super::*;
214    use crate::common::enums::KrakenProductType;
215
216    fn setup_test_env() {
217        let (sender, _receiver) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
218        set_data_event_sender(sender);
219    }
220
221    #[rstest]
222    fn test_kraken_data_client_factory_creation() {
223        let factory = KrakenDataClientFactory::new();
224        assert_eq!(factory.name(), "KRAKEN");
225        assert_eq!(factory.config_type(), "KrakenDataClientConfig");
226    }
227
228    #[rstest]
229    fn test_kraken_data_client_factory_default() {
230        let factory = KrakenDataClientFactory::new();
231        assert_eq!(factory.name(), "KRAKEN");
232    }
233
234    #[rstest]
235    fn test_kraken_data_client_config_implements_client_config() {
236        let config = KrakenDataClientConfig {
237            product_type: KrakenProductType::Spot,
238            ..Default::default()
239        };
240
241        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
242        let downcasted = boxed_config
243            .as_any()
244            .downcast_ref::<KrakenDataClientConfig>();
245
246        assert!(downcasted.is_some());
247    }
248
249    #[rstest]
250    fn test_kraken_data_client_factory_creates_client() {
251        setup_test_env();
252
253        let factory = KrakenDataClientFactory::new();
254        let config = KrakenDataClientConfig {
255            product_type: KrakenProductType::Spot,
256            ..Default::default()
257        };
258
259        let cache = Rc::new(RefCell::new(Cache::default()));
260        let clock = Rc::new(RefCell::new(TestClock::new()));
261
262        let result = factory.create("KRAKEN-TEST", &config, cache, clock);
263        assert!(result.is_ok());
264
265        let client = result.unwrap();
266        assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
267    }
268
269    #[rstest]
270    fn test_kraken_execution_client_factory_creates_spot_client_with_netting_oms() {
271        let factory = KrakenExecutionClientFactory::new();
272        let config = KrakenExecClientConfig {
273            product_type: KrakenProductType::Spot,
274            ..Default::default()
275        };
276        let cache = Rc::new(RefCell::new(Cache::default()));
277
278        let result = factory.create("KRAKEN-TEST", &config, cache);
279        assert!(result.is_ok());
280
281        let client = result.unwrap();
282        assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
283        assert_eq!(client.account_id(), config.account_id);
284        assert_eq!(client.oms_type(), OmsType::Netting);
285    }
286
287    #[rstest]
288    fn test_kraken_execution_client_factory_creates_futures_client_with_netting_oms() {
289        let factory = KrakenExecutionClientFactory::new();
290        let config = KrakenExecClientConfig {
291            product_type: KrakenProductType::Futures,
292            ..Default::default()
293        };
294        let cache = Rc::new(RefCell::new(Cache::default()));
295
296        let result = factory.create("KRAKEN-TEST", &config, cache);
297        assert!(result.is_ok());
298
299        let client = result.unwrap();
300        assert_eq!(client.client_id(), ClientId::from("KRAKEN-TEST"));
301        assert_eq!(client.account_id(), config.account_id);
302        assert_eq!(client.oms_type(), OmsType::Netting);
303    }
304}