use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use crate::common::{
enums::{
BybitAccountType, BybitCancelType, BybitContractType, BybitCreateType, BybitExecType,
BybitInnovationFlag, BybitInstrumentStatus, BybitMarginTrading, BybitOptionType,
BybitOrderSide, BybitOrderStatus, BybitOrderType, BybitPositionIdx, BybitPositionSide,
BybitPositionStatus, BybitProductType, BybitSmpType, BybitStopOrderType, BybitTimeInForce,
BybitTpSlMode, BybitTriggerDirection, BybitTriggerType,
},
models::{
BybitCursorList, BybitCursorListResponse, BybitListResponse, BybitResponse, LeverageFilter,
LinearLotSizeFilter, LinearPriceFilter, OptionLotSizeFilter, SpotLotSizeFilter,
SpotPriceFilter,
},
parse::{
deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero, deserialize_string_to_u8,
},
};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
pub struct BybitOrderCursorList {
pub list: Vec<BybitOrder>,
pub next_page_cursor: Option<String>,
#[serde(default)]
pub category: Option<BybitProductType>,
}
impl From<BybitCursorList<BybitOrder>> for BybitOrderCursorList {
fn from(cursor_list: BybitCursorList<BybitOrder>) -> Self {
Self {
list: cursor_list.list,
next_page_cursor: cursor_list.next_page_cursor,
category: cursor_list.category,
}
}
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitOrderCursorList {
#[getter]
#[must_use]
pub fn list(&self) -> Vec<BybitOrder> {
self.list.clone()
}
#[getter]
#[must_use]
pub fn next_page_cursor(&self) -> Option<&str> {
self.next_page_cursor.as_deref()
}
#[getter]
#[must_use]
pub fn category(&self) -> Option<BybitProductType> {
self.category
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
#[serde(rename_all = "camelCase")]
pub struct BybitServerTime {
pub time_second: String,
pub time_nano: String,
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitServerTime {
#[getter]
#[must_use]
pub fn time_second(&self) -> &str {
&self.time_second
}
#[getter]
#[must_use]
pub fn time_nano(&self) -> &str {
&self.time_nano
}
}
pub type BybitServerTimeResponse = BybitResponse<BybitServerTime>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitTickerSpot {
pub symbol: Ustr,
pub bid1_price: String,
pub bid1_size: String,
pub ask1_price: String,
pub ask1_size: String,
pub last_price: String,
pub prev_price24h: String,
pub price24h_pcnt: String,
pub high_price24h: String,
pub low_price24h: String,
pub turnover24h: String,
pub volume24h: String,
#[serde(default)]
pub usd_index_price: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitTickerLinear {
pub symbol: Ustr,
pub last_price: String,
pub index_price: String,
pub mark_price: String,
pub prev_price24h: String,
pub price24h_pcnt: String,
pub high_price24h: String,
pub low_price24h: String,
pub prev_price1h: String,
pub open_interest: String,
pub open_interest_value: String,
pub turnover24h: String,
pub volume24h: String,
pub funding_rate: String,
pub next_funding_time: String,
pub predicted_delivery_price: String,
pub basis_rate: String,
pub delivery_fee_rate: String,
pub delivery_time: String,
pub ask1_size: String,
pub bid1_price: String,
pub ask1_price: String,
pub bid1_size: String,
pub basis: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitTickerOption {
pub symbol: Ustr,
pub bid1_price: String,
pub bid1_size: String,
pub bid1_iv: String,
pub ask1_price: String,
pub ask1_size: String,
pub ask1_iv: String,
pub last_price: String,
pub high_price24h: String,
pub low_price24h: String,
pub mark_price: String,
pub index_price: String,
pub mark_iv: String,
pub underlying_price: String,
pub open_interest: String,
pub turnover24h: String,
pub volume24h: String,
pub total_volume: String,
pub total_turnover: String,
pub delta: String,
pub gamma: String,
pub vega: String,
pub theta: String,
pub predicted_delivery_price: String,
pub change24h: String,
}
pub type BybitTickersSpotResponse = BybitListResponse<BybitTickerSpot>;
pub type BybitTickersLinearResponse = BybitListResponse<BybitTickerLinear>;
pub type BybitTickersOptionResponse = BybitListResponse<BybitTickerOption>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
pub struct BybitTickerData {
pub symbol: Ustr,
pub bid1_price: String,
pub bid1_size: String,
pub ask1_price: String,
pub ask1_size: String,
pub last_price: String,
pub high_price24h: String,
pub low_price24h: String,
pub turnover24h: String,
pub volume24h: String,
#[serde(default)]
pub open_interest: Option<String>,
#[serde(default)]
pub funding_rate: Option<String>,
#[serde(default)]
pub next_funding_time: Option<String>,
#[serde(default)]
pub mark_price: Option<String>,
#[serde(default)]
pub index_price: Option<String>,
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitTickerData {
#[getter]
#[must_use]
pub fn symbol(&self) -> &str {
self.symbol.as_str()
}
#[getter]
#[must_use]
pub fn bid1_price(&self) -> &str {
&self.bid1_price
}
#[getter]
#[must_use]
pub fn bid1_size(&self) -> &str {
&self.bid1_size
}
#[getter]
#[must_use]
pub fn ask1_price(&self) -> &str {
&self.ask1_price
}
#[getter]
#[must_use]
pub fn ask1_size(&self) -> &str {
&self.ask1_size
}
#[getter]
#[must_use]
pub fn last_price(&self) -> &str {
&self.last_price
}
#[getter]
#[must_use]
pub fn high_price24h(&self) -> &str {
&self.high_price24h
}
#[getter]
#[must_use]
pub fn low_price24h(&self) -> &str {
&self.low_price24h
}
#[getter]
#[must_use]
pub fn turnover24h(&self) -> &str {
&self.turnover24h
}
#[getter]
#[must_use]
pub fn volume24h(&self) -> &str {
&self.volume24h
}
#[getter]
#[must_use]
pub fn open_interest(&self) -> Option<&str> {
self.open_interest.as_deref()
}
#[getter]
#[must_use]
pub fn funding_rate(&self) -> Option<&str> {
self.funding_rate.as_deref()
}
#[getter]
#[must_use]
pub fn next_funding_time(&self) -> Option<&str> {
self.next_funding_time.as_deref()
}
#[getter]
#[must_use]
pub fn mark_price(&self) -> Option<&str> {
self.mark_price.as_deref()
}
#[getter]
#[must_use]
pub fn index_price(&self) -> Option<&str> {
self.index_price.as_deref()
}
}
impl From<BybitTickerSpot> for BybitTickerData {
fn from(ticker: BybitTickerSpot) -> Self {
Self {
symbol: ticker.symbol,
bid1_price: ticker.bid1_price,
bid1_size: ticker.bid1_size,
ask1_price: ticker.ask1_price,
ask1_size: ticker.ask1_size,
last_price: ticker.last_price,
high_price24h: ticker.high_price24h,
low_price24h: ticker.low_price24h,
turnover24h: ticker.turnover24h,
volume24h: ticker.volume24h,
open_interest: None,
funding_rate: None,
next_funding_time: None,
mark_price: None,
index_price: None,
}
}
}
impl From<BybitTickerLinear> for BybitTickerData {
fn from(ticker: BybitTickerLinear) -> Self {
Self {
symbol: ticker.symbol,
bid1_price: ticker.bid1_price,
bid1_size: ticker.bid1_size,
ask1_price: ticker.ask1_price,
ask1_size: ticker.ask1_size,
last_price: ticker.last_price,
high_price24h: ticker.high_price24h,
low_price24h: ticker.low_price24h,
turnover24h: ticker.turnover24h,
volume24h: ticker.volume24h,
open_interest: Some(ticker.open_interest),
funding_rate: Some(ticker.funding_rate),
next_funding_time: Some(ticker.next_funding_time),
mark_price: Some(ticker.mark_price),
index_price: Some(ticker.index_price),
}
}
}
impl From<BybitTickerOption> for BybitTickerData {
fn from(ticker: BybitTickerOption) -> Self {
Self {
symbol: ticker.symbol,
bid1_price: ticker.bid1_price,
bid1_size: ticker.bid1_size,
ask1_price: ticker.ask1_price,
ask1_size: ticker.ask1_size,
last_price: ticker.last_price,
high_price24h: ticker.high_price24h,
low_price24h: ticker.low_price24h,
turnover24h: ticker.turnover24h,
volume24h: ticker.volume24h,
open_interest: Some(ticker.open_interest),
funding_rate: None,
next_funding_time: None,
mark_price: Some(ticker.mark_price),
index_price: Some(ticker.index_price),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct BybitKline {
pub start: String,
pub open: String,
pub high: String,
pub low: String,
pub close: String,
pub volume: String,
pub turnover: String,
}
impl<'de> Deserialize<'de> for BybitKline {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let [start, open, high, low, close, volume, turnover]: [String; 7] =
Deserialize::deserialize(deserializer)?;
Ok(Self {
start,
open,
high,
low,
close,
volume,
turnover,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitKlineResult {
pub category: BybitProductType,
pub symbol: Ustr,
pub list: Vec<BybitKline>,
}
pub type BybitKlinesResponse = BybitResponse<BybitKlineResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitTrade {
pub exec_id: String,
pub symbol: Ustr,
pub price: String,
pub size: String,
pub side: BybitOrderSide,
pub time: String,
pub is_block_trade: bool,
#[serde(default)]
pub m_p: Option<String>,
#[serde(default)]
pub i_p: Option<String>,
#[serde(default)]
pub mlv: Option<String>,
#[serde(default)]
pub iv: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitTradeResult {
pub category: BybitProductType,
pub list: Vec<BybitTrade>,
}
pub type BybitTradesResponse = BybitResponse<BybitTradeResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitFunding {
pub symbol: Ustr,
pub funding_rate: String,
pub funding_rate_timestamp: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitFundingResult {
pub category: BybitProductType,
pub list: Vec<BybitFunding>,
}
pub type BybitFundingResponse = BybitResponse<BybitFundingResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitOrderbookResult {
pub s: Ustr,
pub b: Vec<[String; 2]>,
pub a: Vec<[String; 2]>,
pub ts: i64,
pub u: i64,
pub seq: i64,
pub cts: i64,
}
pub type BybitOrderbookResponse = BybitResponse<BybitOrderbookResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitInstrumentSpot {
pub symbol: Ustr,
pub base_coin: Ustr,
pub quote_coin: Ustr,
pub innovation: BybitInnovationFlag,
pub status: BybitInstrumentStatus,
pub margin_trading: BybitMarginTrading,
pub lot_size_filter: SpotLotSizeFilter,
pub price_filter: SpotPriceFilter,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitInstrumentLinear {
pub symbol: Ustr,
pub contract_type: BybitContractType,
pub status: BybitInstrumentStatus,
pub base_coin: Ustr,
pub quote_coin: Ustr,
pub launch_time: String,
pub delivery_time: String,
pub delivery_fee_rate: String,
pub price_scale: String,
pub leverage_filter: LeverageFilter,
pub price_filter: LinearPriceFilter,
pub lot_size_filter: LinearLotSizeFilter,
pub unified_margin_trade: bool,
pub funding_interval: i64,
pub settle_coin: Ustr,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitInstrumentInverse {
pub symbol: Ustr,
pub contract_type: BybitContractType,
pub status: BybitInstrumentStatus,
pub base_coin: Ustr,
pub quote_coin: Ustr,
pub launch_time: String,
pub delivery_time: String,
pub delivery_fee_rate: String,
pub price_scale: String,
pub leverage_filter: LeverageFilter,
pub price_filter: LinearPriceFilter,
pub lot_size_filter: LinearLotSizeFilter,
pub unified_margin_trade: bool,
pub funding_interval: i64,
pub settle_coin: Ustr,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitInstrumentOption {
pub symbol: Ustr,
pub status: BybitInstrumentStatus,
pub base_coin: Ustr,
pub quote_coin: Ustr,
pub settle_coin: Ustr,
pub options_type: BybitOptionType,
pub launch_time: String,
pub delivery_time: String,
pub delivery_fee_rate: String,
pub price_filter: LinearPriceFilter,
pub lot_size_filter: OptionLotSizeFilter,
}
pub type BybitInstrumentSpotResponse = BybitCursorListResponse<BybitInstrumentSpot>;
pub type BybitInstrumentLinearResponse = BybitCursorListResponse<BybitInstrumentLinear>;
pub type BybitInstrumentInverseResponse = BybitCursorListResponse<BybitInstrumentInverse>;
pub type BybitInstrumentOptionResponse = BybitCursorListResponse<BybitInstrumentOption>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
pub struct BybitFeeRate {
pub symbol: Ustr,
pub taker_fee_rate: String,
pub maker_fee_rate: String,
#[serde(default)]
pub base_coin: Option<Ustr>,
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitFeeRate {
#[getter]
#[must_use]
pub fn symbol(&self) -> &str {
self.symbol.as_str()
}
#[getter]
#[must_use]
pub fn taker_fee_rate(&self) -> &str {
&self.taker_fee_rate
}
#[getter]
#[must_use]
pub fn maker_fee_rate(&self) -> &str {
&self.maker_fee_rate
}
#[getter]
#[must_use]
pub fn base_coin(&self) -> Option<&str> {
self.base_coin.as_ref().map(|u| u.as_str())
}
}
pub type BybitFeeRateResponse = BybitListResponse<BybitFeeRate>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitCoinBalance {
pub available_to_borrow: String,
pub bonus: String,
pub accrued_interest: String,
pub available_to_withdraw: String,
#[serde(default, rename = "totalOrderIM")]
pub total_order_im: Option<String>,
pub equity: String,
pub usd_value: String,
pub borrow_amount: String,
#[serde(default, rename = "totalPositionMM")]
pub total_position_mm: Option<String>,
#[serde(default, rename = "totalPositionIM")]
pub total_position_im: Option<String>,
#[serde(deserialize_with = "deserialize_decimal_or_zero")]
pub wallet_balance: Decimal,
pub unrealised_pnl: String,
pub cum_realised_pnl: String,
#[serde(deserialize_with = "deserialize_decimal_or_zero")]
pub locked: Decimal,
pub collateral_switch: bool,
pub margin_collateral: bool,
pub coin: Ustr,
#[serde(default)]
pub spot_hedging_qty: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
pub spot_borrow: Decimal,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitWalletBalance {
pub total_equity: String,
#[serde(rename = "accountIMRate")]
pub account_im_rate: String,
pub total_margin_balance: String,
pub total_initial_margin: String,
pub account_type: BybitAccountType,
pub total_available_balance: String,
#[serde(rename = "accountMMRate")]
pub account_mm_rate: String,
#[serde(rename = "totalPerpUPL")]
pub total_perp_upl: String,
pub total_wallet_balance: String,
#[serde(rename = "accountLTV")]
pub account_ltv: String,
pub total_maintenance_margin: String,
pub coin: Vec<BybitCoinBalance>,
}
pub type BybitWalletBalanceResponse = BybitListResponse<BybitWalletBalance>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
#[serde(rename_all = "camelCase")]
pub struct BybitOrder {
pub order_id: Ustr,
pub order_link_id: Ustr,
pub block_trade_id: Option<Ustr>,
pub symbol: Ustr,
pub price: String,
pub qty: String,
pub side: BybitOrderSide,
pub is_leverage: String,
pub position_idx: i32,
pub order_status: BybitOrderStatus,
pub cancel_type: BybitCancelType,
pub reject_reason: Ustr,
pub avg_price: Option<String>,
pub leaves_qty: String,
pub leaves_value: String,
pub cum_exec_qty: String,
pub cum_exec_value: String,
pub cum_exec_fee: String,
pub time_in_force: BybitTimeInForce,
pub order_type: BybitOrderType,
pub stop_order_type: BybitStopOrderType,
pub order_iv: Option<String>,
pub trigger_price: String,
pub take_profit: String,
pub stop_loss: String,
pub tp_trigger_by: BybitTriggerType,
pub sl_trigger_by: BybitTriggerType,
pub trigger_direction: BybitTriggerDirection,
pub trigger_by: BybitTriggerType,
pub last_price_on_created: String,
pub reduce_only: bool,
pub close_on_trigger: bool,
pub smp_type: BybitSmpType,
pub smp_group: i32,
pub smp_order_id: Ustr,
pub tpsl_mode: Option<BybitTpSlMode>,
pub tp_limit_price: String,
pub sl_limit_price: String,
pub place_type: Ustr,
pub created_time: String,
pub updated_time: String,
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitOrder {
#[getter]
#[must_use]
pub fn order_id(&self) -> &str {
self.order_id.as_str()
}
#[getter]
#[must_use]
pub fn order_link_id(&self) -> &str {
self.order_link_id.as_str()
}
#[getter]
#[must_use]
pub fn block_trade_id(&self) -> Option<&str> {
self.block_trade_id.as_ref().map(|s| s.as_str())
}
#[getter]
#[must_use]
pub fn symbol(&self) -> &str {
self.symbol.as_str()
}
#[getter]
#[must_use]
pub fn price(&self) -> &str {
&self.price
}
#[getter]
#[must_use]
pub fn qty(&self) -> &str {
&self.qty
}
#[getter]
#[must_use]
pub fn side(&self) -> BybitOrderSide {
self.side
}
#[getter]
#[must_use]
pub fn is_leverage(&self) -> &str {
&self.is_leverage
}
#[getter]
#[must_use]
pub fn position_idx(&self) -> i32 {
self.position_idx
}
#[getter]
#[must_use]
pub fn order_status(&self) -> BybitOrderStatus {
self.order_status
}
#[getter]
#[must_use]
pub fn cancel_type(&self) -> BybitCancelType {
self.cancel_type
}
#[getter]
#[must_use]
pub fn reject_reason(&self) -> &str {
self.reject_reason.as_str()
}
#[getter]
#[must_use]
pub fn avg_price(&self) -> Option<&str> {
self.avg_price.as_deref()
}
#[getter]
#[must_use]
pub fn leaves_qty(&self) -> &str {
&self.leaves_qty
}
#[getter]
#[must_use]
pub fn leaves_value(&self) -> &str {
&self.leaves_value
}
#[getter]
#[must_use]
pub fn cum_exec_qty(&self) -> &str {
&self.cum_exec_qty
}
#[getter]
#[must_use]
pub fn cum_exec_value(&self) -> &str {
&self.cum_exec_value
}
#[getter]
#[must_use]
pub fn cum_exec_fee(&self) -> &str {
&self.cum_exec_fee
}
#[getter]
#[must_use]
pub fn time_in_force(&self) -> BybitTimeInForce {
self.time_in_force
}
#[getter]
#[must_use]
pub fn order_type(&self) -> BybitOrderType {
self.order_type
}
#[getter]
#[must_use]
pub fn stop_order_type(&self) -> BybitStopOrderType {
self.stop_order_type
}
#[getter]
#[must_use]
pub fn order_iv(&self) -> Option<&str> {
self.order_iv.as_deref()
}
#[getter]
#[must_use]
pub fn trigger_price(&self) -> &str {
&self.trigger_price
}
#[getter]
#[must_use]
pub fn take_profit(&self) -> &str {
&self.take_profit
}
#[getter]
#[must_use]
pub fn stop_loss(&self) -> &str {
&self.stop_loss
}
#[getter]
#[must_use]
pub fn tp_trigger_by(&self) -> BybitTriggerType {
self.tp_trigger_by
}
#[getter]
#[must_use]
pub fn sl_trigger_by(&self) -> BybitTriggerType {
self.sl_trigger_by
}
#[getter]
#[must_use]
pub fn trigger_direction(&self) -> BybitTriggerDirection {
self.trigger_direction
}
#[getter]
#[must_use]
pub fn trigger_by(&self) -> BybitTriggerType {
self.trigger_by
}
#[getter]
#[must_use]
pub fn last_price_on_created(&self) -> &str {
&self.last_price_on_created
}
#[getter]
#[must_use]
pub fn reduce_only(&self) -> bool {
self.reduce_only
}
#[getter]
#[must_use]
pub fn close_on_trigger(&self) -> bool {
self.close_on_trigger
}
#[getter]
#[must_use]
#[allow(
clippy::missing_panics_doc,
reason = "serialization of a simple enum cannot fail"
)]
pub fn smp_type(&self) -> String {
serde_json::to_string(&self.smp_type)
.expect("Failed to serialize BybitSmpType")
.trim_matches('"')
.to_string()
}
#[getter]
#[must_use]
pub fn smp_group(&self) -> i32 {
self.smp_group
}
#[getter]
#[must_use]
pub fn smp_order_id(&self) -> &str {
self.smp_order_id.as_str()
}
#[getter]
#[must_use]
pub fn tpsl_mode(&self) -> Option<BybitTpSlMode> {
self.tpsl_mode
}
#[getter]
#[must_use]
pub fn tp_limit_price(&self) -> &str {
&self.tp_limit_price
}
#[getter]
#[must_use]
pub fn sl_limit_price(&self) -> &str {
&self.sl_limit_price
}
#[getter]
#[must_use]
pub fn place_type(&self) -> &str {
self.place_type.as_str()
}
#[getter]
#[must_use]
pub fn created_time(&self) -> &str {
&self.created_time
}
#[getter]
#[must_use]
pub fn updated_time(&self) -> &str {
&self.updated_time
}
}
pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitPlaceOrderResult {
pub order_id: Option<Ustr>,
pub order_link_id: Option<Ustr>,
}
pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitCancelOrderResult {
pub order_id: Option<Ustr>,
pub order_link_id: Option<Ustr>,
}
pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitExecution {
pub symbol: Ustr,
pub order_id: Ustr,
pub order_link_id: Ustr,
pub side: BybitOrderSide,
pub order_price: String,
pub order_qty: String,
pub leaves_qty: String,
pub create_type: Option<BybitCreateType>,
pub order_type: BybitOrderType,
pub stop_order_type: Option<BybitStopOrderType>,
pub exec_fee: String,
pub exec_id: String,
pub exec_price: String,
pub exec_qty: String,
pub exec_type: BybitExecType,
pub exec_value: String,
pub exec_time: String,
pub fee_currency: Ustr,
pub is_maker: bool,
pub fee_rate: String,
pub trade_iv: String,
pub mark_iv: String,
pub mark_price: String,
pub index_price: String,
pub underlying_price: String,
pub block_trade_id: String,
pub closed_size: String,
pub seq: i64,
}
pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitPosition {
pub position_idx: BybitPositionIdx,
pub risk_id: i32,
pub risk_limit_value: String,
pub symbol: Ustr,
pub side: BybitPositionSide,
pub size: String,
pub avg_price: String,
pub position_value: String,
pub trade_mode: i32,
pub position_status: BybitPositionStatus,
pub auto_add_margin: i32,
pub adl_rank_indicator: i32,
pub leverage: String,
pub position_balance: String,
pub mark_price: String,
pub liq_price: String,
pub bust_price: String,
#[serde(rename = "positionMM")]
pub position_mm: String,
#[serde(rename = "positionIM")]
pub position_im: String,
pub tpsl_mode: BybitTpSlMode,
pub take_profit: String,
pub stop_loss: String,
pub trailing_stop: String,
pub unrealised_pnl: String,
pub cur_realised_pnl: String,
pub cum_realised_pnl: String,
pub seq: i64,
pub is_reduce_only: bool,
pub mmr_sys_updated_time: String,
pub leverage_sys_updated_time: String,
pub created_time: String,
pub updated_time: String,
}
pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitSetMarginModeReason {
pub reason_code: String,
pub reason_msg: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitSetMarginModeResult {
#[serde(default)]
pub reasons: Vec<BybitSetMarginModeReason>,
}
pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BybitSetLeverageResult {}
pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BybitSwitchModeResult {}
pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BybitSetTradingStopResult {}
pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitBorrowResult {
pub coin: Ustr,
pub amount: String,
}
pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitNoConvertRepayResult {
pub result_status: String,
}
pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
#[serde(rename_all = "PascalCase")]
pub struct BybitApiKeyPermissions {
#[serde(default)]
pub contract_trade: Vec<String>,
#[serde(default)]
pub spot: Vec<String>,
#[serde(default)]
pub wallet: Vec<String>,
#[serde(default)]
pub options: Vec<String>,
#[serde(default)]
pub derivatives: Vec<String>,
#[serde(default)]
pub exchange: Vec<String>,
#[serde(default)]
pub copy_trading: Vec<String>,
#[serde(default)]
pub block_trade: Vec<String>,
#[serde(default)]
pub nft: Vec<String>,
#[serde(default)]
pub affiliate: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
)]
#[serde(rename_all = "camelCase")]
pub struct BybitAccountDetails {
pub id: String,
pub note: String,
pub api_key: String,
pub read_only: u8,
pub secret: String,
#[serde(rename = "type")]
pub key_type: u8,
pub permissions: BybitApiKeyPermissions,
pub ips: Vec<String>,
#[serde(default)]
pub user_id: Option<u64>,
#[serde(default)]
pub inviter_id: Option<u64>,
pub vip_level: String,
#[serde(deserialize_with = "deserialize_string_to_u8", default)]
pub mkt_maker_level: u8,
#[serde(default)]
pub affiliate_id: Option<u64>,
pub rsa_public_key: String,
pub is_master: bool,
pub parent_uid: String,
pub uta: u8,
pub kyc_level: String,
pub kyc_region: String,
#[serde(default)]
pub deadline_day: i64,
#[serde(default)]
pub expired_at: Option<String>,
pub created_at: String,
}
#[cfg(feature = "python")]
#[pyo3::pymethods]
impl BybitAccountDetails {
#[getter]
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[getter]
#[must_use]
pub fn note(&self) -> &str {
&self.note
}
#[getter]
#[must_use]
pub fn api_key(&self) -> &str {
&self.api_key
}
#[getter]
#[must_use]
pub fn read_only(&self) -> u8 {
self.read_only
}
#[getter]
#[must_use]
pub fn key_type(&self) -> u8 {
self.key_type
}
#[getter]
#[must_use]
pub fn user_id(&self) -> Option<u64> {
self.user_id
}
#[getter]
#[must_use]
pub fn inviter_id(&self) -> Option<u64> {
self.inviter_id
}
#[getter]
#[must_use]
pub fn vip_level(&self) -> &str {
&self.vip_level
}
#[getter]
#[must_use]
pub fn mkt_maker_level(&self) -> u8 {
self.mkt_maker_level
}
#[getter]
#[must_use]
pub fn affiliate_id(&self) -> Option<u64> {
self.affiliate_id
}
#[getter]
#[must_use]
pub fn rsa_public_key(&self) -> &str {
&self.rsa_public_key
}
#[getter]
#[must_use]
pub fn is_master(&self) -> bool {
self.is_master
}
#[getter]
#[must_use]
pub fn parent_uid(&self) -> &str {
&self.parent_uid
}
#[getter]
#[must_use]
pub fn uta(&self) -> u8 {
self.uta
}
#[getter]
#[must_use]
pub fn kyc_level(&self) -> &str {
&self.kyc_level
}
#[getter]
#[must_use]
pub fn kyc_region(&self) -> &str {
&self.kyc_region
}
#[getter]
#[must_use]
pub fn deadline_day(&self) -> i64 {
self.deadline_day
}
#[getter]
#[must_use]
pub fn expired_at(&self) -> Option<&str> {
self.expired_at.as_deref()
}
#[getter]
#[must_use]
pub fn created_at(&self) -> &str {
&self.created_at
}
}
pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
#[cfg(test)]
mod tests {
use nautilus_core::UnixNanos;
use nautilus_model::identifiers::AccountId;
use rstest::rstest;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use super::*;
use crate::common::testing::load_test_json;
#[rstest]
fn deserialize_spot_instrument_uses_enums() {
let json = load_test_json("http_get_instruments_spot.json");
let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
let instrument = &response.result.list[0];
assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
}
#[rstest]
fn deserialize_linear_instrument_status() {
let json = load_test_json("http_get_instruments_linear.json");
let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
let instrument = &response.result.list[0];
assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
}
#[rstest]
fn deserialize_order_response_maps_enums() {
let json = load_test_json("http_get_orders_history.json");
let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
let order = &response.result.list[0];
assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
assert_eq!(order.order_type, BybitOrderType::Limit);
assert_eq!(order.smp_type, BybitSmpType::None);
}
#[rstest]
fn deserialize_wallet_balance_without_optional_fields() {
let json = r#"{
"retCode": 0,
"retMsg": "OK",
"result": {
"list": [{
"totalEquity": "1000.00",
"accountIMRate": "0",
"totalMarginBalance": "1000.00",
"totalInitialMargin": "0",
"accountType": "UNIFIED",
"totalAvailableBalance": "1000.00",
"accountMMRate": "0",
"totalPerpUPL": "0",
"totalWalletBalance": "1000.00",
"accountLTV": "0",
"totalMaintenanceMargin": "0",
"coin": [{
"availableToBorrow": "0",
"bonus": "0",
"accruedInterest": "0",
"availableToWithdraw": "1000.00",
"equity": "1000.00",
"usdValue": "1000.00",
"borrowAmount": "0",
"totalPositionIM": "0",
"walletBalance": "1000.00",
"unrealisedPnl": "0",
"cumRealisedPnl": "0",
"locked": "0",
"collateralSwitch": true,
"marginCollateral": true,
"coin": "USDT"
}]
}]
}
}"#;
let response: BybitWalletBalanceResponse = serde_json::from_str(json)
.expect("Failed to parse wallet balance without optional fields");
assert_eq!(response.ret_code, 0);
assert_eq!(response.result.list[0].coin[0].total_order_im, None);
assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
}
#[rstest]
fn deserialize_wallet_balance_from_docs() {
let json = include_str!("../../test_data/http_get_wallet_balance.json");
let response: BybitWalletBalanceResponse = serde_json::from_str(json)
.expect("Failed to parse wallet balance from Bybit docs example");
assert_eq!(response.ret_code, 0);
assert_eq!(response.ret_msg, "OK");
let wallet = &response.result.list[0];
assert_eq!(wallet.total_equity, "3.31216591");
assert_eq!(wallet.account_im_rate, "0");
assert_eq!(wallet.account_mm_rate, "0");
assert_eq!(wallet.total_perp_upl, "0");
assert_eq!(wallet.account_ltv, "0");
let btc = &wallet.coin[0];
assert_eq!(btc.coin.as_str(), "BTC");
assert_eq!(btc.available_to_borrow, "3");
assert_eq!(btc.total_order_im, Some("0".to_string()));
assert_eq!(btc.total_position_mm, Some("0".to_string()));
assert_eq!(btc.total_position_im, Some("0".to_string()));
let usdt = &wallet.coin[1];
assert_eq!(usdt.coin.as_str(), "USDT");
assert_eq!(usdt.wallet_balance, dec!(1000.50));
assert_eq!(usdt.total_order_im, None);
assert_eq!(usdt.total_position_mm, None);
assert_eq!(usdt.total_position_im, None);
assert_eq!(btc.spot_borrow, Decimal::ZERO);
assert_eq!(usdt.spot_borrow, Decimal::ZERO);
}
#[rstest]
fn test_parse_wallet_balance_with_spot_borrow() {
let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
let response: BybitWalletBalanceResponse =
serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
let wallet = &response.result.list[0];
let usdt = &wallet.coin[0];
assert_eq!(usdt.coin.as_str(), "USDT");
assert_eq!(usdt.wallet_balance, dec!(1200.00));
assert_eq!(usdt.spot_borrow, dec!(200.00));
assert_eq!(usdt.borrow_amount, "200.00");
let account_id = crate::common::parse::parse_account_state(
wallet,
AccountId::new("BYBIT-001"),
UnixNanos::default(),
)
.expect("Failed to parse account state");
let balance = &account_id.balances[0];
assert_eq!(balance.total.as_f64(), 1000.0);
}
#[rstest]
fn test_parse_wallet_balance_spot_short() {
let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
let response: BybitWalletBalanceResponse = serde_json::from_str(json)
.expect("Failed to parse wallet balance with SHORT SPOT position");
let wallet = &response.result.list[0];
let eth = &wallet.coin[0];
assert_eq!(eth.coin.as_str(), "ETH");
assert_eq!(eth.wallet_balance, dec!(0));
assert_eq!(eth.spot_borrow, dec!(0.06142));
assert_eq!(eth.borrow_amount, "0.06142");
let account_state = crate::common::parse::parse_account_state(
wallet,
AccountId::new("BYBIT-001"),
UnixNanos::default(),
)
.expect("Failed to parse account state");
let eth_balance = account_state
.balances
.iter()
.find(|b| b.currency.code.as_str() == "ETH")
.expect("ETH balance not found");
assert_eq!(eth_balance.total.as_f64(), -0.06142);
}
#[rstest]
fn deserialize_borrow_response() {
let json = r#"{
"retCode": 0,
"retMsg": "success",
"result": {
"coin": "BTC",
"amount": "0.01"
},
"retExtInfo": {},
"time": 1756197991955
}"#;
let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.ret_code, 0);
assert_eq!(response.ret_msg, "success");
assert_eq!(response.result.coin, "BTC");
assert_eq!(response.result.amount, "0.01");
}
#[rstest]
fn deserialize_no_convert_repay_response() {
let json = r#"{
"retCode": 0,
"retMsg": "OK",
"result": {
"resultStatus": "SU"
},
"retExtInfo": {},
"time": 1234567890
}"#;
let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.ret_code, 0);
assert_eq!(response.ret_msg, "OK");
assert_eq!(response.result.result_status, "SU");
}
}