Skip to main content

nautilus_architect_ax/
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 AX Exchange 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::{
34        consts::{AX, AX_VENUE},
35        credential::Credential,
36    },
37    config::{AxDataClientConfig, AxExecutionClientConfig},
38    data::AxDataClient,
39    execution::AxExecutionClient,
40    http::client::AxHttpClient,
41    websocket::data::AxMdWebSocketClient,
42};
43
44impl ClientConfig for AxDataClientConfig {
45    fn as_any(&self) -> &dyn Any {
46        self
47    }
48}
49
50impl ClientConfig for AxExecutionClientConfig {
51    fn as_any(&self) -> &dyn Any {
52        self
53    }
54}
55
56/// Factory for creating AX Exchange data clients.
57#[derive(Debug, Clone)]
58#[cfg_attr(
59    feature = "python",
60    pyo3::pyclass(module = "nautilus_trader.adapters.architect_ax", from_py_object)
61)]
62#[cfg_attr(
63    feature = "python",
64    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.architect_ax")
65)]
66pub struct AxDataClientFactory;
67
68impl AxDataClientFactory {
69    /// Creates a new [`AxDataClientFactory`] instance.
70    #[must_use]
71    pub const fn new() -> Self {
72        Self
73    }
74}
75
76impl Default for AxDataClientFactory {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl DataClientFactory for AxDataClientFactory {
83    fn create(
84        &self,
85        name: &str,
86        config: &dyn ClientConfig,
87        _cache: CacheView,
88        _clock: Rc<RefCell<dyn Clock>>,
89    ) -> anyhow::Result<Box<dyn DataClient>> {
90        let ax_config = config
91            .as_any()
92            .downcast_ref::<AxDataClientConfig>()
93            .ok_or_else(|| {
94                anyhow::anyhow!(
95                    "Invalid config type for AxDataClientFactory. Expected AxDataClientConfig, was {config:?}",
96                )
97            })?
98            .clone();
99
100        let client_id = ClientId::from(name);
101
102        let http_client = if ax_config.has_api_credentials() {
103            let credential =
104                Credential::resolve(ax_config.api_key.clone(), ax_config.api_secret.clone())
105                    .ok_or_else(|| anyhow::anyhow!("API credentials not configured"))?;
106
107            AxHttpClient::with_credentials(
108                credential.api_key().to_string(),
109                credential.api_secret().to_string(),
110                Some(ax_config.http_base_url()),
111                None, // orders_base_url
112                ax_config.http_timeout_secs,
113                ax_config.max_retries,
114                ax_config.retry_delay_initial_ms,
115                ax_config.retry_delay_max_ms,
116                ax_config.proxy_url.clone(),
117            )
118            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
119        } else {
120            AxHttpClient::new(
121                Some(ax_config.http_base_url()),
122                None, // orders_base_url
123                ax_config.http_timeout_secs,
124                ax_config.max_retries,
125                ax_config.retry_delay_initial_ms,
126                ax_config.retry_delay_max_ms,
127                ax_config.proxy_url.clone(),
128            )
129            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
130        };
131
132        let ws_url = ax_config.ws_public_url();
133
134        // Token set during connect
135        let ws_client = AxMdWebSocketClient::without_auth(
136            ws_url,
137            ax_config.heartbeat_interval_secs,
138            ax_config.transport_backend,
139            ax_config.proxy_url.clone(),
140        );
141
142        let client = AxDataClient::new(client_id, ax_config, http_client, ws_client)?;
143        Ok(Box::new(client))
144    }
145
146    fn name(&self) -> &'static str {
147        AX
148    }
149
150    fn config_type(&self) -> &'static str {
151        "AxDataClientConfig"
152    }
153}
154
155/// Factory for creating AX Exchange execution clients.
156#[derive(Debug, Clone)]
157#[cfg_attr(
158    feature = "python",
159    pyo3::pyclass(module = "nautilus_trader.adapters.architect_ax", from_py_object)
160)]
161#[cfg_attr(
162    feature = "python",
163    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.architect_ax")
164)]
165pub struct AxExecutionClientFactory;
166
167impl AxExecutionClientFactory {
168    /// Creates a new [`AxExecutionClientFactory`] instance.
169    #[must_use]
170    pub const fn new() -> Self {
171        Self
172    }
173}
174
175impl Default for AxExecutionClientFactory {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181impl ExecutionClientFactory for AxExecutionClientFactory {
182    fn create(
183        &self,
184        trader_id: TraderId,
185        name: &str,
186        config: &dyn ClientConfig,
187        cache: CacheView,
188    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
189        let ax_config = config
190            .as_any()
191            .downcast_ref::<AxExecutionClientConfig>()
192            .ok_or_else(|| {
193                anyhow::anyhow!(
194                    "Invalid config type for AxExecutionClientFactory. Expected AxExecutionClientConfig, was {config:?}",
195                )
196            })?
197            .clone();
198
199        // AX uses netting for perpetual futures
200        let oms_type = OmsType::Netting;
201        let account_type = AccountType::Margin;
202
203        let core = ExecutionClientCore::new(
204            trader_id,
205            ClientId::from(name),
206            *AX_VENUE,
207            oms_type,
208            ax_config.account_id,
209            account_type,
210            None, // base_currency
211            cache,
212        );
213
214        let client = AxExecutionClient::new(core, ax_config)?;
215
216        Ok(Box::new(client))
217    }
218
219    fn name(&self) -> &'static str {
220        AX
221    }
222
223    fn config_type(&self) -> &'static str {
224        "AxExecutionClientConfig"
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use nautilus_common::factories::ClientConfig;
231    use rstest::rstest;
232
233    use super::*;
234    use crate::config::AxDataClientConfig;
235
236    #[rstest]
237    fn test_ax_data_client_config_implements_client_config() {
238        let config = AxDataClientConfig::default();
239
240        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
241        let downcasted = boxed_config.as_any().downcast_ref::<AxDataClientConfig>();
242
243        assert!(downcasted.is_some());
244    }
245
246    #[rstest]
247    fn test_ax_data_client_factory_creation() {
248        let factory = AxDataClientFactory::new();
249        assert_eq!(factory.name(), AX);
250        assert_eq!(factory.config_type(), "AxDataClientConfig");
251    }
252
253    #[rstest]
254    fn test_ax_data_client_factory_default() {
255        let factory = AxDataClientFactory;
256        assert_eq!(factory.name(), AX);
257    }
258}