Skip to main content

nautilus_hyperliquid/
factories.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Factory functions for creating Hyperliquid clients and components.
17
18use 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/// Factory for creating Hyperliquid data clients.
52#[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    /// Creates a new [`HyperliquidDataClientFactory`] instance.
65    #[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/// Factory for creating Hyperliquid execution clients.
110#[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    /// Creates a new [`HyperliquidExecutionClientFactory`] instance.
123    #[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        _clock: Rc<RefCell<dyn Clock>>,
143    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
144        let hyperliquid_config = config
145            .as_any()
146            .downcast_ref::<HyperliquidExecutionClientConfig>()
147            .ok_or_else(|| {
148                anyhow::anyhow!(
149                    "Invalid config type for HyperliquidExecutionClientFactory. Expected HyperliquidExecutionClientConfig, was {config:?}",
150                )
151            })?
152            .clone();
153
154        // Hyperliquid uses netting for perpetual futures
155        let oms_type = OmsType::Netting;
156
157        // Hyperliquid is always margin (perpetual futures)
158        let account_type = AccountType::Margin;
159
160        let core = ExecutionClientCore::new(
161            trader_id,
162            ClientId::from(name),
163            *HYPERLIQUID_VENUE,
164            oms_type,
165            hyperliquid_config.account_id,
166            account_type,
167            None,
168            cache,
169        );
170
171        let client = HyperliquidExecutionClient::new(core, hyperliquid_config)?;
172        Ok(Box::new(client))
173    }
174
175    fn name(&self) -> &'static str {
176        HYPERLIQUID
177    }
178
179    fn config_type(&self) -> &'static str {
180        "HyperliquidExecutionClientConfig"
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use std::{cell::RefCell, rc::Rc};
187
188    use nautilus_common::{
189        cache::Cache,
190        clock::TestClock,
191        factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
192    };
193    use nautilus_model::identifiers::TraderId;
194    use rstest::rstest;
195
196    use super::*;
197    use crate::config::{HyperliquidDataClientConfig, HyperliquidExecutionClientConfig};
198
199    #[rstest]
200    fn test_hyperliquid_data_client_factory_creation() {
201        let factory = HyperliquidDataClientFactory::new();
202        assert_eq!(factory.name(), HYPERLIQUID);
203        assert_eq!(factory.config_type(), "HyperliquidDataClientConfig");
204    }
205
206    #[rstest]
207    fn test_hyperliquid_data_client_factory_default() {
208        let factory = HyperliquidDataClientFactory;
209        assert_eq!(factory.name(), HYPERLIQUID);
210    }
211
212    #[rstest]
213    fn test_hyperliquid_execution_client_factory_creation() {
214        let factory = HyperliquidExecutionClientFactory::new();
215        assert_eq!(factory.name(), HYPERLIQUID);
216        assert_eq!(factory.config_type(), "HyperliquidExecutionClientConfig");
217    }
218
219    #[rstest]
220    fn test_hyperliquid_execution_client_factory_default() {
221        let factory = HyperliquidExecutionClientFactory;
222        assert_eq!(factory.name(), HYPERLIQUID);
223    }
224
225    #[rstest]
226    fn test_hyperliquid_data_client_config_implements_client_config() {
227        let config = HyperliquidDataClientConfig::default();
228        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
229        let downcasted = boxed_config
230            .as_any()
231            .downcast_ref::<HyperliquidDataClientConfig>();
232
233        assert!(downcasted.is_some());
234    }
235
236    #[rstest]
237    fn test_hyperliquid_exec_client_config_implements_client_config() {
238        let config = HyperliquidExecutionClientConfig::builder()
239            .private_key("test_private_key".into())
240            .build();
241
242        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
243        let downcasted = boxed_config
244            .as_any()
245            .downcast_ref::<HyperliquidExecutionClientConfig>();
246
247        assert!(downcasted.is_some());
248    }
249
250    #[rstest]
251    fn test_hyperliquid_data_client_factory_rejects_wrong_config_type() {
252        let factory = HyperliquidDataClientFactory::new();
253        let wrong_config = HyperliquidExecutionClientConfig::builder()
254            .private_key("test_private_key".into())
255            .build();
256
257        let cache = Rc::new(RefCell::new(Cache::default()));
258        let clock = Rc::new(RefCell::new(TestClock::new()));
259
260        let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache.into(), clock);
261        assert!(result.is_err());
262        assert!(
263            result
264                .err()
265                .unwrap()
266                .to_string()
267                .contains("Invalid config type")
268        );
269    }
270
271    #[rstest]
272    fn test_hyperliquid_execution_client_factory_rejects_wrong_config_type() {
273        let factory = HyperliquidExecutionClientFactory::new();
274        let wrong_config = HyperliquidDataClientConfig::default();
275
276        let cache = Rc::new(RefCell::new(Cache::default()));
277
278        let result = factory.create(
279            TraderId::from("TRADER-001"),
280            "HYPERLIQUID-TEST",
281            &wrong_config,
282            cache.into(),
283            Rc::new(RefCell::new(TestClock::new())),
284        );
285        assert!(result.is_err());
286        assert!(
287            result
288                .err()
289                .unwrap()
290                .to_string()
291                .contains("Invalid config type")
292        );
293    }
294}