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::Cache,
22    clients::{DataClient, ExecutionClient},
23    clock::Clock,
24};
25use nautilus_live::ExecutionClientCore;
26use nautilus_model::{
27    enums::{AccountType, OmsType},
28    identifiers::{AccountId, ClientId, TraderId},
29};
30use nautilus_system::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
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/// Factory for creating Hyperliquid data clients.
52#[derive(Debug)]
53pub struct HyperliquidDataClientFactory;
54
55impl HyperliquidDataClientFactory {
56    /// Creates a new [`HyperliquidDataClientFactory`] instance.
57    #[must_use]
58    pub const fn new() -> Self {
59        Self
60    }
61}
62
63impl Default for HyperliquidDataClientFactory {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl DataClientFactory for HyperliquidDataClientFactory {
70    fn create(
71        &self,
72        name: &str,
73        config: &dyn ClientConfig,
74        _cache: Rc<RefCell<Cache>>,
75        _clock: Rc<RefCell<dyn Clock>>,
76    ) -> anyhow::Result<Box<dyn DataClient>> {
77        let hyperliquid_config = config
78            .as_any()
79            .downcast_ref::<HyperliquidDataClientConfig>()
80            .ok_or_else(|| {
81                anyhow::anyhow!(
82                    "Invalid config type for HyperliquidDataClientFactory. Expected HyperliquidDataClientConfig, was {config:?}",
83                )
84            })?
85            .clone();
86
87        let client_id = ClientId::from(name);
88        let client = HyperliquidDataClient::new(client_id, hyperliquid_config)?;
89        Ok(Box::new(client))
90    }
91
92    fn name(&self) -> &'static str {
93        "HYPERLIQUID"
94    }
95
96    fn config_type(&self) -> &'static str {
97        "HyperliquidDataClientConfig"
98    }
99}
100
101/// Configuration for creating Hyperliquid execution clients via factory.
102///
103/// This wraps [`HyperliquidExecClientConfig`] with the additional trader and account
104/// identifiers required by the [`ExecutionClientCore`].
105#[derive(Clone, Debug)]
106pub struct HyperliquidExecFactoryConfig {
107    /// The trader ID for the execution client.
108    pub trader_id: TraderId,
109    /// The account ID for the execution client.
110    pub account_id: AccountId,
111    /// The underlying execution client configuration.
112    pub config: HyperliquidExecClientConfig,
113}
114
115impl ClientConfig for HyperliquidExecFactoryConfig {
116    fn as_any(&self) -> &dyn Any {
117        self
118    }
119}
120
121/// Factory for creating Hyperliquid execution clients.
122#[derive(Debug)]
123pub struct HyperliquidExecutionClientFactory;
124
125impl HyperliquidExecutionClientFactory {
126    /// Creates a new [`HyperliquidExecutionClientFactory`] instance.
127    #[must_use]
128    pub const fn new() -> Self {
129        Self
130    }
131}
132
133impl Default for HyperliquidExecutionClientFactory {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl ExecutionClientFactory for HyperliquidExecutionClientFactory {
140    fn create(
141        &self,
142        name: &str,
143        config: &dyn ClientConfig,
144        cache: Rc<RefCell<Cache>>,
145    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
146        let factory_config = config
147            .as_any()
148            .downcast_ref::<HyperliquidExecFactoryConfig>()
149            .ok_or_else(|| {
150                anyhow::anyhow!(
151                    "Invalid config type for HyperliquidExecutionClientFactory. Expected HyperliquidExecFactoryConfig, was {config:?}",
152                )
153            })?
154            .clone();
155
156        // Hyperliquid uses netting for perpetual futures
157        let oms_type = OmsType::Netting;
158
159        // Hyperliquid is always margin (perpetual futures)
160        let account_type = AccountType::Margin;
161
162        let core = ExecutionClientCore::new(
163            factory_config.trader_id,
164            ClientId::from(name),
165            *HYPERLIQUID_VENUE,
166            oms_type,
167            factory_config.account_id,
168            account_type,
169            None,
170            cache,
171        );
172
173        let client = HyperliquidExecutionClient::new(core, factory_config.config)?;
174        Ok(Box::new(client))
175    }
176
177    fn name(&self) -> &'static str {
178        "HYPERLIQUID"
179    }
180
181    fn config_type(&self) -> &'static str {
182        "HyperliquidExecFactoryConfig"
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use std::{cell::RefCell, rc::Rc};
189
190    use nautilus_common::{cache::Cache, clock::TestClock};
191    use nautilus_model::identifiers::{AccountId, TraderId};
192    use nautilus_system::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
193    use rstest::rstest;
194
195    use super::*;
196    use crate::config::{HyperliquidDataClientConfig, HyperliquidExecClientConfig};
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(), "HyperliquidExecFactoryConfig");
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_factory_config_implements_client_config() {
237        let config = HyperliquidExecFactoryConfig {
238            trader_id: TraderId::from("TRADER-001"),
239            account_id: AccountId::from("HYPERLIQUID-001"),
240            config: HyperliquidExecClientConfig::new("test_private_key".to_string()),
241        };
242
243        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
244        let downcasted = boxed_config
245            .as_any()
246            .downcast_ref::<HyperliquidExecFactoryConfig>();
247
248        assert!(downcasted.is_some());
249    }
250
251    #[rstest]
252    fn test_hyperliquid_data_client_factory_rejects_wrong_config_type() {
253        let factory = HyperliquidDataClientFactory::new();
254        let wrong_config = HyperliquidExecFactoryConfig {
255            trader_id: TraderId::from("TRADER-001"),
256            account_id: AccountId::from("HYPERLIQUID-001"),
257            config: HyperliquidExecClientConfig::new("test_private_key".to_string()),
258        };
259
260        let cache = Rc::new(RefCell::new(Cache::default()));
261        let clock = Rc::new(RefCell::new(TestClock::new()));
262
263        let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache, clock);
264        assert!(result.is_err());
265        assert!(
266            result
267                .err()
268                .unwrap()
269                .to_string()
270                .contains("Invalid config type")
271        );
272    }
273
274    #[rstest]
275    fn test_hyperliquid_execution_client_factory_rejects_wrong_config_type() {
276        let factory = HyperliquidExecutionClientFactory::new();
277        let wrong_config = HyperliquidDataClientConfig::default();
278
279        let cache = Rc::new(RefCell::new(Cache::default()));
280
281        let result = factory.create("HYPERLIQUID-TEST", &wrong_config, cache);
282        assert!(result.is_err());
283        assert!(
284            result
285                .err()
286                .unwrap()
287                .to_string()
288                .contains("Invalid config type")
289        );
290    }
291}