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