Skip to main content

nautilus_hyperliquid/python/
mod.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//! Python bindings from `pyo3`.
17
18#![expect(
19    clippy::missing_errors_doc,
20    reason = "errors documented on underlying Rust methods"
21)]
22
23pub mod config;
24pub mod enums;
25pub mod factories;
26pub mod http;
27pub mod urls;
28pub mod websocket;
29
30use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
31use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
32use nautilus_model::identifiers::ClientOrderId;
33use nautilus_system::get_global_pyo3_registry;
34use pyo3::prelude::*;
35
36use crate::{
37    common::{
38        consts::HYPERLIQUID_POST_ONLY_WOULD_MATCH,
39        enums::{
40            HyperliquidConditionalOrderType, HyperliquidEnvironment, HyperliquidProductType,
41            HyperliquidTpSl, HyperliquidTrailingOffsetType,
42        },
43    },
44    config::{HyperliquidDataClientConfig, HyperliquidExecClientConfig},
45    factories::{
46        HyperliquidDataClientFactory, HyperliquidExecFactoryConfig,
47        HyperliquidExecutionClientFactory,
48    },
49    http::{HyperliquidHttpClient, models::Cloid},
50    websocket::HyperliquidWebSocketClient,
51};
52
53/// Compute the cloid (hex hash) from a client_order_id.
54///
55/// The cloid is a keccak256 hash of the client_order_id, truncated to 16 bytes,
56/// represented as a hex string with `0x` prefix.
57#[pyfunction]
58#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.hyperliquid")]
59#[pyo3(name = "hyperliquid_cloid_from_client_order_id")]
60fn py_hyperliquid_cloid_from_client_order_id(client_order_id: ClientOrderId) -> String {
61    Cloid::from_client_order_id(client_order_id).to_hex()
62}
63
64/// Extract product type from a Hyperliquid symbol.
65///
66/// # Errors
67///
68/// Returns an error if the symbol does not contain a valid Hyperliquid product type suffix.
69#[pyfunction]
70#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.hyperliquid")]
71#[pyo3(name = "hyperliquid_product_type_from_symbol")]
72fn py_hyperliquid_product_type_from_symbol(symbol: &str) -> PyResult<HyperliquidProductType> {
73    HyperliquidProductType::from_symbol(symbol).map_err(to_pyvalue_err)
74}
75
76#[expect(clippy::needless_pass_by_value)]
77fn extract_hyperliquid_data_factory(
78    py: Python<'_>,
79    factory: Py<PyAny>,
80) -> PyResult<Box<dyn DataClientFactory>> {
81    match factory.extract::<HyperliquidDataClientFactory>(py) {
82        Ok(f) => Ok(Box::new(f)),
83        Err(e) => Err(to_pyvalue_err(format!(
84            "Failed to extract HyperliquidDataClientFactory: {e}"
85        ))),
86    }
87}
88
89#[expect(clippy::needless_pass_by_value)]
90fn extract_hyperliquid_exec_factory(
91    py: Python<'_>,
92    factory: Py<PyAny>,
93) -> PyResult<Box<dyn ExecutionClientFactory>> {
94    match factory.extract::<HyperliquidExecutionClientFactory>(py) {
95        Ok(f) => Ok(Box::new(f)),
96        Err(e) => Err(to_pyvalue_err(format!(
97            "Failed to extract HyperliquidExecutionClientFactory: {e}"
98        ))),
99    }
100}
101
102#[expect(clippy::needless_pass_by_value)]
103fn extract_hyperliquid_data_config(
104    py: Python<'_>,
105    config: Py<PyAny>,
106) -> PyResult<Box<dyn ClientConfig>> {
107    match config.extract::<HyperliquidDataClientConfig>(py) {
108        Ok(c) => Ok(Box::new(c)),
109        Err(e) => Err(to_pyvalue_err(format!(
110            "Failed to extract HyperliquidDataClientConfig: {e}"
111        ))),
112    }
113}
114
115#[expect(clippy::needless_pass_by_value)]
116fn extract_hyperliquid_exec_config(
117    py: Python<'_>,
118    config: Py<PyAny>,
119) -> PyResult<Box<dyn ClientConfig>> {
120    match config.extract::<HyperliquidExecFactoryConfig>(py) {
121        Ok(c) => Ok(Box::new(c)),
122        Err(e) => Err(to_pyvalue_err(format!(
123            "Failed to extract HyperliquidExecFactoryConfig: {e}"
124        ))),
125    }
126}
127
128/// Loaded as `nautilus_pyo3.hyperliquid`.
129#[pymodule]
130pub fn hyperliquid(m: &Bound<'_, PyModule>) -> PyResult<()> {
131    m.add(
132        "HYPERLIQUID_POST_ONLY_WOULD_MATCH",
133        HYPERLIQUID_POST_ONLY_WOULD_MATCH,
134    )?;
135    m.add_class::<HyperliquidHttpClient>()?;
136    m.add_class::<HyperliquidWebSocketClient>()?;
137    m.add_class::<HyperliquidProductType>()?;
138    m.add_class::<HyperliquidTpSl>()?;
139    m.add_class::<HyperliquidConditionalOrderType>()?;
140    m.add_class::<HyperliquidTrailingOffsetType>()?;
141    m.add_class::<HyperliquidEnvironment>()?;
142    m.add_function(wrap_pyfunction!(urls::py_get_hyperliquid_http_base_url, m)?)?;
143    m.add_function(wrap_pyfunction!(urls::py_get_hyperliquid_ws_url, m)?)?;
144    m.add_function(wrap_pyfunction!(
145        py_hyperliquid_product_type_from_symbol,
146        m
147    )?)?;
148    m.add_function(wrap_pyfunction!(
149        py_hyperliquid_cloid_from_client_order_id,
150        m
151    )?)?;
152    m.add_class::<HyperliquidDataClientConfig>()?;
153    m.add_class::<HyperliquidExecClientConfig>()?;
154    m.add_class::<HyperliquidExecFactoryConfig>()?;
155    m.add_class::<HyperliquidDataClientFactory>()?;
156    m.add_class::<HyperliquidExecutionClientFactory>()?;
157
158    let registry = get_global_pyo3_registry();
159
160    if let Err(e) = registry
161        .register_factory_extractor("HYPERLIQUID".to_string(), extract_hyperliquid_data_factory)
162    {
163        return Err(to_pyruntime_err(format!(
164            "Failed to register Hyperliquid data factory extractor: {e}"
165        )));
166    }
167
168    if let Err(e) = registry.register_exec_factory_extractor(
169        "HYPERLIQUID".to_string(),
170        extract_hyperliquid_exec_factory,
171    ) {
172        return Err(to_pyruntime_err(format!(
173            "Failed to register Hyperliquid exec factory extractor: {e}"
174        )));
175    }
176
177    if let Err(e) = registry.register_config_extractor(
178        "HyperliquidDataClientConfig".to_string(),
179        extract_hyperliquid_data_config,
180    ) {
181        return Err(to_pyruntime_err(format!(
182            "Failed to register Hyperliquid data config extractor: {e}"
183        )));
184    }
185
186    if let Err(e) = registry.register_config_extractor(
187        "HyperliquidExecFactoryConfig".to_string(),
188        extract_hyperliquid_exec_config,
189    ) {
190        return Err(to_pyruntime_err(format!(
191            "Failed to register Hyperliquid exec config extractor: {e}"
192        )));
193    }
194
195    Ok(())
196}