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.adapters.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.adapters.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
129#[cfg(feature = "python")]
130nautilus_core::impl_pyo3_config_getters!(DeriveExecFactoryConfig {
131    trader_id: TraderId,
132    account_id: AccountId,
133    config: DeriveExecClientConfig,
134});
135
136impl ClientConfig for DeriveExecFactoryConfig {
137    fn as_any(&self) -> &dyn Any {
138        self
139    }
140}
141
142/// Factory for creating Derive execution clients.
143#[derive(Debug, Clone)]
144#[cfg_attr(
145    feature = "python",
146    pyo3::pyclass(module = "nautilus_trader.adapters.derive", from_py_object)
147)]
148#[cfg_attr(
149    feature = "python",
150    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.derive")
151)]
152pub struct DeriveExecutionClientFactory;
153
154impl DeriveExecutionClientFactory {
155    #[must_use]
156    pub const fn new() -> Self {
157        Self
158    }
159}
160
161impl Default for DeriveExecutionClientFactory {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl ExecutionClientFactory for DeriveExecutionClientFactory {
168    fn create(
169        &self,
170        name: &str,
171        config: &dyn ClientConfig,
172        cache: CacheView,
173    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
174        let factory_config = config
175            .as_any()
176            .downcast_ref::<DeriveExecFactoryConfig>()
177            .ok_or_else(|| {
178                anyhow::anyhow!(
179                    "Invalid config type for DeriveExecutionClientFactory. Expected DeriveExecFactoryConfig, was {config:?}",
180                )
181            })?
182            .clone();
183
184        // Derive perpetuals net per-subaccount; cash accounts are spot.
185        let oms_type = OmsType::Netting;
186        let account_type = AccountType::Margin;
187
188        let core = ExecutionClientCore::new(
189            factory_config.trader_id,
190            ClientId::from(name),
191            *DERIVE_VENUE,
192            oms_type,
193            factory_config.account_id,
194            account_type,
195            None,
196            cache,
197        );
198
199        let client = DeriveExecutionClient::new(core, factory_config.config)?;
200        Ok(Box::new(client))
201    }
202
203    fn name(&self) -> &'static str {
204        DERIVE
205    }
206
207    fn config_type(&self) -> &'static str {
208        stringify!(DeriveExecFactoryConfig)
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use nautilus_common::{
215        cache::Cache, clock::TestClock, live::runner::replace_data_event_sender,
216        messages::DataEvent,
217    };
218    use rstest::rstest;
219
220    use super::*;
221
222    #[derive(Debug)]
223    struct WrongConfig;
224
225    impl ClientConfig for WrongConfig {
226        fn as_any(&self) -> &dyn Any {
227            self
228        }
229    }
230
231    #[rstest]
232    fn test_data_client_factory_metadata() {
233        let factory = DeriveDataClientFactory::new();
234
235        assert_eq!(factory.name(), DERIVE);
236        assert_eq!(factory.config_type(), "DeriveDataClientConfig");
237    }
238
239    #[rstest]
240    fn test_data_client_factory_creates_client() {
241        let factory = DeriveDataClientFactory::new();
242        let cache = Rc::new(RefCell::new(Cache::default()));
243        let clock = Rc::new(RefCell::new(TestClock::new()));
244        let config = DeriveDataClientConfig::default();
245        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<DataEvent>();
246        replace_data_event_sender(tx);
247
248        let client = factory
249            .create(DERIVE, &config, cache.into(), clock)
250            .expect("factory creates data client");
251
252        assert_eq!(client.client_id(), ClientId::from(DERIVE));
253        assert_eq!(client.venue(), Some(*DERIVE_VENUE));
254    }
255
256    #[rstest]
257    fn test_data_client_factory_rejects_wrong_config_type() {
258        let factory = DeriveDataClientFactory::new();
259        let cache = Rc::new(RefCell::new(Cache::default()));
260        let clock = Rc::new(RefCell::new(TestClock::new()));
261        let wrong_config = WrongConfig;
262
263        let result = factory.create(DERIVE, &wrong_config, cache.into(), clock);
264
265        assert!(result.is_err());
266        assert!(
267            result
268                .err()
269                .unwrap()
270                .to_string()
271                .contains("Invalid config type")
272        );
273    }
274
275    #[rstest]
276    fn test_exec_client_factory_metadata() {
277        let factory = DeriveExecutionClientFactory::new();
278
279        assert_eq!(factory.name(), DERIVE);
280        assert_eq!(factory.config_type(), "DeriveExecFactoryConfig");
281    }
282
283    #[rstest]
284    fn test_exec_client_factory_rejects_wrong_config_type() {
285        let factory = DeriveExecutionClientFactory::new();
286        let cache = Rc::new(RefCell::new(Cache::default()));
287        let wrong_config = DeriveDataClientConfig::default();
288
289        let result = factory.create(DERIVE, &wrong_config, cache.into());
290
291        assert!(result.is_err());
292        assert!(
293            result
294                .err()
295                .unwrap()
296                .to_string()
297                .contains("Invalid config type")
298        );
299    }
300
301    #[rstest]
302    fn test_exec_factory_config_implements_client_config() {
303        let factory_config = DeriveExecFactoryConfig {
304            trader_id: TraderId::from("TRADER-001"),
305            account_id: AccountId::from("DERIVE-001"),
306            config: DeriveExecClientConfig::default(),
307        };
308
309        let boxed: Box<dyn ClientConfig> = Box::new(factory_config);
310        assert!(
311            boxed
312                .as_any()
313                .downcast_ref::<DeriveExecFactoryConfig>()
314                .is_some()
315        );
316    }
317}