nautilus_hyperliquid/
factories.rs1use 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::{ClientId, TraderId},
30};
31
32use crate::{
33 common::consts::{HYPERLIQUID, HYPERLIQUID_VENUE},
34 config::{HyperliquidDataClientConfig, HyperliquidExecutionClientConfig},
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 HyperliquidExecutionClientConfig {
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(Debug, Clone)]
111#[cfg_attr(
112 feature = "python",
113 pyo3::pyclass(module = "nautilus_trader.adapters.hyperliquid", from_py_object)
114)]
115#[cfg_attr(
116 feature = "python",
117 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
118)]
119pub struct HyperliquidExecutionClientFactory;
120
121impl HyperliquidExecutionClientFactory {
122 #[must_use]
124 pub const fn new() -> Self {
125 Self
126 }
127}
128
129impl Default for HyperliquidExecutionClientFactory {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl ExecutionClientFactory for HyperliquidExecutionClientFactory {
136 fn create(
137 &self,
138 trader_id: TraderId,
139 name: &str,
140 config: &dyn ClientConfig,
141 cache: CacheView,
142 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
143 let hyperliquid_config = config
144 .as_any()
145 .downcast_ref::<HyperliquidExecutionClientConfig>()
146 .ok_or_else(|| {
147 anyhow::anyhow!(
148 "Invalid config type for HyperliquidExecutionClientFactory. Expected HyperliquidExecutionClientConfig, was {config:?}",
149 )
150 })?
151 .clone();
152
153 let oms_type = OmsType::Netting;
155
156 let account_type = AccountType::Margin;
158
159 let core = ExecutionClientCore::new(
160 trader_id,
161 ClientId::from(name),
162 *HYPERLIQUID_VENUE,
163 oms_type,
164 hyperliquid_config.account_id,
165 account_type,
166 None,
167 cache,
168 );
169
170 let client = HyperliquidExecutionClient::new(core, hyperliquid_config)?;
171 Ok(Box::new(client))
172 }
173
174 fn name(&self) -> &'static str {
175 HYPERLIQUID
176 }
177
178 fn config_type(&self) -> &'static str {
179 "HyperliquidExecutionClientConfig"
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use std::{cell::RefCell, rc::Rc};
186
187 use nautilus_common::{
188 cache::Cache,
189 clock::TestClock,
190 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
191 };
192 use nautilus_model::identifiers::TraderId;
193 use rstest::rstest;
194
195 use super::*;
196 use crate::config::{HyperliquidDataClientConfig, HyperliquidExecutionClientConfig};
197
198 #[rstest]
199 fn test_hyperliquid_data_client_factory_creation() {
200 let factory = HyperliquidDataClientFactory::new();
201 assert_eq!(factory.name(), HYPERLIQUID);
202 assert_eq!(factory.config_type(), "HyperliquidDataClientConfig");
203 }
204
205 #[rstest]
206 fn test_hyperliquid_data_client_factory_default() {
207 let factory = HyperliquidDataClientFactory;
208 assert_eq!(factory.name(), HYPERLIQUID);
209 }
210
211 #[rstest]
212 fn test_hyperliquid_execution_client_factory_creation() {
213 let factory = HyperliquidExecutionClientFactory::new();
214 assert_eq!(factory.name(), HYPERLIQUID);
215 assert_eq!(factory.config_type(), "HyperliquidExecutionClientConfig");
216 }
217
218 #[rstest]
219 fn test_hyperliquid_execution_client_factory_default() {
220 let factory = HyperliquidExecutionClientFactory;
221 assert_eq!(factory.name(), HYPERLIQUID);
222 }
223
224 #[rstest]
225 fn test_hyperliquid_data_client_config_implements_client_config() {
226 let config = HyperliquidDataClientConfig::default();
227 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
228 let downcasted = boxed_config
229 .as_any()
230 .downcast_ref::<HyperliquidDataClientConfig>();
231
232 assert!(downcasted.is_some());
233 }
234
235 #[rstest]
236 fn test_hyperliquid_exec_client_config_implements_client_config() {
237 let config = HyperliquidExecutionClientConfig::builder()
238 .private_key("test_private_key".to_string())
239 .build();
240
241 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
242 let downcasted = boxed_config
243 .as_any()
244 .downcast_ref::<HyperliquidExecutionClientConfig>();
245
246 assert!(downcasted.is_some());
247 }
248
249 #[rstest]
250 fn test_hyperliquid_data_client_factory_rejects_wrong_config_type() {
251 let factory = HyperliquidDataClientFactory::new();
252 let wrong_config = HyperliquidExecutionClientConfig::builder()
253 .private_key("test_private_key".to_string())
254 .build();
255
256 let cache = Rc::new(RefCell::new(Cache::default()));
257 let clock = Rc::new(RefCell::new(TestClock::new()));
258
259 let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache.into(), clock);
260 assert!(result.is_err());
261 assert!(
262 result
263 .err()
264 .unwrap()
265 .to_string()
266 .contains("Invalid config type")
267 );
268 }
269
270 #[rstest]
271 fn test_hyperliquid_execution_client_factory_rejects_wrong_config_type() {
272 let factory = HyperliquidExecutionClientFactory::new();
273 let wrong_config = HyperliquidDataClientConfig::default();
274
275 let cache = Rc::new(RefCell::new(Cache::default()));
276
277 let result = factory.create(
278 TraderId::from("TRADER-001"),
279 "HYPERLIQUID-TEST",
280 &wrong_config,
281 cache.into(),
282 );
283 assert!(result.is_err());
284 assert!(
285 result
286 .err()
287 .unwrap()
288 .to_string()
289 .contains("Invalid config type")
290 );
291 }
292}