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    ) -> 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        // Hyperliquid uses netting for perpetual futures
154        let oms_type = OmsType::Netting;
155
156        // Hyperliquid is always margin (perpetual futures)
157        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}