nautilus_hyperliquid/
config.rs1use nautilus_network::websocket::TransportBackend;
19use serde::{Deserialize, Serialize};
20
21use crate::common::{
22 consts::{info_url, ws_url},
23 enums::HyperliquidEnvironment,
24};
25
26#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
28#[serde(default, deny_unknown_fields)]
29#[cfg_attr(
30 feature = "python",
31 pyo3::pyclass(
32 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
33 from_py_object
34 )
35)]
36#[cfg_attr(
37 feature = "python",
38 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
39)]
40pub struct HyperliquidDataClientConfig {
41 pub private_key: Option<String>,
43 pub base_url_ws: Option<String>,
45 pub base_url_http: Option<String>,
47 pub proxy_url: Option<String>,
49 #[builder(default)]
51 pub environment: HyperliquidEnvironment,
52 #[builder(default = 60)]
54 pub http_timeout_secs: u64,
55 #[builder(default = 30)]
57 pub ws_timeout_secs: u64,
58 #[builder(default = 60)]
60 pub update_instruments_interval_mins: u64,
61 #[builder(default)]
64 pub transport_backend: TransportBackend,
65}
66
67impl Default for HyperliquidDataClientConfig {
68 fn default() -> Self {
69 Self::builder().build()
70 }
71}
72
73impl HyperliquidDataClientConfig {
74 #[must_use]
76 pub fn new() -> Self {
77 Self::default()
78 }
79
80 #[must_use]
82 pub fn has_credentials(&self) -> bool {
83 self.private_key
84 .as_deref()
85 .is_some_and(|s| !s.trim().is_empty())
86 }
87
88 #[must_use]
90 pub fn ws_url(&self) -> String {
91 self.base_url_ws
92 .clone()
93 .unwrap_or_else(|| ws_url(self.environment).to_string())
94 }
95
96 #[must_use]
98 pub fn http_url(&self) -> String {
99 self.base_url_http
100 .clone()
101 .unwrap_or_else(|| info_url(self.environment).to_string())
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
107#[serde(default, deny_unknown_fields)]
108#[cfg_attr(
109 feature = "python",
110 pyo3::pyclass(
111 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
112 from_py_object
113 )
114)]
115#[cfg_attr(
116 feature = "python",
117 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
118)]
119pub struct HyperliquidExecClientConfig {
120 pub private_key: Option<String>,
126 pub vault_address: Option<String>,
132 pub account_address: Option<String>,
139 pub base_url_ws: Option<String>,
141 pub base_url_http: Option<String>,
143 pub base_url_exchange: Option<String>,
145 pub proxy_url: Option<String>,
147 #[builder(default)]
149 pub environment: HyperliquidEnvironment,
150 #[builder(default = 60)]
152 pub http_timeout_secs: u64,
153 #[builder(default = 3)]
155 pub max_retries: u32,
156 #[builder(default = 100)]
158 pub retry_delay_initial_ms: u64,
159 #[builder(default = 5000)]
161 pub retry_delay_max_ms: u64,
162 #[builder(default = true)]
165 pub normalize_prices: bool,
166 #[builder(default = 50)]
170 pub market_order_slippage_bps: u32,
171 #[builder(default = true)]
173 pub include_builder_attribution: bool,
174 #[builder(default)]
177 pub transport_backend: TransportBackend,
178 #[builder(default = 10)]
180 pub ws_post_timeout_secs: u64,
181 #[builder(default = 0)]
186 pub outcome_settlement_poll_secs: u64,
187}
188
189impl Default for HyperliquidExecClientConfig {
190 fn default() -> Self {
191 Self::builder().build()
192 }
193}
194
195impl HyperliquidExecClientConfig {
196 #[must_use]
198 pub fn has_credentials(&self) -> bool {
199 self.private_key
200 .as_deref()
201 .is_some_and(|s| !s.trim().is_empty())
202 }
203
204 #[must_use]
206 pub fn ws_url(&self) -> String {
207 self.base_url_ws
208 .clone()
209 .unwrap_or_else(|| ws_url(self.environment).to_string())
210 }
211
212 #[must_use]
214 pub fn http_url(&self) -> String {
215 self.base_url_http
216 .clone()
217 .unwrap_or_else(|| info_url(self.environment).to_string())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use rstest::rstest;
224
225 use super::*;
226
227 #[rstest]
228 fn test_exec_config_default_account_address_is_none() {
229 let config = HyperliquidExecClientConfig::default();
230 assert!(config.account_address.is_none());
231 }
232
233 #[rstest]
234 fn test_exec_config_with_account_address() {
235 let config = HyperliquidExecClientConfig {
236 account_address: Some("0x1234".to_string()),
237 ..HyperliquidExecClientConfig::default()
238 };
239 assert_eq!(config.account_address.as_deref(), Some("0x1234"));
240 }
241
242 #[rstest]
243 fn test_data_config_toml_minimal() {
244 let config: HyperliquidDataClientConfig = toml::from_str(
245 r#"
246environment = "testnet"
247http_timeout_secs = 30
248update_instruments_interval_mins = 10
249transport_backend = "tungstenite"
250"#,
251 )
252 .unwrap();
253
254 assert_eq!(config.environment, HyperliquidEnvironment::Testnet);
255 assert_eq!(config.http_timeout_secs, 30);
256 assert_eq!(config.update_instruments_interval_mins, 10);
257 assert_eq!(config.transport_backend, TransportBackend::Tungstenite);
258 }
259
260 #[rstest]
261 fn test_exec_config_toml_empty_uses_defaults() {
262 let config: HyperliquidExecClientConfig = toml::from_str("").unwrap();
263 let expected = HyperliquidExecClientConfig::default();
264
265 assert_eq!(config.environment, expected.environment);
266 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
267 assert_eq!(config.max_retries, expected.max_retries);
268 assert_eq!(config.normalize_prices, expected.normalize_prices);
269 assert_eq!(
270 config.market_order_slippage_bps,
271 expected.market_order_slippage_bps,
272 );
273 assert_eq!(
274 config.include_builder_attribution,
275 expected.include_builder_attribution,
276 );
277 assert_eq!(config.transport_backend, expected.transport_backend);
278 assert_eq!(config.ws_post_timeout_secs, expected.ws_post_timeout_secs);
279 assert_eq!(
280 config.outcome_settlement_poll_secs,
281 expected.outcome_settlement_poll_secs,
282 );
283 }
284
285 #[rstest]
286 fn test_exec_config_toml_include_builder_attribution_false() {
287 let config: HyperliquidExecClientConfig =
288 toml::from_str("include_builder_attribution = false").unwrap();
289
290 assert!(!config.include_builder_attribution);
291 }
292}