use std::collections::HashSet;
use chrono::{DateTime, Utc};
use nautilus_core::{
UnixNanos,
python::{to_pyruntime_err, to_pyvalue_err},
};
use nautilus_model::{
data::{BarType, forward::ForwardPrice},
enums::{OrderSide, OrderType, TimeInForce},
identifiers::{AccountId, ClientOrderId, InstrumentId, Symbol, VenueOrderId},
python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
types::{Price, Quantity},
};
use pyo3::{
conversion::IntoPyObjectExt,
prelude::*,
types::{PyDict, PyList},
};
use ustr::Ustr;
use crate::{
common::{
enums::{
BybitMarginMode, BybitOpenOnly, BybitOrderFilter, BybitPositionIdx, BybitPositionMode,
BybitProductType,
},
parse::{extract_raw_symbol, parse_bbo_level, parse_bbo_side_type},
},
http::{
client::{BybitHttpClient, BybitRawHttpClient},
error::BybitHttpError,
models::BybitOrderCursorList,
},
};
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl BybitRawHttpClient {
#[new]
#[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, testnet=false, timeout_secs=60, max_retries=3, retry_delay_ms=1000, retry_delay_max_ms=10_000, recv_window_ms=5_000, proxy_url=None))]
#[expect(clippy::too_many_arguments)]
fn py_new(
api_key: Option<String>,
api_secret: Option<String>,
base_url: Option<String>,
demo: bool,
testnet: bool,
timeout_secs: u64,
max_retries: u32,
retry_delay_ms: u64,
retry_delay_max_ms: u64,
recv_window_ms: u64,
proxy_url: Option<String>,
) -> PyResult<Self> {
Self::new_with_env(
api_key,
api_secret,
base_url,
demo,
testnet,
timeout_secs,
max_retries,
retry_delay_ms,
retry_delay_max_ms,
recv_window_ms,
proxy_url,
)
.map_err(to_pyvalue_err)
}
#[getter]
#[pyo3(name = "base_url")]
#[must_use]
pub fn py_base_url(&self) -> &str {
self.base_url()
}
#[getter]
#[pyo3(name = "api_key")]
#[must_use]
pub fn py_api_key(&self) -> Option<String> {
self.credential().map(|c| c.api_key().to_string())
}
#[getter]
#[pyo3(name = "recv_window_ms")]
#[must_use]
pub fn py_recv_window_ms(&self) -> u64 {
self.recv_window_ms()
}
#[pyo3(name = "cancel_all_requests")]
fn py_cancel_all_requests(&self) {
self.cancel_all_requests();
}
#[pyo3(name = "get_server_time")]
fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let response = client.get_server_time().await.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let server_time = Py::new(py, response.result)?;
Ok(server_time.into_any())
})
})
}
#[pyo3(name = "get_open_orders")]
#[pyo3(signature = (category, symbol=None, base_coin=None, settle_coin=None, order_id=None, order_link_id=None, open_only=None, order_filter=None, limit=None, cursor=None))]
#[expect(clippy::too_many_arguments)]
fn py_get_open_orders<'py>(
&self,
py: Python<'py>,
category: BybitProductType,
symbol: Option<String>,
base_coin: Option<String>,
settle_coin: Option<String>,
order_id: Option<String>,
order_link_id: Option<String>,
open_only: Option<BybitOpenOnly>,
order_filter: Option<BybitOrderFilter>,
limit: Option<u32>,
cursor: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let response = client
.get_open_orders(
category,
symbol,
base_coin,
settle_coin,
order_id,
order_link_id,
open_only,
order_filter,
limit,
cursor,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let open_orders = BybitOrderCursorList::from(response.result);
let py_open_orders = Py::new(py, open_orders)?;
Ok(py_open_orders.into_any())
})
})
}
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl BybitHttpClient {
#[new]
#[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, testnet=false, timeout_secs=60, max_retries=3, retry_delay_ms=1000, retry_delay_max_ms=10_000, recv_window_ms=5_000, proxy_url=None))]
#[expect(clippy::too_many_arguments)]
fn py_new(
api_key: Option<String>,
api_secret: Option<String>,
base_url: Option<String>,
demo: bool,
testnet: bool,
timeout_secs: u64,
max_retries: u32,
retry_delay_ms: u64,
retry_delay_max_ms: u64,
recv_window_ms: u64,
proxy_url: Option<String>,
) -> PyResult<Self> {
Self::new_with_env(
api_key,
api_secret,
base_url,
demo,
testnet,
timeout_secs,
max_retries,
retry_delay_ms,
retry_delay_max_ms,
recv_window_ms,
proxy_url,
)
.map_err(to_pyvalue_err)
}
#[getter]
#[pyo3(name = "base_url")]
#[must_use]
pub fn py_base_url(&self) -> &str {
self.base_url()
}
#[getter]
#[pyo3(name = "api_key")]
#[must_use]
pub fn py_api_key(&self) -> Option<&str> {
self.credential().map(|c| c.api_key())
}
#[getter]
#[pyo3(name = "api_key_masked")]
#[must_use]
pub fn py_api_key_masked(&self) -> Option<String> {
self.credential().map(|c| c.api_key_masked())
}
#[pyo3(name = "cache_instrument")]
fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
let inst_any = pyobject_to_instrument_any(py, instrument)?;
self.cache_instrument(inst_any);
Ok(())
}
#[pyo3(name = "cancel_all_requests")]
fn py_cancel_all_requests(&self) {
self.cancel_all_requests();
}
#[pyo3(name = "set_use_spot_position_reports")]
fn py_set_use_spot_position_reports(&self, value: bool) {
self.set_use_spot_position_reports(value);
}
#[pyo3(name = "set_margin_mode")]
fn py_set_margin_mode<'py>(
&self,
py: Python<'py>,
margin_mode: BybitMarginMode,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.set_margin_mode(margin_mode)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "get_account_details")]
fn py_get_account_details<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let response = client.get_account_details().await.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let account_details = Py::new(py, response.result)?;
Ok(account_details.into_any())
})
})
}
#[pyo3(name = "set_leverage")]
#[pyo3(signature = (product_type, symbol, buy_leverage, sell_leverage))]
fn py_set_leverage<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
symbol: String,
buy_leverage: String,
sell_leverage: String,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.set_leverage(product_type, &symbol, &buy_leverage, &sell_leverage)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "switch_mode")]
#[pyo3(signature = (product_type, mode, symbol=None, coin=None))]
fn py_switch_mode<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
mode: BybitPositionMode,
symbol: Option<String>,
coin: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.switch_mode(product_type, mode, symbol, coin)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "get_spot_borrow_amount")]
fn py_get_spot_borrow_amount<'py>(
&self,
py: Python<'py>,
coin: String,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let borrow_amount = client
.get_spot_borrow_amount(&coin)
.await
.map_err(to_pyvalue_err)?;
Ok(borrow_amount)
})
}
#[pyo3(name = "borrow_spot")]
#[pyo3(signature = (coin, amount))]
fn py_borrow_spot<'py>(
&self,
py: Python<'py>,
coin: String,
amount: Quantity,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.borrow_spot(&coin, amount)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "repay_spot_borrow")]
#[pyo3(signature = (coin, amount=None))]
fn py_repay_spot_borrow<'py>(
&self,
py: Python<'py>,
coin: String,
amount: Option<Quantity>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client
.repay_spot_borrow(&coin, amount)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(py.None()))
})
}
#[pyo3(name = "request_instruments")]
#[pyo3(signature = (product_type, symbol=None, base_coin=None))]
fn py_request_instruments<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
symbol: Option<String>,
base_coin: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let base_coin = base_coin.map(|s| Ustr::from(&s));
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instruments = client
.request_instruments(product_type, symbol, base_coin)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_instruments: PyResult<Vec<_>> = instruments
.into_iter()
.map(|inst| instrument_any_to_pyobject(py, inst))
.collect();
let pylist = PyList::new(py, py_instruments?)
.unwrap()
.into_any()
.unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_instrument_statuses")]
fn py_request_instrument_statuses<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let statuses = client
.request_instrument_statuses(product_type)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let dict = PyDict::new(py);
for (instrument_id, action) in statuses {
dict.set_item(
instrument_id.into_bound_py_any(py)?,
action.into_bound_py_any(py)?,
)?;
}
Ok(dict.into_any().unbind())
})
})
}
#[pyo3(name = "request_tickers")]
fn py_request_tickers<'py>(
&self,
py: Python<'py>,
params: crate::python::params::BybitTickersParams,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let tickers = client
.request_tickers(¶ms.into())
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_tickers: PyResult<Vec<_>> = tickers
.into_iter()
.map(|ticker| Py::new(py, ticker))
.collect();
let pylist = PyList::new(py, py_tickers?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "submit_order")]
#[pyo3(signature = (
account_id,
product_type,
instrument_id,
client_order_id,
order_side,
order_type,
quantity,
time_in_force = None,
price = None,
trigger_price = None,
post_only = None,
reduce_only = false,
is_quote_quantity = false,
is_leverage = false,
position_idx = None,
bbo_side_type = None,
bbo_level = None,
))]
#[expect(clippy::too_many_arguments)]
fn py_submit_order<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
order_side: OrderSide,
order_type: OrderType,
quantity: Quantity,
time_in_force: Option<TimeInForce>,
price: Option<Price>,
trigger_price: Option<Price>,
post_only: Option<bool>,
reduce_only: bool,
is_quote_quantity: bool,
is_leverage: bool,
position_idx: Option<BybitPositionIdx>,
bbo_side_type: Option<String>,
bbo_level: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
let bbo_side_type = bbo_side_type
.map(|value| parse_bbo_side_type(&value))
.transpose()
.map_err(to_pyvalue_err)?;
let bbo_level = bbo_level
.map(parse_bbo_level)
.transpose()
.map_err(to_pyvalue_err)?;
if bbo_side_type.is_some() != bbo_level.is_some() {
return Err(to_pyvalue_err(anyhow::anyhow!(
"'bbo_side_type' and 'bbo_level' must be provided together"
)));
}
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let report = client
.submit_order(
account_id,
product_type,
instrument_id,
client_order_id,
order_side,
order_type,
quantity,
time_in_force,
price,
trigger_price,
post_only,
reduce_only,
is_quote_quantity,
is_leverage,
position_idx,
bbo_side_type,
bbo_level,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| report.into_py_any(py))
})
}
#[pyo3(name = "modify_order")]
#[pyo3(signature = (
account_id,
product_type,
instrument_id,
client_order_id=None,
venue_order_id=None,
quantity=None,
price=None
))]
#[expect(clippy::too_many_arguments)]
fn py_modify_order<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: InstrumentId,
client_order_id: Option<ClientOrderId>,
venue_order_id: Option<VenueOrderId>,
quantity: Option<Quantity>,
price: Option<Price>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let report = client
.modify_order(
account_id,
product_type,
instrument_id,
client_order_id,
venue_order_id,
quantity,
price,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| report.into_py_any(py))
})
}
#[pyo3(name = "cancel_order")]
#[pyo3(signature = (account_id, product_type, instrument_id, client_order_id=None, venue_order_id=None))]
fn py_cancel_order<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
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 {
let report = client
.cancel_order(
account_id,
product_type,
instrument_id,
client_order_id,
venue_order_id,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| report.into_py_any(py))
})
}
#[pyo3(name = "cancel_all_orders")]
fn py_cancel_all_orders<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: InstrumentId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.cancel_all_orders(account_id, product_type, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_reports: PyResult<Vec<_>> = reports
.into_iter()
.map(|report| report.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "query_order")]
#[pyo3(signature = (account_id, product_type, instrument_id, client_order_id=None, venue_order_id=None))]
fn py_query_order<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
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 {
match client
.query_order(
account_id,
product_type,
instrument_id,
client_order_id,
venue_order_id,
)
.await
{
Ok(Some(report)) => Python::attach(|py| report.into_py_any(py)),
Ok(None) => Ok(Python::attach(|py| py.None())),
Err(e) => Err(to_pyvalue_err(e)),
}
})
}
#[pyo3(name = "request_trades")]
#[pyo3(signature = (product_type, instrument_id, limit=None))]
fn py_request_trades<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
instrument_id: InstrumentId,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let trades = client
.request_trades(product_type, instrument_id, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_trades: PyResult<Vec<_>> = trades
.into_iter()
.map(|trade| trade.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_funding_rates")]
#[pyo3(signature = (product_type, instrument_id, start=None, end=None, limit=None))]
fn py_request_funding_rates<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
instrument_id: InstrumentId,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let funding_rates = client
.request_funding_rates(product_type, instrument_id, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_funding_rates: PyResult<Vec<_>> = funding_rates
.into_iter()
.map(|funding_rate| funding_rate.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_funding_rates?)
.unwrap()
.into_any()
.unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_orderbook_snapshot")]
#[pyo3(signature = (product_type, instrument_id, limit=None))]
fn py_request_orderbook_snapshot<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
instrument_id: InstrumentId,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let deltas = client
.request_orderbook_snapshot(product_type, instrument_id, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| Ok(deltas.into_py_any(py).unwrap()))
})
}
#[pyo3(name = "request_bars")]
#[pyo3(signature = (product_type, bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
#[expect(clippy::too_many_arguments)]
fn py_request_bars<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
bar_type: BarType,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
timestamp_on_close: bool,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let bars = client
.request_bars(
product_type,
bar_type,
start,
end,
limit,
timestamp_on_close,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_bars: PyResult<Vec<_>> =
bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_fee_rates")]
#[pyo3(signature = (product_type, symbol=None, base_coin=None))]
fn py_request_fee_rates<'py>(
&self,
py: Python<'py>,
product_type: BybitProductType,
symbol: Option<String>,
base_coin: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let fee_rates = client
.request_fee_rates(product_type, symbol, base_coin)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_fee_rates: PyResult<Vec<_>> = fee_rates
.into_iter()
.map(|rate| Py::new(py, rate))
.collect();
let pylist = PyList::new(py, py_fee_rates?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_account_state")]
fn py_request_account_state<'py>(
&self,
py: Python<'py>,
account_type: crate::common::enums::BybitAccountType,
account_id: AccountId,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let account_state = client
.request_account_state(account_type, account_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| account_state.into_py_any(py))
})
}
#[pyo3(name = "request_order_status_reports")]
#[pyo3(signature = (account_id, product_type, instrument_id=None, open_only=false, start=None, end=None, limit=None))]
#[expect(clippy::too_many_arguments)]
fn py_request_order_status_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: Option<InstrumentId>,
open_only: bool,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_order_status_reports(
account_id,
product_type,
instrument_id,
open_only,
start,
end,
limit,
)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_reports: PyResult<Vec<_>> = reports
.into_iter()
.map(|report| report.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_fill_reports")]
#[pyo3(signature = (account_id, product_type, instrument_id=None, start=None, end=None, limit=None))]
#[expect(clippy::too_many_arguments)]
fn py_request_fill_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: Option<InstrumentId>,
start: Option<i64>,
end: Option<i64>,
limit: Option<u32>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_fill_reports(account_id, product_type, instrument_id, start, end, limit)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_reports: PyResult<Vec<_>> = reports
.into_iter()
.map(|report| report.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_position_status_reports")]
#[pyo3(signature = (account_id, product_type, instrument_id=None))]
fn py_request_position_status_reports<'py>(
&self,
py: Python<'py>,
account_id: AccountId,
product_type: BybitProductType,
instrument_id: Option<InstrumentId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let reports = client
.request_position_status_reports(account_id, product_type, instrument_id)
.await
.map_err(to_pyvalue_err)?;
Python::attach(|py| {
let py_reports: PyResult<Vec<_>> = reports
.into_iter()
.map(|report| report.into_py_any(py))
.collect();
let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
Ok(pylist)
})
})
}
#[pyo3(name = "request_forward_prices")]
#[pyo3(signature = (base_coin, instrument_id=None))]
fn py_request_forward_prices<'py>(
&self,
py: Python<'py>,
base_coin: String,
instrument_id: Option<InstrumentId>,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let forward_prices: Vec<ForwardPrice> = if let Some(inst_id) = instrument_id {
let raw_symbol = extract_raw_symbol(inst_id.symbol.as_str()).to_string();
let params = crate::http::query::BybitTickersParams {
category: BybitProductType::Option,
symbol: Some(raw_symbol),
base_coin: None,
exp_date: None,
};
let tickers = client
.request_option_tickers_raw_with_params(¶ms)
.await
.map_err(to_pyvalue_err)?;
let ts = UnixNanos::default();
tickers
.into_iter()
.filter_map(|t| {
let up: rust_decimal::Decimal = t.underlying_price.parse().ok()?;
if up.is_zero() {
return None;
}
Some(ForwardPrice::new(inst_id, up, None, ts, ts))
})
.collect()
} else {
let tickers = client
.request_option_tickers_raw(&base_coin)
.await
.map_err(to_pyvalue_err)?;
let ts = nautilus_core::UnixNanos::default();
let mut seen_expiries = HashSet::new();
tickers
.into_iter()
.filter_map(|t| {
let up: rust_decimal::Decimal = t.underlying_price.parse().ok()?;
if up.is_zero() {
return None;
}
let parts: Vec<&str> = t.symbol.splitn(3, '-').collect();
let expiry_key = if parts.len() >= 2 {
format!("{}-{}", parts[0], parts[1])
} else {
t.symbol.to_string()
};
if !seen_expiries.insert(expiry_key) {
return None;
}
let symbol_str = format!("{}-OPTION", t.symbol);
let inst_id = InstrumentId::new(
Symbol::new(&symbol_str),
*crate::common::consts::BYBIT_VENUE,
);
Some(ForwardPrice::new(inst_id, up, None, ts, ts))
})
.collect()
};
Python::attach(|py| {
let py_prices: PyResult<Vec<_>> = forward_prices
.into_iter()
.map(|fp| Py::new(py, fp))
.collect();
let pylist = PyList::new(py, py_prices?)?.into_any().unbind();
Ok(pylist)
})
})
}
}
impl From<BybitHttpError> for PyErr {
fn from(error: BybitHttpError) -> Self {
match error {
BybitHttpError::Canceled(msg) => to_pyruntime_err(format!("Request canceled: {msg}")),
BybitHttpError::NetworkError(msg) => to_pyruntime_err(format!("Network error: {msg}")),
BybitHttpError::UnexpectedStatus { status, body } => {
to_pyruntime_err(format!("Unexpected HTTP status code {status}: {body}"))
}
BybitHttpError::MissingCredentials => {
to_pyvalue_err("Missing credentials for authenticated request")
}
BybitHttpError::ValidationError(msg) => {
to_pyvalue_err(format!("Parameter validation error: {msg}"))
}
BybitHttpError::JsonError(msg) => to_pyvalue_err(format!("JSON error: {msg}")),
BybitHttpError::BuildError(e) => to_pyvalue_err(format!("Build error: {e}")),
BybitHttpError::BybitError {
error_code,
message,
} => to_pyvalue_err(format!("Bybit error {error_code}: {message}")),
}
}
}