1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::{AccountId, ClientId, TraderId},
30};
31
32use crate::{
33 common::consts::{HYPERLIQUID, HYPERLIQUID_VENUE},
34 config::{HyperliquidDataClientConfig, HyperliquidExecClientConfig},
35 data::HyperliquidDataClient,
36 execution::HyperliquidExecutionClient,
37};
38
39impl ClientConfig for HyperliquidDataClientConfig {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45impl ClientConfig for HyperliquidExecClientConfig {
46 fn as_any(&self) -> &dyn Any {
47 self
48 }
49}
50
51#[derive(Debug, Clone)]
53#[cfg_attr(
54 feature = "python",
55 pyo3::pyclass(
56 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
57 from_py_object
58 )
59)]
60#[cfg_attr(
61 feature = "python",
62 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
63)]
64pub struct HyperliquidDataClientFactory;
65
66impl HyperliquidDataClientFactory {
67 #[must_use]
69 pub const fn new() -> Self {
70 Self
71 }
72}
73
74impl Default for HyperliquidDataClientFactory {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl DataClientFactory for HyperliquidDataClientFactory {
81 fn create(
82 &self,
83 name: &str,
84 config: &dyn ClientConfig,
85 _cache: CacheView,
86 _clock: Rc<RefCell<dyn Clock>>,
87 ) -> anyhow::Result<Box<dyn DataClient>> {
88 let hyperliquid_config = config
89 .as_any()
90 .downcast_ref::<HyperliquidDataClientConfig>()
91 .ok_or_else(|| {
92 anyhow::anyhow!(
93 "Invalid config type for HyperliquidDataClientFactory. Expected HyperliquidDataClientConfig, was {config:?}",
94 )
95 })?
96 .clone();
97
98 let client_id = ClientId::from(name);
99 let client = HyperliquidDataClient::new(client_id, hyperliquid_config)?;
100 Ok(Box::new(client))
101 }
102
103 fn name(&self) -> &'static str {
104 HYPERLIQUID
105 }
106
107 fn config_type(&self) -> &'static str {
108 "HyperliquidDataClientConfig"
109 }
110}
111
112#[derive(Clone, Debug)]
117#[cfg_attr(
118 feature = "python",
119 pyo3::pyclass(
120 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
121 from_py_object
122 )
123)]
124#[cfg_attr(
125 feature = "python",
126 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
127)]
128pub struct HyperliquidExecFactoryConfig {
129 pub trader_id: TraderId,
131 pub account_id: AccountId,
133 pub config: HyperliquidExecClientConfig,
135}
136
137#[cfg(feature = "python")]
138nautilus_core::impl_pyo3_config_getters!(HyperliquidExecFactoryConfig {
139 trader_id: TraderId,
140 account_id: AccountId,
141 config: HyperliquidExecClientConfig,
142});
143
144impl ClientConfig for HyperliquidExecFactoryConfig {
145 fn as_any(&self) -> &dyn Any {
146 self
147 }
148}
149
150#[derive(Debug, Clone)]
152#[cfg_attr(
153 feature = "python",
154 pyo3::pyclass(
155 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
156 from_py_object
157 )
158)]
159#[cfg_attr(
160 feature = "python",
161 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
162)]
163pub struct HyperliquidExecutionClientFactory;
164
165impl HyperliquidExecutionClientFactory {
166 #[must_use]
168 pub const fn new() -> Self {
169 Self
170 }
171}
172
173impl Default for HyperliquidExecutionClientFactory {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl ExecutionClientFactory for HyperliquidExecutionClientFactory {
180 fn create(
181 &self,
182 name: &str,
183 config: &dyn ClientConfig,
184 cache: CacheView,
185 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
186 let factory_config = config
187 .as_any()
188 .downcast_ref::<HyperliquidExecFactoryConfig>()
189 .ok_or_else(|| {
190 anyhow::anyhow!(
191 "Invalid config type for HyperliquidExecutionClientFactory. Expected HyperliquidExecFactoryConfig, was {config:?}",
192 )
193 })?
194 .clone();
195
196 let oms_type = OmsType::Netting;
198
199 let account_type = AccountType::Margin;
201
202 let core = ExecutionClientCore::new(
203 factory_config.trader_id,
204 ClientId::from(name),
205 *HYPERLIQUID_VENUE,
206 oms_type,
207 factory_config.account_id,
208 account_type,
209 None,
210 cache,
211 );
212
213 let client = HyperliquidExecutionClient::new(core, factory_config.config)?;
214 Ok(Box::new(client))
215 }
216
217 fn name(&self) -> &'static str {
218 HYPERLIQUID
219 }
220
221 fn config_type(&self) -> &'static str {
222 "HyperliquidExecFactoryConfig"
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use std::{cell::RefCell, rc::Rc};
229
230 use nautilus_common::{
231 cache::Cache,
232 clock::TestClock,
233 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
234 };
235 use nautilus_model::identifiers::{AccountId, TraderId};
236 use rstest::rstest;
237
238 use super::*;
239 use crate::config::{HyperliquidDataClientConfig, HyperliquidExecClientConfig};
240
241 #[rstest]
242 fn test_hyperliquid_data_client_factory_creation() {
243 let factory = HyperliquidDataClientFactory::new();
244 assert_eq!(factory.name(), HYPERLIQUID);
245 assert_eq!(factory.config_type(), "HyperliquidDataClientConfig");
246 }
247
248 #[rstest]
249 fn test_hyperliquid_data_client_factory_default() {
250 let factory = HyperliquidDataClientFactory;
251 assert_eq!(factory.name(), HYPERLIQUID);
252 }
253
254 #[rstest]
255 fn test_hyperliquid_execution_client_factory_creation() {
256 let factory = HyperliquidExecutionClientFactory::new();
257 assert_eq!(factory.name(), HYPERLIQUID);
258 assert_eq!(factory.config_type(), "HyperliquidExecFactoryConfig");
259 }
260
261 #[rstest]
262 fn test_hyperliquid_execution_client_factory_default() {
263 let factory = HyperliquidExecutionClientFactory;
264 assert_eq!(factory.name(), HYPERLIQUID);
265 }
266
267 #[rstest]
268 fn test_hyperliquid_data_client_config_implements_client_config() {
269 let config = HyperliquidDataClientConfig::default();
270 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
271 let downcasted = boxed_config
272 .as_any()
273 .downcast_ref::<HyperliquidDataClientConfig>();
274
275 assert!(downcasted.is_some());
276 }
277
278 #[rstest]
279 fn test_hyperliquid_exec_factory_config_implements_client_config() {
280 let config = HyperliquidExecFactoryConfig {
281 trader_id: TraderId::from("TRADER-001"),
282 account_id: AccountId::from("HYPERLIQUID-001"),
283 config: HyperliquidExecClientConfig::builder()
284 .private_key("test_private_key".to_string())
285 .build(),
286 };
287
288 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
289 let downcasted = boxed_config
290 .as_any()
291 .downcast_ref::<HyperliquidExecFactoryConfig>();
292
293 assert!(downcasted.is_some());
294 }
295
296 #[rstest]
297 fn test_hyperliquid_data_client_factory_rejects_wrong_config_type() {
298 let factory = HyperliquidDataClientFactory::new();
299 let wrong_config = HyperliquidExecFactoryConfig {
300 trader_id: TraderId::from("TRADER-001"),
301 account_id: AccountId::from("HYPERLIQUID-001"),
302 config: HyperliquidExecClientConfig::builder()
303 .private_key("test_private_key".to_string())
304 .build(),
305 };
306
307 let cache = Rc::new(RefCell::new(Cache::default()));
308 let clock = Rc::new(RefCell::new(TestClock::new()));
309
310 let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache.into(), clock);
311 assert!(result.is_err());
312 assert!(
313 result
314 .err()
315 .unwrap()
316 .to_string()
317 .contains("Invalid config type")
318 );
319 }
320
321 #[rstest]
322 fn test_hyperliquid_execution_client_factory_rejects_wrong_config_type() {
323 let factory = HyperliquidExecutionClientFactory::new();
324 let wrong_config = HyperliquidDataClientConfig::default();
325
326 let cache = Rc::new(RefCell::new(Cache::default()));
327
328 let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache.into());
329 assert!(result.is_err());
330 assert!(
331 result
332 .err()
333 .unwrap()
334 .to_string()
335 .contains("Invalid config type")
336 );
337 }
338}