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 = 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, 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, 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 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#[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 #[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 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, 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}