nautilus_hyperliquid/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::Cache,
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_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.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: Rc<RefCell<Cache>>,
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.hyperliquid")
127)]
128pub struct HyperliquidExecFactoryConfig {
129 pub trader_id: TraderId,
131 pub account_id: AccountId,
133 pub config: HyperliquidExecClientConfig,
135}
136
137impl ClientConfig for HyperliquidExecFactoryConfig {
138 fn as_any(&self) -> &dyn Any {
139 self
140 }
141}
142
143#[derive(Debug, Clone)]
145#[cfg_attr(
146 feature = "python",
147 pyo3::pyclass(
148 module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
149 from_py_object
150 )
151)]
152#[cfg_attr(
153 feature = "python",
154 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.hyperliquid")
155)]
156pub struct HyperliquidExecutionClientFactory;
157
158impl HyperliquidExecutionClientFactory {
159 #[must_use]
161 pub const fn new() -> Self {
162 Self
163 }
164}
165
166impl Default for HyperliquidExecutionClientFactory {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172impl ExecutionClientFactory for HyperliquidExecutionClientFactory {
173 fn create(
174 &self,
175 name: &str,
176 config: &dyn ClientConfig,
177 cache: Rc<RefCell<Cache>>,
178 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
179 let factory_config = config
180 .as_any()
181 .downcast_ref::<HyperliquidExecFactoryConfig>()
182 .ok_or_else(|| {
183 anyhow::anyhow!(
184 "Invalid config type for HyperliquidExecutionClientFactory. Expected HyperliquidExecFactoryConfig, was {config:?}",
185 )
186 })?
187 .clone();
188
189 let oms_type = OmsType::Netting;
191
192 let account_type = AccountType::Margin;
194
195 let core = ExecutionClientCore::new(
196 factory_config.trader_id,
197 ClientId::from(name),
198 *HYPERLIQUID_VENUE,
199 oms_type,
200 factory_config.account_id,
201 account_type,
202 None,
203 cache,
204 );
205
206 let client = HyperliquidExecutionClient::new(core, factory_config.config)?;
207 Ok(Box::new(client))
208 }
209
210 fn name(&self) -> &'static str {
211 "HYPERLIQUID"
212 }
213
214 fn config_type(&self) -> &'static str {
215 "HyperliquidExecFactoryConfig"
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use std::{cell::RefCell, rc::Rc};
222
223 use nautilus_common::{
224 cache::Cache,
225 clock::TestClock,
226 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
227 };
228 use nautilus_model::identifiers::{AccountId, TraderId};
229 use rstest::rstest;
230
231 use super::*;
232 use crate::config::{HyperliquidDataClientConfig, HyperliquidExecClientConfig};
233
234 #[rstest]
235 fn test_hyperliquid_data_client_factory_creation() {
236 let factory = HyperliquidDataClientFactory::new();
237 assert_eq!(factory.name(), "HYPERLIQUID");
238 assert_eq!(factory.config_type(), "HyperliquidDataClientConfig");
239 }
240
241 #[rstest]
242 fn test_hyperliquid_data_client_factory_default() {
243 let factory = HyperliquidDataClientFactory;
244 assert_eq!(factory.name(), "HYPERLIQUID");
245 }
246
247 #[rstest]
248 fn test_hyperliquid_execution_client_factory_creation() {
249 let factory = HyperliquidExecutionClientFactory::new();
250 assert_eq!(factory.name(), "HYPERLIQUID");
251 assert_eq!(factory.config_type(), "HyperliquidExecFactoryConfig");
252 }
253
254 #[rstest]
255 fn test_hyperliquid_execution_client_factory_default() {
256 let factory = HyperliquidExecutionClientFactory;
257 assert_eq!(factory.name(), "HYPERLIQUID");
258 }
259
260 #[rstest]
261 fn test_hyperliquid_data_client_config_implements_client_config() {
262 let config = HyperliquidDataClientConfig::default();
263 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
264 let downcasted = boxed_config
265 .as_any()
266 .downcast_ref::<HyperliquidDataClientConfig>();
267
268 assert!(downcasted.is_some());
269 }
270
271 #[rstest]
272 fn test_hyperliquid_exec_factory_config_implements_client_config() {
273 let config = HyperliquidExecFactoryConfig {
274 trader_id: TraderId::from("TRADER-001"),
275 account_id: AccountId::from("HYPERLIQUID-001"),
276 config: HyperliquidExecClientConfig::builder()
277 .private_key("test_private_key".to_string())
278 .build(),
279 };
280
281 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
282 let downcasted = boxed_config
283 .as_any()
284 .downcast_ref::<HyperliquidExecFactoryConfig>();
285
286 assert!(downcasted.is_some());
287 }
288
289 #[rstest]
290 fn test_hyperliquid_data_client_factory_rejects_wrong_config_type() {
291 let factory = HyperliquidDataClientFactory::new();
292 let wrong_config = HyperliquidExecFactoryConfig {
293 trader_id: TraderId::from("TRADER-001"),
294 account_id: AccountId::from("HYPERLIQUID-001"),
295 config: HyperliquidExecClientConfig::builder()
296 .private_key("test_private_key".to_string())
297 .build(),
298 };
299
300 let cache = Rc::new(RefCell::new(Cache::default()));
301 let clock = Rc::new(RefCell::new(TestClock::new()));
302
303 let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache, clock);
304 assert!(result.is_err());
305 assert!(
306 result
307 .err()
308 .unwrap()
309 .to_string()
310 .contains("Invalid config type")
311 );
312 }
313
314 #[rstest]
315 fn test_hyperliquid_execution_client_factory_rejects_wrong_config_type() {
316 let factory = HyperliquidExecutionClientFactory::new();
317 let wrong_config = HyperliquidDataClientConfig::default();
318
319 let cache = Rc::new(RefCell::new(Cache::default()));
320
321 let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache);
322 assert!(result.is_err());
323 assert!(
324 result
325 .err()
326 .unwrap()
327 .to_string()
328 .contains("Invalid config type")
329 );
330 }
331}