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.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.hyperliquid")
117)]
118pub struct HyperliquidExecClientConfig {
119 pub private_key: Option<String>,
125 pub vault_address: Option<String>,
127 pub account_address: Option<String>,
131 pub base_url_ws: Option<String>,
133 pub base_url_http: Option<String>,
135 pub base_url_exchange: Option<String>,
137 pub proxy_url: Option<String>,
139 #[builder(default)]
141 pub environment: HyperliquidEnvironment,
142 #[builder(default = 60)]
144 pub http_timeout_secs: u64,
145 #[builder(default = 3)]
147 pub max_retries: u32,
148 #[builder(default = 100)]
150 pub retry_delay_initial_ms: u64,
151 #[builder(default = 5000)]
153 pub retry_delay_max_ms: u64,
154 #[builder(default = true)]
157 pub normalize_prices: bool,
158 #[builder(default = 50)]
162 pub market_order_slippage_bps: u32,
163 #[builder(default)]
165 pub transport_backend: TransportBackend,
166 #[builder(default = 0)]
171 pub outcome_settlement_poll_secs: u64,
172}
173
174impl Default for HyperliquidExecClientConfig {
175 fn default() -> Self {
176 Self::builder().build()
177 }
178}
179
180impl HyperliquidExecClientConfig {
181 #[must_use]
183 pub fn has_credentials(&self) -> bool {
184 self.private_key
185 .as_deref()
186 .is_some_and(|s| !s.trim().is_empty())
187 }
188
189 #[must_use]
191 pub fn ws_url(&self) -> String {
192 self.base_url_ws
193 .clone()
194 .unwrap_or_else(|| ws_url(self.environment).to_string())
195 }
196
197 #[must_use]
199 pub fn http_url(&self) -> String {
200 self.base_url_http
201 .clone()
202 .unwrap_or_else(|| info_url(self.environment).to_string())
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use rstest::rstest;
209
210 use super::*;
211
212 #[rstest]
213 fn test_exec_config_default_account_address_is_none() {
214 let config = HyperliquidExecClientConfig::default();
215 assert!(config.account_address.is_none());
216 }
217
218 #[rstest]
219 fn test_exec_config_with_account_address() {
220 let config = HyperliquidExecClientConfig {
221 account_address: Some("0x1234".to_string()),
222 ..HyperliquidExecClientConfig::default()
223 };
224 assert_eq!(config.account_address.as_deref(), Some("0x1234"));
225 }
226
227 #[rstest]
228 fn test_data_config_toml_minimal() {
229 let config: HyperliquidDataClientConfig = toml::from_str(
230 r#"
231environment = "testnet"
232http_timeout_secs = 30
233update_instruments_interval_mins = 10
234transport_backend = "tungstenite"
235"#,
236 )
237 .unwrap();
238
239 assert_eq!(config.environment, HyperliquidEnvironment::Testnet);
240 assert_eq!(config.http_timeout_secs, 30);
241 assert_eq!(config.update_instruments_interval_mins, 10);
242 assert_eq!(config.transport_backend, TransportBackend::Tungstenite);
243 }
244
245 #[rstest]
246 fn test_exec_config_toml_empty_uses_defaults() {
247 let config: HyperliquidExecClientConfig = toml::from_str("").unwrap();
248 let expected = HyperliquidExecClientConfig::default();
249
250 assert_eq!(config.environment, expected.environment);
251 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
252 assert_eq!(config.max_retries, expected.max_retries);
253 assert_eq!(config.normalize_prices, expected.normalize_prices);
254 assert_eq!(
255 config.market_order_slippage_bps,
256 expected.market_order_slippage_bps,
257 );
258 assert_eq!(config.transport_backend, expected.transport_backend);
259 assert_eq!(
260 config.outcome_settlement_poll_secs,
261 expected.outcome_settlement_poll_secs,
262 );
263 }
264}