Skip to main content

nautilus_architect_ax/
config.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//! Configuration structures for the AX Exchange adapter.
17
18use nautilus_model::identifiers::{AccountId, TraderId};
19use nautilus_network::websocket::TransportBackend;
20
21use crate::common::{credential::credential_env_vars, enums::AxEnvironment};
22
23/// Configuration for the AX Exchange live data client.
24#[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    /// Optional API key for authenticated REST/WebSocket requests.
38    pub api_key: Option<String>,
39    /// Optional API secret for authenticated REST/WebSocket requests.
40    pub api_secret: Option<String>,
41    /// Trading environment (Sandbox or Production).
42    #[builder(default)]
43    pub environment: AxEnvironment,
44    /// Optional override for the REST base URL.
45    pub base_url_http: Option<String>,
46    /// Optional override for the public WebSocket URL.
47    pub base_url_ws_public: Option<String>,
48    /// Optional override for the private WebSocket URL.
49    pub base_url_ws_private: Option<String>,
50    /// Optional proxy URL for HTTP and WebSocket transports.
51    pub proxy_url: Option<String>,
52    /// REST timeout in seconds.
53    #[builder(default = 60)]
54    pub http_timeout_secs: u64,
55    /// Maximum retry attempts for REST requests.
56    #[builder(default = 3)]
57    pub max_retries: u32,
58    /// Initial retry backoff in milliseconds.
59    #[builder(default = 1_000)]
60    pub retry_delay_initial_ms: u64,
61    /// Maximum retry backoff in milliseconds.
62    #[builder(default = 10_000)]
63    pub retry_delay_max_ms: u64,
64    /// Heartbeat interval (seconds) for WebSocket clients.
65    #[builder(default = 20)]
66    pub heartbeat_interval_secs: u64,
67    /// Receive window in milliseconds for signed requests.
68    #[builder(default = 5_000)]
69    pub recv_window_ms: u64,
70    /// Interval (minutes) for instrument refresh from REST.
71    #[builder(default = 60)]
72    pub update_instruments_interval_mins: u64,
73    /// Funding rate poll interval in minutes.
74    #[builder(default = 15)]
75    pub funding_rate_poll_interval_mins: u64,
76    /// WebSocket transport backend (defaults to `Tungstenite`).
77    #[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    /// Creates a configuration with default values.
89    #[must_use]
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Returns `true` if both API key and secret are available.
95    #[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    /// Returns the REST base URL, considering overrides and environment.
104    #[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    /// Returns the public WebSocket URL, considering overrides and environment.
112    #[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    /// Returns the private WebSocket URL, considering overrides and environment.
120    #[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/// Configuration for the AX Exchange live execution client.
129#[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    /// The trader ID for the client.
143    #[builder(default = TraderId::from("TRADER-001"))]
144    pub trader_id: TraderId,
145    /// The account ID for the client.
146    #[builder(default = AccountId::from("AX-001"))]
147    pub account_id: AccountId,
148    /// API key for authenticated requests.
149    pub api_key: Option<String>,
150    /// API secret for authenticated requests.
151    pub api_secret: Option<String>,
152    /// Trading environment (Sandbox or Production).
153    #[builder(default)]
154    pub environment: AxEnvironment,
155    /// Optional override for the REST base URL.
156    pub base_url_http: Option<String>,
157    /// Optional override for the orders REST base URL.
158    pub base_url_orders: Option<String>,
159    /// Optional override for the private WebSocket URL.
160    pub base_url_ws_private: Option<String>,
161    /// Optional proxy URL for HTTP and WebSocket transports.
162    pub proxy_url: Option<String>,
163    /// REST timeout in seconds.
164    #[builder(default = 60)]
165    pub http_timeout_secs: u64,
166    /// Maximum retry attempts for REST requests.
167    #[builder(default = 3)]
168    pub max_retries: u32,
169    /// Initial retry backoff in milliseconds.
170    #[builder(default = 1_000)]
171    pub retry_delay_initial_ms: u64,
172    /// Maximum retry backoff in milliseconds.
173    #[builder(default = 10_000)]
174    pub retry_delay_max_ms: u64,
175    /// Heartbeat interval (seconds) for WebSocket clients.
176    #[builder(default = 30)]
177    pub heartbeat_interval_secs: u64,
178    /// Receive window in milliseconds for signed requests.
179    #[builder(default = 5_000)]
180    pub recv_window_ms: u64,
181    /// Cancel all open orders when the orders WebSocket disconnects.
182    #[builder(default)]
183    pub cancel_on_disconnect: bool,
184    /// WebSocket transport backend (defaults to `Tungstenite`).
185    #[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    /// Creates a configuration with default values.
197    #[must_use]
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    /// Returns `true` if both API key and secret are available.
203    #[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    /// Returns the REST base URL, considering overrides and environment.
212    #[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    /// Returns the orders REST base URL, considering overrides and environment.
220    #[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    /// Returns the private WebSocket URL, considering overrides and environment.
228    #[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}