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