nautilus_hyperliquid/python/
mod.rs1#![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;
27
28#[cfg(feature = "arrow")]
29pub mod arrow;
30
31use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
32use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
33use nautilus_model::{data::ensure_rust_extractor_registered, identifiers::ClientOrderId};
34use nautilus_system::get_global_pyo3_registry;
35use pyo3::prelude::*;
36
37use crate::{
38 account::resolve_execution_account_address,
39 common::{
40 builder_fee::{approve_from_env, revoke_from_env},
41 consts::{HYPERLIQUID, HYPERLIQUID_CLIENT_ID, HYPERLIQUID_VENUE},
42 enums::{
43 HyperliquidConditionalOrderType, HyperliquidEnvironment, HyperliquidProductType,
44 HyperliquidTpSl, HyperliquidTrailingOffsetType,
45 },
46 },
47 config::{HyperliquidDataClientConfig, HyperliquidExecutionClientConfig},
48 data_types::{
49 HyperliquidAllDexsAssetCtxs, HyperliquidAllMids, HyperliquidOpenInterest,
50 HyperliquidPublicTrade, HyperliquidTwapHistory, HyperliquidTwapSliceFill,
51 register_hyperliquid_custom_data,
52 },
53 factories::{HyperliquidDataClientFactory, HyperliquidExecutionClientFactory},
54 http::{HyperliquidHttpClient, models::Cloid},
55};
56
57#[pyfunction]
71#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.hyperliquid")]
72#[pyo3(name = "builder_fee_approve")]
73fn py_builder_fee_approve() -> PyResult<bool> {
74 std::thread::spawn(move || {
75 let runtime = tokio::runtime::Builder::new_current_thread()
76 .enable_all()
77 .build()
78 .map_err(|e| to_pyruntime_err(format!("Failed to create runtime: {e}")))?;
79
80 Ok(runtime.block_on(approve_from_env(true)))
81 })
82 .join()
83 .map_err(|_| to_pyruntime_err("Thread panicked"))?
84}
85
86#[pyfunction]
100#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.hyperliquid")]
101#[pyo3(name = "builder_fee_revoke")]
102fn py_builder_fee_revoke() -> PyResult<bool> {
103 std::thread::spawn(move || {
104 let runtime = tokio::runtime::Builder::new_current_thread()
105 .enable_all()
106 .build()
107 .map_err(|e| to_pyruntime_err(format!("Failed to create runtime: {e}")))?;
108
109 Ok(runtime.block_on(revoke_from_env(true)))
110 })
111 .join()
112 .map_err(|_| to_pyruntime_err("Thread panicked"))?
113}
114
115#[pyfunction]
120#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.hyperliquid")]
121#[pyo3(name = "hyperliquid_cloid_from_client_order_id")]
122fn py_hyperliquid_cloid_from_client_order_id(client_order_id: ClientOrderId) -> String {
123 Cloid::from_client_order_id(client_order_id).to_hex()
124}
125
126#[pyfunction]
132#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.hyperliquid")]
133#[pyo3(name = "hyperliquid_product_type_from_symbol")]
134fn py_hyperliquid_product_type_from_symbol(symbol: &str) -> PyResult<HyperliquidProductType> {
135 HyperliquidProductType::from_symbol(symbol).map_err(to_pyvalue_err)
136}
137
138#[pyfunction]
144#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.hyperliquid")]
145#[pyo3(name = "hyperliquid_resolve_execution_account_address", signature = (private_key=None, vault_address=None, account_address=None, environment=HyperliquidEnvironment::Mainnet))]
146fn py_hyperliquid_resolve_execution_account_address(
147 private_key: Option<&str>,
148 vault_address: Option<&str>,
149 account_address: Option<&str>,
150 environment: HyperliquidEnvironment,
151) -> PyResult<Option<String>> {
152 resolve_execution_account_address(private_key, vault_address, account_address, environment)
153 .map_err(to_pyvalue_err)
154}
155
156#[expect(clippy::needless_pass_by_value)]
157fn extract_hyperliquid_data_factory(
158 py: Python<'_>,
159 factory: Py<PyAny>,
160) -> PyResult<Box<dyn DataClientFactory>> {
161 match factory.extract::<HyperliquidDataClientFactory>(py) {
162 Ok(f) => Ok(Box::new(f)),
163 Err(e) => Err(to_pyvalue_err(format!(
164 "Failed to extract HyperliquidDataClientFactory: {e}"
165 ))),
166 }
167}
168
169#[expect(clippy::needless_pass_by_value)]
170fn extract_hyperliquid_exec_factory(
171 py: Python<'_>,
172 factory: Py<PyAny>,
173) -> PyResult<Box<dyn ExecutionClientFactory>> {
174 match factory.extract::<HyperliquidExecutionClientFactory>(py) {
175 Ok(f) => Ok(Box::new(f)),
176 Err(e) => Err(to_pyvalue_err(format!(
177 "Failed to extract HyperliquidExecutionClientFactory: {e}"
178 ))),
179 }
180}
181
182#[expect(clippy::needless_pass_by_value)]
183fn extract_hyperliquid_data_config(
184 py: Python<'_>,
185 config: Py<PyAny>,
186) -> PyResult<Box<dyn ClientConfig>> {
187 match config.extract::<HyperliquidDataClientConfig>(py) {
188 Ok(c) => Ok(Box::new(c)),
189 Err(e) => Err(to_pyvalue_err(format!(
190 "Failed to extract HyperliquidDataClientConfig: {e}"
191 ))),
192 }
193}
194
195#[expect(clippy::needless_pass_by_value)]
196fn extract_hyperliquid_exec_config(
197 py: Python<'_>,
198 config: Py<PyAny>,
199) -> PyResult<Box<dyn ClientConfig>> {
200 match config.extract::<HyperliquidExecutionClientConfig>(py) {
201 Ok(c) => Ok(Box::new(c)),
202 Err(e) => Err(to_pyvalue_err(format!(
203 "Failed to extract HyperliquidExecutionClientConfig: {e}"
204 ))),
205 }
206}
207
208#[pymodule]
210pub fn hyperliquid(m: &Bound<'_, PyModule>) -> PyResult<()> {
211 m.add(stringify!(HYPERLIQUID), HYPERLIQUID)?;
212 m.add(stringify!(HYPERLIQUID_CLIENT_ID), *HYPERLIQUID_CLIENT_ID)?;
213 m.add(stringify!(HYPERLIQUID_VENUE), *HYPERLIQUID_VENUE)?;
214 m.add_class::<HyperliquidHttpClient>()?;
215 m.add_class::<HyperliquidProductType>()?;
216 m.add_class::<HyperliquidTpSl>()?;
217 m.add_class::<HyperliquidConditionalOrderType>()?;
218 m.add_class::<HyperliquidTrailingOffsetType>()?;
219 m.add_class::<HyperliquidEnvironment>()?;
220 m.add_function(wrap_pyfunction!(
221 py_hyperliquid_product_type_from_symbol,
222 m
223 )?)?;
224 m.add_function(wrap_pyfunction!(
225 py_hyperliquid_cloid_from_client_order_id,
226 m
227 )?)?;
228 m.add_function(wrap_pyfunction!(
229 py_hyperliquid_resolve_execution_account_address,
230 m
231 )?)?;
232 m.add_function(wrap_pyfunction!(py_builder_fee_approve, m)?)?;
233 m.add_function(wrap_pyfunction!(py_builder_fee_revoke, m)?)?;
234 m.add_class::<HyperliquidDataClientConfig>()?;
235 m.add_class::<HyperliquidDataClientFactory>()?;
236 m.add_class::<HyperliquidExecutionClientConfig>()?;
237 m.add_class::<HyperliquidExecutionClientFactory>()?;
238 m.add_class::<HyperliquidAllDexsAssetCtxs>()?;
239 m.add_class::<HyperliquidAllMids>()?;
240 m.add_class::<HyperliquidOpenInterest>()?;
241 m.add_class::<HyperliquidPublicTrade>()?;
242 m.add_class::<HyperliquidTwapHistory>()?;
243 m.add_class::<HyperliquidTwapSliceFill>()?;
244
245 register_hyperliquid_custom_data();
246 let _result = ensure_rust_extractor_registered::<HyperliquidAllDexsAssetCtxs>();
247 let _result = ensure_rust_extractor_registered::<HyperliquidAllMids>();
248 let _result = ensure_rust_extractor_registered::<HyperliquidOpenInterest>();
249 let _result = ensure_rust_extractor_registered::<HyperliquidPublicTrade>();
250 let _result = ensure_rust_extractor_registered::<HyperliquidTwapHistory>();
251 let _result = ensure_rust_extractor_registered::<HyperliquidTwapSliceFill>();
252
253 let registry = get_global_pyo3_registry();
254
255 if let Err(e) = registry
256 .register_factory_extractor(HYPERLIQUID.to_string(), extract_hyperliquid_data_factory)
257 {
258 return Err(to_pyruntime_err(format!(
259 "Failed to register Hyperliquid data factory extractor: {e}"
260 )));
261 }
262
263 if let Err(e) = registry
264 .register_exec_factory_extractor(HYPERLIQUID.to_string(), extract_hyperliquid_exec_factory)
265 {
266 return Err(to_pyruntime_err(format!(
267 "Failed to register Hyperliquid exec factory extractor: {e}"
268 )));
269 }
270
271 if let Err(e) = registry.register_config_extractor(
272 "HyperliquidDataClientConfig".to_string(),
273 extract_hyperliquid_data_config,
274 ) {
275 return Err(to_pyruntime_err(format!(
276 "Failed to register Hyperliquid data config extractor: {e}"
277 )));
278 }
279
280 if let Err(e) = registry.register_config_extractor(
281 "HyperliquidExecutionClientConfig".to_string(),
282 extract_hyperliquid_exec_config,
283 ) {
284 return Err(to_pyruntime_err(format!(
285 "Failed to register Hyperliquid exec config extractor: {e}"
286 )));
287 }
288
289 Ok(())
290}