nautilus_architect_ax/
config.rs1use nautilus_model::identifiers::{AccountId, TraderId};
19use nautilus_network::websocket::TransportBackend;
20
21use crate::common::{credential::credential_env_vars, enums::AxEnvironment};
22
23#[cfg_attr(
25 feature = "python",
26 pyo3::pyclass(
27 module = "nautilus_trader.core.nautilus_pyo3.architect",
28 from_py_object
29 )
30)]
31#[cfg_attr(
32 feature = "python",
33 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.architect_ax")
34)]
35#[derive(Clone, Debug, bon::Builder)]
36pub struct AxDataClientConfig {
37 pub api_key: Option<String>,
39 pub api_secret: Option<String>,
41 #[builder(default)]
43 pub environment: AxEnvironment,
44 pub base_url_http: Option<String>,
46 pub base_url_ws_public: Option<String>,
48 pub base_url_ws_private: Option<String>,
50 pub proxy_url: Option<String>,
52 #[builder(default = 60)]
54 pub http_timeout_secs: u64,
55 #[builder(default = 3)]
57 pub max_retries: u32,
58 #[builder(default = 1_000)]
60 pub retry_delay_initial_ms: u64,
61 #[builder(default = 10_000)]
63 pub retry_delay_max_ms: u64,
64 #[builder(default = 20)]
66 pub heartbeat_interval_secs: u64,
67 #[builder(default = 5_000)]
69 pub recv_window_ms: u64,
70 #[builder(default = 60)]
72 pub update_instruments_interval_mins: u64,
73 #[builder(default = 15)]
75 pub funding_rate_poll_interval_mins: u64,
76 #[builder(default)]
78 pub transport_backend: TransportBackend,
79}
80
81impl Default for AxDataClientConfig {
82 fn default() -> Self {
83 Self::builder().build()
84 }
85}
86
87impl AxDataClientConfig {
88 #[must_use]
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 #[must_use]
96 pub fn has_api_credentials(&self) -> bool {
97 let (key_var, secret_var) = credential_env_vars();
98 let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
99 let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
100 has_key && has_secret
101 }
102
103 #[must_use]
105 pub fn http_base_url(&self) -> String {
106 self.base_url_http
107 .clone()
108 .unwrap_or_else(|| self.environment.http_url().to_string())
109 }
110
111 #[must_use]
113 pub fn ws_public_url(&self) -> String {
114 self.base_url_ws_public
115 .clone()
116 .unwrap_or_else(|| self.environment.ws_md_url().to_string())
117 }
118
119 #[must_use]
121 pub fn ws_private_url(&self) -> String {
122 self.base_url_ws_private
123 .clone()
124 .unwrap_or_else(|| self.environment.ws_orders_url().to_string())
125 }
126}
127
128#[cfg_attr(
130 feature = "python",
131 pyo3::pyclass(
132 module = "nautilus_trader.core.nautilus_pyo3.architect",
133 from_py_object
134 )
135)]
136#[cfg_attr(
137 feature = "python",
138 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.architect_ax")
139)]
140#[derive(Clone, Debug, bon::Builder)]
141pub struct AxExecClientConfig {
142 #[builder(default = TraderId::from("TRADER-001"))]
144 pub trader_id: TraderId,
145 #[builder(default = AccountId::from("AX-001"))]
147 pub account_id: AccountId,
148 pub api_key: Option<String>,
150 pub api_secret: Option<String>,
152 #[builder(default)]
154 pub environment: AxEnvironment,
155 pub base_url_http: Option<String>,
157 pub base_url_orders: Option<String>,
159 pub base_url_ws_private: Option<String>,
161 pub proxy_url: Option<String>,
163 #[builder(default = 60)]
165 pub http_timeout_secs: u64,
166 #[builder(default = 3)]
168 pub max_retries: u32,
169 #[builder(default = 1_000)]
171 pub retry_delay_initial_ms: u64,
172 #[builder(default = 10_000)]
174 pub retry_delay_max_ms: u64,
175 #[builder(default = 30)]
177 pub heartbeat_interval_secs: u64,
178 #[builder(default = 5_000)]
180 pub recv_window_ms: u64,
181 #[builder(default)]
183 pub cancel_on_disconnect: bool,
184 #[builder(default)]
186 pub transport_backend: TransportBackend,
187}
188
189impl Default for AxExecClientConfig {
190 fn default() -> Self {
191 Self::builder().build()
192 }
193}
194
195impl AxExecClientConfig {
196 #[must_use]
198 pub fn new() -> Self {
199 Self::default()
200 }
201
202 #[must_use]
204 pub fn has_api_credentials(&self) -> bool {
205 let (key_var, secret_var) = credential_env_vars();
206 let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
207 let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
208 has_key && has_secret
209 }
210
211 #[must_use]
213 pub fn http_base_url(&self) -> String {
214 self.base_url_http
215 .clone()
216 .unwrap_or_else(|| self.environment.http_url().to_string())
217 }
218
219 #[must_use]
221 pub fn orders_base_url(&self) -> String {
222 self.base_url_orders
223 .clone()
224 .unwrap_or_else(|| self.environment.orders_url().to_string())
225 }
226
227 #[must_use]
229 pub fn ws_private_url(&self) -> String {
230 self.base_url_ws_private
231 .clone()
232 .unwrap_or_else(|| self.environment.ws_orders_url().to_string())
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use rstest::rstest;
239
240 use super::*;
241 use crate::common::consts::{
242 AX_HTTP_SANDBOX_URL, AX_HTTP_URL, AX_ORDERS_SANDBOX_URL, AX_ORDERS_URL, AX_WS_PRIVATE_URL,
243 AX_WS_PUBLIC_URL, AX_WS_SANDBOX_PRIVATE_URL, AX_WS_SANDBOX_PUBLIC_URL,
244 };
245
246 #[rstest]
247 fn test_data_config_sandbox_urls_match_consts() {
248 let config = AxDataClientConfig::builder()
249 .environment(AxEnvironment::Sandbox)
250 .build();
251 assert_eq!(config.http_base_url(), AX_HTTP_SANDBOX_URL);
252 assert_eq!(config.ws_public_url(), AX_WS_SANDBOX_PUBLIC_URL);
253 assert_eq!(config.ws_private_url(), AX_WS_SANDBOX_PRIVATE_URL);
254 }
255
256 #[rstest]
257 fn test_data_config_production_urls_match_consts() {
258 let config = AxDataClientConfig::builder()
259 .environment(AxEnvironment::Production)
260 .build();
261 assert_eq!(config.http_base_url(), AX_HTTP_URL);
262 assert_eq!(config.ws_public_url(), AX_WS_PUBLIC_URL);
263 assert_eq!(config.ws_private_url(), AX_WS_PRIVATE_URL);
264 }
265
266 #[rstest]
267 fn test_data_config_url_overrides() {
268 let config = AxDataClientConfig::builder()
269 .base_url_http("http://custom".to_string())
270 .base_url_ws_public("ws://custom-pub".to_string())
271 .base_url_ws_private("ws://custom-priv".to_string())
272 .build();
273 assert_eq!(config.http_base_url(), "http://custom");
274 assert_eq!(config.ws_public_url(), "ws://custom-pub");
275 assert_eq!(config.ws_private_url(), "ws://custom-priv");
276 }
277
278 #[rstest]
279 fn test_exec_config_sandbox_urls_match_consts() {
280 let config = AxExecClientConfig::builder()
281 .environment(AxEnvironment::Sandbox)
282 .build();
283 assert_eq!(config.http_base_url(), AX_HTTP_SANDBOX_URL);
284 assert_eq!(config.orders_base_url(), AX_ORDERS_SANDBOX_URL);
285 assert_eq!(config.ws_private_url(), AX_WS_SANDBOX_PRIVATE_URL);
286 }
287
288 #[rstest]
289 fn test_exec_config_production_urls_match_consts() {
290 let config = AxExecClientConfig::builder()
291 .environment(AxEnvironment::Production)
292 .build();
293 assert_eq!(config.http_base_url(), AX_HTTP_URL);
294 assert_eq!(config.orders_base_url(), AX_ORDERS_URL);
295 assert_eq!(config.ws_private_url(), AX_WS_PRIVATE_URL);
296 }
297
298 #[rstest]
299 fn test_exec_config_cancel_on_disconnect_default_false() {
300 let config = AxExecClientConfig::default();
301 assert!(!config.cancel_on_disconnect);
302 }
303
304 #[rstest]
305 fn test_exec_config_cancel_on_disconnect_enabled() {
306 let config = AxExecClientConfig::builder()
307 .cancel_on_disconnect(true)
308 .build();
309 assert!(config.cancel_on_disconnect);
310 }
311
312 #[rstest]
313 fn test_default_environment_is_sandbox() {
314 let data = AxDataClientConfig::default();
315 assert_eq!(data.environment, AxEnvironment::Sandbox);
316
317 let exec = AxExecClientConfig::default();
318 assert_eq!(exec.environment, AxEnvironment::Sandbox);
319 }
320}