use std::collections::HashMap;
use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
use nautilus_model::{
data::BarType,
enums::{OrderSide, OrderType, TimeInForce},
identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
instruments::Instrument,
orders::OrderAny,
python::{
instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
orders::pyobject_to_order_any,
},
types::{Price, Quantity},
};
use pyo3::{prelude::*, types::PyList};
use serde_json::to_string;
use crate::http::{client::HyperliquidHttpClient, parse::HyperliquidMarketType};
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl HyperliquidHttpClient {
#[new]
#[pyo3(signature = (private_key=None, vault_address=None, account_address=None, is_testnet=false, timeout_secs=60, proxy_url=None, normalize_prices=true))]
fn py_new(
private_key: Option<String>,
vault_address: Option<String>,
account_address: Option<String>,
is_testnet: bool,
timeout_secs: u64,
proxy_url: Option<String>,
normalize_prices: bool,
) -> PyResult<Self> {
let mut client = Self::with_credentials(
private_key,
vault_address,
account_address,
is_testnet,
timeout_secs,
proxy_url,
)
.map_err(to_pyvalue_err)?;
client.set_normalize_prices(normalize_prices);
Ok(client)
}
#[staticmethod]
#[pyo3(name = "from_env", signature = (is_testnet=false))]
fn py_from_env(is_testnet: bool) -> PyResult<Self> {
Self::from_env(is_testnet).map_err(to_pyvalue_err)
}
#[staticmethod]
#[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, is_testnet=false, timeout_secs=60, proxy_url=None))]
fn py_from_credentials(
private_key: &str,
vault_address: Option<&str>,
is_testnet: bool,
timeout_secs: u64,
proxy_url: Option<String>,
) -> PyResult<Self> {
Self::from_credentials(
private_key,
vault_address,
is_testnet,
timeout_secs,
proxy_url,
)
.map_err(to_pyvalue_err)
}
#[pyo3(name = "cache_instrument")]
fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
self.cache_instrument(&pyobject_to_instrument_any(py, instrument)?);
Ok(())
}
#[pyo3(name = "set_account_id")]
fn py_set_account_id(&mut self, account_id: &str) {
let account_id = AccountId::from(account_id);
self.set_account_id(account_id);
}
#[pyo3(name = "get_user_address")]
fn py_get_user_address(&self) -> PyResult<String> {
self.get_user_address().map_err(to_pyvalue_err)
}
#[pyo3(name = "get_spot_fill_coin_mapping")]
fn py_get_spot_fill_coin_mapping(&self) -> HashMap<String, String> {
self.get_spot_fill_coin_mapping()
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[pyo3(name = "get_spot_meta")]
fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
to_string(&meta).map_err(to_pyvalue_err)
})
}
#[pyo3(name = "get_perp_meta")]
fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
to_string(&meta).map_err(to_pyvalue_err)
})
}
#[pyo3(name = "load_instrument_definitions", signature = (include_spot=true, include_perps=true, include_perps_hip3=false))]
fn py_load_instrument_definitions<'py>(
&self,
py: Python<'py>,
include_spot: bool,
include_perps: bool,
include_perps_hip3: bool,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let mut defs = client
.request_instrument_defs()
.await
.map_err(to_pyvalue_err)?;
defs.retain(|def| match def.market_type {
HyperliquidMarketType::Perp => {
if def.is_hip3 {
include_perps_hip3
} else {
include_perps
}
}
HyperliquidMarketType::Spot => include_spot,
});
let mut instruments = client.convert_defs(defs);
instruments.sort_by_key(|instrument| instrument.id());
Python::attach(|py| {
let mut py_instruments = Vec::with_capacity(instruments.len());
for instrument in instruments {
py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
}
let py_list = PyList::new(py, &py_instruments)?;
Ok(py_list.into_any().unbind())
})
})
}
#[pyo3(name = "request_quote_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
fn py_request_quote_ticks<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
start: Option<chrono::DateTime<chrono::Utc>>,
end: Option<chrono::DateTime<chrono::Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let _ = (instrument_id, start, end, limit);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
"Hyperliquid does not provide historical quotes via HTTP API"
)))
})
}
#[pyo3(name = "request_trade_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
fn py_request_trade_ticks<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
start: Option<chrono::DateTime<chrono::Utc>>,
end: Option<chrono::DateTime<chrono::Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let _ = (instrument_id, start, end, limit);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
"Hyperliquid does not provide historical market trades via HTTP API"
)))
})
}
#[pyo3(name = "request_bars", signature = (bar_type, start=None, end=None, limit=None))]
fn py_request_bars<'py>(
&self,
py: Python<'py>,
bar_type: BarType,
start: Option<chrono::DateTime<chrono::Utc>>,
end: Option<chrono::DateTime<chrono::Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let bars = client
.request_bars(bar_type, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "submit_order", signature = (
instrument_id,
client_order_id,
order_side,
order_type,
quantity,
time_in_force,
price=None,
trigger_price=None,
post_only=false,
reduce_only=false,
))]
#[allow(clippy::too_many_arguments)]
fn py_submit_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
order_side: OrderSide,
order_type: OrderType,
quantity: Quantity,
time_in_force: TimeInForce,
price: Option<Price>,
trigger_price: Option<Price>,
post_only: bool,
reduce_only: bool,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let report = client
.submit_order(
instrument_id,
client_order_id,
order_side,
order_type,
quantity,
time_in_force,
price,
trigger_price,
post_only,
reduce_only,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "cancel_order", signature = (
instrument_id,
client_order_id=None,
venue_order_id=None,
))]
fn py_cancel_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
client_order_id: Option<ClientOrderId>,
venue_order_id: Option<VenueOrderId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.cancel_order(instrument_id, client_order_id, venue_order_id)
.await
.map_err(to_pyvalue_err)?;
Ok(())
})
}
#[pyo3(name = "modify_order")]
#[allow(clippy::too_many_arguments)]
fn py_modify_order<'py>(
&self,
py: Python<'py>,
instrument_id: InstrumentId,
venue_order_id: VenueOrderId,
order_side: OrderSide,
order_type: OrderType,
price: Price,
quantity: Quantity,
trigger_price: Option<Price>,
reduce_only: bool,
post_only: bool,
time_in_force: TimeInForce,
client_order_id: Option<ClientOrderId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.modify_order(
instrument_id,
venue_order_id,
order_side,
order_type,
price,
quantity,
trigger_price,
reduce_only,
post_only,
time_in_force,
client_order_id,
)
.await
.map_err(to_pyvalue_err)?;
Ok(())
})
}
#[pyo3(name = "submit_orders")]
fn py_submit_orders<'py>(
&self,
py: Python<'py>,
orders: Vec<Py<PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let order_anys: Vec<OrderAny> = Python::attach(|py| {
orders
.into_iter()
.map(|order| pyobject_to_order_any(py, order))
.collect::<PyResult<Vec<_>>>()
.map_err(to_pyvalue_err)
})?;
let order_refs: Vec<&OrderAny> = order_anys.iter().collect();
let reports = client
.submit_orders(&order_refs)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_order_status_reports")]
fn py_request_order_status_reports<'py>(
&self,
py: Python<'py>,
instrument_id: Option<&str>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let instrument_id = instrument_id.map(InstrumentId::from);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
let reports = client
.request_order_status_reports(&account_address, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_fill_reports")]
fn py_request_fill_reports<'py>(
&self,
py: Python<'py>,
instrument_id: Option<&str>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let instrument_id = instrument_id.map(InstrumentId::from);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
let reports = client
.request_fill_reports(&account_address, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_position_status_reports")]
fn py_request_position_status_reports<'py>(
&self,
py: Python<'py>,
instrument_id: Option<&str>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let instrument_id = instrument_id.map(InstrumentId::from);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
let reports = client
.request_position_status_reports(&account_address, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let pylist =
PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
Ok(pylist.into_py_any_unwrap(py))
})
})
}
#[pyo3(name = "request_account_state")]
fn py_request_account_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
let account_state = client
.request_account_state(&account_address)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
})
}
#[pyo3(name = "info_user_fees")]
fn py_info_user_fees<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
let json = client
.info_user_fees(&account_address)
.await
.map_err(to_pyvalue_err)?;
to_string(&json).map_err(to_pyvalue_err)
})
}
}