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};
25use nautilus_live::ExecutionClientCore;
26use nautilus_model::{
27    enums::{AccountType, OmsType},
28    identifiers::ClientId,
29};
30use nautilus_system::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
31
32use crate::{
33    common::consts::{AX_VENUE, AX_WS_PUBLIC_URL, AX_WS_SANDBOX_PUBLIC_URL},
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 api_key = ax_config
93                .api_key
94                .clone()
95                .or_else(|| std::env::var("AX_API_KEY").ok())
96                .ok_or_else(|| anyhow::anyhow!("AX_API_KEY not configured"))?;
97
98            let api_secret = ax_config
99                .api_secret
100                .clone()
101                .or_else(|| std::env::var("AX_API_SECRET").ok())
102                .ok_or_else(|| anyhow::anyhow!("AX_API_SECRET not configured"))?;
103
104            AxHttpClient::with_credentials(
105                api_key,
106                api_secret,
107                Some(ax_config.http_base_url()),
108                None, // orders_base_url
109                ax_config.http_timeout_secs,
110                ax_config.max_retries,
111                ax_config.retry_delay_initial_ms,
112                ax_config.retry_delay_max_ms,
113                ax_config.http_proxy_url.clone(),
114            )
115            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
116        } else {
117            AxHttpClient::new(
118                Some(ax_config.http_base_url()),
119                None, // orders_base_url
120                ax_config.http_timeout_secs,
121                ax_config.max_retries,
122                ax_config.retry_delay_initial_ms,
123                ax_config.retry_delay_max_ms,
124                ax_config.http_proxy_url.clone(),
125            )
126            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
127        };
128
129        let ws_url = ax_config.base_url_ws_public.clone().unwrap_or_else(|| {
130            if ax_config.is_sandbox {
131                AX_WS_SANDBOX_PUBLIC_URL.to_string()
132            } else {
133                AX_WS_PUBLIC_URL.to_string()
134            }
135        });
136
137        // Token set during connect
138        let ws_client =
139            AxMdWebSocketClient::without_auth(ws_url, ax_config.heartbeat_interval_secs);
140
141        let client = AxDataClient::new(client_id, ax_config, http_client, ws_client)?;
142        Ok(Box::new(client))
143    }
144
145    fn name(&self) -> &'static str {
146        "AX"
147    }
148
149    fn config_type(&self) -> &'static str {
150        "AxDataClientConfig"
151    }
152}
153
154/// Factory for creating AX Exchange execution clients.
155#[derive(Debug)]
156pub struct AxExecutionClientFactory;
157
158impl AxExecutionClientFactory {
159    /// Creates a new [`AxExecutionClientFactory`] instance.
160    #[must_use]
161    pub const fn new() -> Self {
162        Self
163    }
164}
165
166impl Default for AxExecutionClientFactory {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172impl ExecutionClientFactory for AxExecutionClientFactory {
173    fn create(
174        &self,
175        name: &str,
176        config: &dyn ClientConfig,
177        cache: Rc<RefCell<Cache>>,
178    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
179        let ax_config = config
180            .as_any()
181            .downcast_ref::<AxExecClientConfig>()
182            .ok_or_else(|| {
183                anyhow::anyhow!(
184                    "Invalid config type for AxExecutionClientFactory. Expected AxExecClientConfig, was {config:?}",
185                )
186            })?
187            .clone();
188
189        // AX uses netting for perpetual futures
190        let oms_type = OmsType::Netting;
191        let account_type = AccountType::Margin;
192
193        let core = ExecutionClientCore::new(
194            ax_config.trader_id,
195            ClientId::from(name),
196            *AX_VENUE,
197            oms_type,
198            ax_config.account_id,
199            account_type,
200            None, // base_currency
201            cache,
202        );
203
204        let client = AxExecutionClient::new(core, ax_config)?;
205
206        Ok(Box::new(client))
207    }
208
209    fn name(&self) -> &'static str {
210        "AX"
211    }
212
213    fn config_type(&self) -> &'static str {
214        "AxExecClientConfig"
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use nautilus_system::factories::ClientConfig;
221    use rstest::rstest;
222
223    use super::*;
224    use crate::config::AxDataClientConfig;
225
226    #[rstest]
227    fn test_ax_data_client_config_implements_client_config() {
228        let config = AxDataClientConfig::default();
229
230        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
231        let downcasted = boxed_config.as_any().downcast_ref::<AxDataClientConfig>();
232
233        assert!(downcasted.is_some());
234    }
235
236    #[rstest]
237    fn test_ax_data_client_factory_creation() {
238        let factory = AxDataClientFactory::new();
239        assert_eq!(factory.name(), "AX");
240        assert_eq!(factory.config_type(), "AxDataClientConfig");
241    }
242
243    #[rstest]
244    fn test_ax_data_client_factory_default() {
245        let factory = AxDataClientFactory;
246        assert_eq!(factory.name(), "AX");
247    }
248}