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 = Credential::resolve(
104                ax_config.api_key.clone().map(|value| value.into_inner()),
105                ax_config.api_secret.clone().map(|value| value.into_inner()),
106            )
107            .ok_or_else(|| anyhow::anyhow!("API credentials not configured"))?;
108
109            AxHttpClient::with_credentials(
110                credential.api_key().to_string(),
111                credential.api_secret().to_string(),
112                Some(ax_config.http_base_url()),
113                None, // orders_base_url
114                ax_config.http_timeout_secs,
115                ax_config.max_retries,
116                ax_config.retry_delay_initial_ms,
117                ax_config.retry_delay_max_ms,
118                ax_config
119                    .proxy_url
120                    .as_ref()
121                    .map(|url| url.expose_secret().to_owned()),
122            )
123            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
124        } else {
125            AxHttpClient::new(
126                Some(ax_config.http_base_url()),
127                None, // orders_base_url
128                ax_config.http_timeout_secs,
129                ax_config.max_retries,
130                ax_config.retry_delay_initial_ms,
131                ax_config.retry_delay_max_ms,
132                ax_config
133                    .proxy_url
134                    .as_ref()
135                    .map(|url| url.expose_secret().to_owned()),
136            )
137            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
138        };
139
140        let ws_url = ax_config.ws_public_url();
141
142        // Token set during connect
143        let ws_client = AxMdWebSocketClient::without_auth(
144            ws_url,
145            ax_config.heartbeat_interval_secs,
146            ax_config.transport_backend,
147            ax_config
148                .proxy_url
149                .as_ref()
150                .map(|url| url.expose_secret().to_owned()),
151        );
152
153        let client = AxDataClient::new(client_id, ax_config, http_client, ws_client)?;
154        Ok(Box::new(client))
155    }
156
157    fn name(&self) -> &'static str {
158        AX
159    }
160
161    fn config_type(&self) -> &'static str {
162        "AxDataClientConfig"
163    }
164}
165
166/// Factory for creating AX Exchange execution clients.
167#[derive(Debug, Clone)]
168#[cfg_attr(
169    feature = "python",
170    pyo3::pyclass(module = "nautilus_trader.adapters.architect_ax", from_py_object)
171)]
172#[cfg_attr(
173    feature = "python",
174    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.architect_ax")
175)]
176pub struct AxExecutionClientFactory;
177
178impl AxExecutionClientFactory {
179    /// Creates a new [`AxExecutionClientFactory`] instance.
180    #[must_use]
181    pub const fn new() -> Self {
182        Self
183    }
184}
185
186impl Default for AxExecutionClientFactory {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192impl ExecutionClientFactory for AxExecutionClientFactory {
193    fn create(
194        &self,
195        trader_id: TraderId,
196        name: &str,
197        config: &dyn ClientConfig,
198        cache: CacheView,
199        _clock: Rc<RefCell<dyn Clock>>,
200    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
201        let ax_config = config
202            .as_any()
203            .downcast_ref::<AxExecutionClientConfig>()
204            .ok_or_else(|| {
205                anyhow::anyhow!(
206                    "Invalid config type for AxExecutionClientFactory. Expected AxExecutionClientConfig, was {config:?}",
207                )
208            })?
209            .clone();
210
211        // AX uses netting for perpetual futures
212        let oms_type = OmsType::Netting;
213        let account_type = AccountType::Margin;
214
215        let core = ExecutionClientCore::new(
216            trader_id,
217            ClientId::from(name),
218            *AX_VENUE,
219            oms_type,
220            ax_config.account_id,
221            account_type,
222            None, // base_currency
223            cache,
224        );
225
226        let client = AxExecutionClient::new(core, ax_config)?;
227
228        Ok(Box::new(client))
229    }
230
231    fn name(&self) -> &'static str {
232        AX
233    }
234
235    fn config_type(&self) -> &'static str {
236        "AxExecutionClientConfig"
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use nautilus_common::factories::ClientConfig;
243    use rstest::rstest;
244
245    use super::*;
246    use crate::config::AxDataClientConfig;
247
248    #[rstest]
249    fn test_ax_data_client_config_implements_client_config() {
250        let config = AxDataClientConfig::default();
251
252        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
253        let downcasted = boxed_config.as_any().downcast_ref::<AxDataClientConfig>();
254
255        assert!(downcasted.is_some());
256    }
257
258    #[rstest]
259    fn test_ax_data_client_factory_creation() {
260        let factory = AxDataClientFactory::new();
261        assert_eq!(factory.name(), AX);
262        assert_eq!(factory.config_type(), "AxDataClientConfig");
263    }
264
265    #[rstest]
266    fn test_ax_data_client_factory_default() {
267        let factory = AxDataClientFactory;
268        assert_eq!(factory.name(), AX);
269    }
270}