Skip to main content

nautilus_derive/
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 Derive clients and components.
17
18use 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::{AccountId, ClientId, TraderId},
30};
31
32use crate::{
33    common::consts::{DERIVE, DERIVE_VENUE},
34    config::{DeriveDataClientConfig, DeriveExecClientConfig},
35    data::DeriveDataClient,
36    execution::DeriveExecutionClient,
37};
38
39impl ClientConfig for DeriveDataClientConfig {
40    fn as_any(&self) -> &dyn Any {
41        self
42    }
43}
44
45impl ClientConfig for DeriveExecClientConfig {
46    fn as_any(&self) -> &dyn Any {
47        self
48    }
49}
50
51/// Factory for creating Derive data clients.
52#[derive(Debug, Clone)]
53#[cfg_attr(
54    feature = "python",
55    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
56)]
57#[cfg_attr(
58    feature = "python",
59    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
60)]
61pub struct DeriveDataClientFactory;
62
63impl DeriveDataClientFactory {
64    #[must_use]
65    pub const fn new() -> Self {
66        Self
67    }
68}
69
70impl Default for DeriveDataClientFactory {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl DataClientFactory for DeriveDataClientFactory {
77    fn create(
78        &self,
79        name: &str,
80        config: &dyn ClientConfig,
81        _cache: CacheView,
82        _clock: Rc<RefCell<dyn Clock>>,
83    ) -> anyhow::Result<Box<dyn DataClient>> {
84        let derive_config = config
85            .as_any()
86            .downcast_ref::<DeriveDataClientConfig>()
87            .ok_or_else(|| {
88                anyhow::anyhow!(
89                    "Invalid config type for DeriveDataClientFactory. Expected DeriveDataClientConfig, was {config:?}",
90                )
91            })?
92            .clone();
93
94        let client = DeriveDataClient::new(ClientId::from(name), derive_config)?;
95        Ok(Box::new(client))
96    }
97
98    fn name(&self) -> &'static str {
99        DERIVE
100    }
101
102    fn config_type(&self) -> &'static str {
103        stringify!(DeriveDataClientConfig)
104    }
105}
106
107/// Configuration for creating Derive execution clients via factory.
108///
109/// Bundles the trader and account identifiers required by
110/// [`ExecutionClientCore`] alongside the underlying execution client config.
111#[derive(Clone, Debug)]
112#[cfg_attr(
113    feature = "python",
114    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
115)]
116#[cfg_attr(
117    feature = "python",
118    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
119)]
120pub struct DeriveExecFactoryConfig {
121    /// The trader ID for the execution client.
122    pub trader_id: TraderId,
123    /// The account ID for the execution client.
124    pub account_id: AccountId,
125    /// The underlying execution client configuration.
126    pub config: DeriveExecClientConfig,
127}
128
129impl ClientConfig for DeriveExecFactoryConfig {
130    fn as_any(&self) -> &dyn Any {
131        self
132    }
133}
134
135/// Factory for creating Derive execution clients.
136#[derive(Debug, Clone)]
137#[cfg_attr(
138    feature = "python",
139    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.derive", from_py_object)
140)]
141#[cfg_attr(
142    feature = "python",
143    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
144)]
145pub struct DeriveExecutionClientFactory;
146
147impl DeriveExecutionClientFactory {
148    #[must_use]
149    pub const fn new() -> Self {
150        Self
151    }
152}
153
154impl Default for DeriveExecutionClientFactory {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl ExecutionClientFactory for DeriveExecutionClientFactory {
161    fn create(
162        &self,
163        name: &str,
164        config: &dyn ClientConfig,
165        cache: CacheView,
166    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
167        let factory_config = config
168            .as_any()
169            .downcast_ref::<DeriveExecFactoryConfig>()
170            .ok_or_else(|| {
171                anyhow::anyhow!(
172                    "Invalid config type for DeriveExecutionClientFactory. Expected DeriveExecFactoryConfig, was {config:?}",
173                )
174            })?
175            .clone();
176
177        // Derive perpetuals net per-subaccount; cash accounts are spot.
178        let oms_type = OmsType::Netting;
179        let account_type = AccountType::Margin;
180
181        let core = ExecutionClientCore::new(
182            factory_config.trader_id,
183            ClientId::from(name),
184            *DERIVE_VENUE,
185            oms_type,
186            factory_config.account_id,
187            account_type,
188            None,
189            cache,
190        );
191
192        let client = DeriveExecutionClient::new(core, factory_config.config)?;
193        Ok(Box::new(client))
194    }
195
196    fn name(&self) -> &'static str {
197        DERIVE
198    }
199
200    fn config_type(&self) -> &'static str {
201        stringify!(DeriveExecFactoryConfig)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use nautilus_common::{
208        cache::Cache, clock::TestClock, live::runner::replace_data_event_sender,
209        messages::DataEvent,
210    };
211    use rstest::rstest;
212
213    use super::*;
214
215    #[derive(Debug)]
216    struct WrongConfig;
217
218    impl ClientConfig for WrongConfig {
219        fn as_any(&self) -> &dyn Any {
220            self
221        }
222    }
223
224    #[rstest]
225    fn test_data_client_factory_metadata() {
226        let factory = DeriveDataClientFactory::new();
227
228        assert_eq!(factory.name(), DERIVE);
229        assert_eq!(factory.config_type(), "DeriveDataClientConfig");
230    }
231
232    #[rstest]
233    fn test_data_client_factory_creates_client() {
234        let factory = DeriveDataClientFactory::new();
235        let cache = Rc::new(RefCell::new(Cache::default()));
236        let clock = Rc::new(RefCell::new(TestClock::new()));
237        let config = DeriveDataClientConfig::default();
238        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
239        replace_data_event_sender(tx);
240
241        let client = factory
242            .create(DERIVE, &config, cache.into(), clock)
243            .expect("factory creates data client");
244
245        assert_eq!(client.client_id(), ClientId::from(DERIVE));
246        assert_eq!(client.venue(), Some(*DERIVE_VENUE));
247    }
248
249    #[rstest]
250    fn test_data_client_factory_rejects_wrong_config_type() {
251        let factory = DeriveDataClientFactory::new();
252        let cache = Rc::new(RefCell::new(Cache::default()));
253        let clock = Rc::new(RefCell::new(TestClock::new()));
254        let wrong_config = WrongConfig;
255
256        let result = factory.create(DERIVE, &wrong_config, cache.into(), clock);
257
258        assert!(result.is_err());
259        assert!(
260            result
261                .err()
262                .unwrap()
263                .to_string()
264                .contains("Invalid config type")
265        );
266    }
267
268    #[rstest]
269    fn test_exec_client_factory_metadata() {
270        let factory = DeriveExecutionClientFactory::new();
271
272        assert_eq!(factory.name(), DERIVE);
273        assert_eq!(factory.config_type(), "DeriveExecFactoryConfig");
274    }
275
276    #[rstest]
277    fn test_exec_client_factory_rejects_wrong_config_type() {
278        let factory = DeriveExecutionClientFactory::new();
279        let cache = Rc::new(RefCell::new(Cache::default()));
280        let wrong_config = DeriveDataClientConfig::default();
281
282        let result = factory.create(DERIVE, &wrong_config, cache.into());
283
284        assert!(result.is_err());
285        assert!(
286            result
287                .err()
288                .unwrap()
289                .to_string()
290                .contains("Invalid config type")
291        );
292    }
293
294    #[rstest]
295    fn test_exec_factory_config_implements_client_config() {
296        let factory_config = DeriveExecFactoryConfig {
297            trader_id: TraderId::from("TRADER-001"),
298            account_id: AccountId::from("DERIVE-001"),
299            config: DeriveExecClientConfig::default(),
300        };
301
302        let boxed: Box<dyn ClientConfig> = Box::new(factory_config);
303        assert!(
304            boxed
305                .as_any()
306                .downcast_ref::<DeriveExecFactoryConfig>()
307                .is_some()
308        );
309    }
310}