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)]
63 pub transport_backend: TransportBackend,
64}
65
66impl Default for HyperliquidDataClientConfig {
67 fn default() -> Self {
68 Self::builder().build()
69 }
70}
71
72impl HyperliquidDataClientConfig {
73 #[must_use]
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 #[must_use]
81 pub fn has_credentials(&self) -> bool {
82 self.private_key
83 .as_deref()
84 .is_some_and(|s| !s.trim().is_empty())
85 }
86
87 #[must_use]
89 pub fn ws_url(&self) -> String {
90 self.base_url_ws
91 .clone()
92 .unwrap_or_else(|| ws_url(self.environment).to_string())
93 }
94
95 #[must_use]
97 pub fn http_url(&self) -> String {
98 self.base_url_http
99 .clone()
100 .unwrap_or_else(|| info_url(self.environment).to_string())
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
106#[serde(default, deny_unknown_fields)]
107#[cfg_attr(
108 feature = "python",
109 pyo3::pyclass(
110 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
111 from_py_object
112 )
113)]
114#[cfg_attr(
115 feature = "python",
116 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
117)]
118pub struct HyperliquidExecClientConfig {
119 pub private_key: Option<String>,
125 pub vault_address: Option<String>,
131 pub account_address: Option<String>,
138 pub base_url_ws: Option<String>,
140 pub base_url_http: Option<String>,
142 pub base_url_exchange: Option<String>,
144 pub proxy_url: Option<String>,
146 #[builder(default)]
148 pub environment: HyperliquidEnvironment,
149 #[builder(default = 60)]
151 pub http_timeout_secs: u64,
152 #[builder(default = 3)]
154 pub max_retries: u32,
155 #[builder(default = 100)]
157 pub retry_delay_initial_ms: u64,
158 #[builder(default = 5000)]
160 pub retry_delay_max_ms: u64,
161 #[builder(default = true)]
164 pub normalize_prices: bool,
165 #[builder(default = 50)]
169 pub market_order_slippage_bps: u32,
170 #[builder(default)]
172 pub transport_backend: TransportBackend,
173 #[builder(default = 10)]
175 pub ws_post_timeout_secs: u64,
176 #[builder(default = 0)]
181 pub outcome_settlement_poll_secs: u64,
182}
183
184impl Default for HyperliquidExecClientConfig {
185 fn default() -> Self {
186 Self::builder().build()
187 }
188}
189
190impl HyperliquidExecClientConfig {
191 #[must_use]
193 pub fn has_credentials(&self) -> bool {
194 self.private_key
195 .as_deref()
196 .is_some_and(|s| !s.trim().is_empty())
197 }
198
199 #[must_use]
201 pub fn ws_url(&self) -> String {
202 self.base_url_ws
203 .clone()
204 .unwrap_or_else(|| ws_url(self.environment).to_string())
205 }
206
207 #[must_use]
209 pub fn http_url(&self) -> String {
210 self.base_url_http
211 .clone()
212 .unwrap_or_else(|| info_url(self.environment).to_string())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use rstest::rstest;
219
220 use super::*;
221
222 #[rstest]
223 fn test_exec_config_default_account_address_is_none() {
224 let config = HyperliquidExecClientConfig::default();
225 assert!(config.account_address.is_none());
226 }
227
228 #[rstest]
229 fn test_exec_config_with_account_address() {
230 let config = HyperliquidExecClientConfig {
231 account_address: Some("0x1234".to_string()),
232 ..HyperliquidExecClientConfig::default()
233 };
234 assert_eq!(config.account_address.as_deref(), Some("0x1234"));
235 }
236
237 #[rstest]
238 fn test_data_config_toml_minimal() {
239 let config: HyperliquidDataClientConfig = toml::from_str(
240 r#"
241environment = "testnet"
242http_timeout_secs = 30
243update_instruments_interval_mins = 10
244transport_backend = "tungstenite"
245"#,
246 )
247 .unwrap();
248
249 assert_eq!(config.environment, HyperliquidEnvironment::Testnet);
250 assert_eq!(config.http_timeout_secs, 30);
251 assert_eq!(config.update_instruments_interval_mins, 10);
252 assert_eq!(config.transport_backend, TransportBackend::Tungstenite);
253 }
254
255 #[rstest]
256 fn test_exec_config_toml_empty_uses_defaults() {
257 let config: HyperliquidExecClientConfig = toml::from_str("").unwrap();
258 let expected = HyperliquidExecClientConfig::default();
259
260 assert_eq!(config.environment, expected.environment);
261 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
262 assert_eq!(config.max_retries, expected.max_retries);
263 assert_eq!(config.normalize_prices, expected.normalize_prices);
264 assert_eq!(
265 config.market_order_slippage_bps,
266 expected.market_order_slippage_bps,
267 );
268 assert_eq!(config.transport_backend, expected.transport_backend);
269 assert_eq!(config.ws_post_timeout_secs, expected.ws_post_timeout_secs);
270 assert_eq!(
271 config.outcome_settlement_poll_secs,
272 expected.outcome_settlement_poll_secs,
273 );
274 }
275}