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