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