use crate::error::{BybitError, Result};
use crate::models::common::*;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaceOrderParams {
pub category: Category,
pub symbol: String,
pub side: Side,
pub order_type: OrderType,
pub qty: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_in_force: Option<TimeInForce>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position_idx: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_link_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub take_profit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_loss: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reduce_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_on_trigger: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_by: Option<TriggerBy>,
}
impl PlaceOrderParams {
pub fn market(category: Category, symbol: &str, side: Side, qty: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
side,
order_type: OrderType::Market,
qty: qty.to_string(),
price: None,
time_in_force: None,
position_idx: None,
order_link_id: None,
take_profit: None,
stop_loss: None,
reduce_only: None,
close_on_trigger: None,
trigger_price: None,
trigger_by: None,
}
}
pub fn limit(category: Category, symbol: &str, side: Side, qty: &str, price: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
side,
order_type: OrderType::Limit,
qty: qty.to_string(),
price: Some(price.to_string()),
time_in_force: Some(TimeInForce::GTC),
position_idx: None,
order_link_id: None,
take_profit: None,
stop_loss: None,
reduce_only: None,
close_on_trigger: None,
trigger_price: None,
trigger_by: None,
}
}
pub fn with_position_idx(mut self, idx: i32) -> Self {
self.position_idx = Some(idx);
self
}
pub fn with_order_link_id(mut self, id: &str) -> Self {
self.order_link_id = Some(id.to_string());
self
}
pub fn with_take_profit(mut self, price: &str) -> Self {
self.take_profit = Some(price.to_string());
self
}
pub fn with_stop_loss(mut self, price: &str) -> Self {
self.stop_loss = Some(price.to_string());
self
}
pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
self.reduce_only = Some(reduce_only);
self
}
pub fn validate(&self) -> Result<()> {
if self.symbol.is_empty() {
return Err(BybitError::InvalidParam("symbol cannot be empty".into()));
}
if self.qty.is_empty() {
return Err(BybitError::InvalidParam("qty cannot be empty".into()));
}
let qty: Decimal = self
.qty
.parse()
.map_err(|_| BybitError::InvalidParam("qty must be a valid number".into()))?;
if qty <= Decimal::ZERO {
return Err(BybitError::InvalidParam("qty must be positive".into()));
}
if self.order_type == OrderType::Limit {
match &self.price {
None => {
return Err(BybitError::InvalidParam(
"price is required for limit orders".into(),
))
}
Some(p) => {
let price: Decimal = p.parse().map_err(|_| {
BybitError::InvalidParam("price must be a valid number".into())
})?;
if price <= Decimal::ZERO {
return Err(BybitError::InvalidParam("price must be positive".into()));
}
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AmendOrderParams {
pub category: Category,
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_link_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qty: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub take_profit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_loss: Option<String>,
}
impl AmendOrderParams {
pub fn by_order_id(category: Category, symbol: &str, order_id: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
order_id: Some(order_id.to_string()),
order_link_id: None,
qty: None,
price: None,
take_profit: None,
stop_loss: None,
}
}
pub fn by_order_link_id(category: Category, symbol: &str, order_link_id: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
order_id: None,
order_link_id: Some(order_link_id.to_string()),
qty: None,
price: None,
take_profit: None,
stop_loss: None,
}
}
pub fn with_price(mut self, price: &str) -> Self {
self.price = Some(price.to_string());
self
}
pub fn with_qty(mut self, qty: &str) -> Self {
self.qty = Some(qty.to_string());
self
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelOrderParams {
pub category: Category,
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_link_id: Option<String>,
}
impl CancelOrderParams {
pub fn by_order_id(category: Category, symbol: &str, order_id: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
order_id: Some(order_id.to_string()),
order_link_id: None,
}
}
pub fn by_order_link_id(category: Category, symbol: &str, order_link_id: &str) -> Self {
Self {
category,
symbol: symbol.to_string(),
order_id: None,
order_link_id: Some(order_link_id.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelAllOrdersParams {
pub category: Category,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_coin: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub settle_coin: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderResponse {
pub order_id: String,
#[serde(default)]
pub order_link_id: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrdersList {
pub category: String,
pub list: Vec<OrderInfo>,
#[serde(default)]
pub next_page_cursor: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderInfo {
pub order_id: String,
#[serde(default)]
pub order_link_id: String,
pub symbol: String,
pub side: String,
pub order_type: String,
#[serde(default)]
pub price: String,
pub qty: String,
#[serde(default)]
pub time_in_force: String,
pub order_status: String,
#[serde(default)]
pub cum_exec_qty: String,
#[serde(default)]
pub cum_exec_value: String,
#[serde(default)]
pub avg_price: String,
pub created_time: String,
pub updated_time: String,
#[serde(default)]
pub take_profit: String,
#[serde(default)]
pub stop_loss: String,
#[serde(default)]
pub position_idx: i32,
#[serde(default)]
pub reduce_only: bool,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchOrderRequest {
pub category: Category,
pub request: Vec<PlaceOrderParams>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchOrderResponse {
pub list: Vec<BatchOrderResult>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchOrderResult {
pub category: String,
pub symbol: String,
#[serde(default)]
pub order_id: String,
#[serde(default)]
pub order_link_id: String,
#[serde(default)]
pub create_type: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelAllResponse {
pub list: Vec<CancelledOrder>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelledOrder {
pub order_id: String,
#[serde(default)]
pub order_link_id: String,
}