nautilus_architect_ax/
factories.rs1use 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#[derive(Debug)]
55pub struct AxDataClientFactory;
56
57impl AxDataClientFactory {
58 #[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, 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, 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 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#[derive(Debug)]
156pub struct AxExecutionClientFactory;
157
158impl AxExecutionClientFactory {
159 #[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 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, 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}