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