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