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