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