use std::fmt;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::shared::models::{
leverage::Leverage,
margin::Margin,
price::Price,
quantity::Quantity,
serde_util,
trade::{TradeExecution, TradeExecutionType, TradeSide, TradeSize},
};
use super::{
client_id::ClientId, cross_leverage::CrossLeverage,
error::FuturesIsolatedTradeRequestValidationError,
};
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub(in crate::api_v3) struct FuturesIsolatedTradeRequestBody {
leverage: Leverage,
side: TradeSide,
#[serde(skip_serializing_if = "Option::is_none")]
stoploss: Option<Price>,
#[serde(skip_serializing_if = "Option::is_none")]
takeprofit: Option<Price>,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<ClientId>,
#[serde(flatten)]
size: TradeSize,
#[serde(rename = "type")]
trade_type: TradeExecutionType,
#[serde(skip_serializing_if = "Option::is_none")]
price: Option<Price>,
}
impl FuturesIsolatedTradeRequestBody {
pub fn new(
leverage: Leverage,
stoploss: Option<Price>,
takeprofit: Option<Price>,
side: TradeSide,
client_id: Option<ClientId>,
size: TradeSize,
trade_execution: TradeExecution,
) -> Result<Self, FuturesIsolatedTradeRequestValidationError> {
if let TradeExecution::Limit(price) = trade_execution {
if let TradeSize::Margin(margin) = &size {
let _ = Quantity::try_calculate(*margin, price, leverage)?;
}
if let Some(stoploss) = stoploss
&& stoploss >= price
{
return Err(FuturesIsolatedTradeRequestValidationError::StopLossHigherThanPrice);
}
if let Some(takeprofit) = takeprofit
&& takeprofit <= price
{
return Err(FuturesIsolatedTradeRequestValidationError::TakeProfitLowerThanPrice);
}
}
let (trade_type, price) = match trade_execution {
TradeExecution::Market => (TradeExecutionType::Market, None),
TradeExecution::Limit(price) => (TradeExecutionType::Limit, Some(price)),
};
Ok(FuturesIsolatedTradeRequestBody {
leverage,
stoploss,
takeprofit,
side,
client_id,
size,
trade_type,
price,
})
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Trade {
id: Uuid,
#[serde(rename = "type")]
trade_type: TradeExecutionType,
side: TradeSide,
opening_fee: u64,
closing_fee: u64,
maintenance_margin: i64,
quantity: Quantity,
margin: Margin,
leverage: Leverage,
price: Price,
liquidation: Price,
#[serde(with = "serde_util::price_option")]
stoploss: Option<Price>,
#[serde(with = "serde_util::price_option")]
takeprofit: Option<Price>,
#[serde(with = "serde_util::price_option")]
exit_price: Option<Price>,
pl: i64,
created_at: DateTime<Utc>,
filled_at: Option<DateTime<Utc>>,
closed_at: Option<DateTime<Utc>>,
#[serde(with = "serde_util::price_option")]
entry_price: Option<Price>,
entry_margin: Option<Margin>,
open: bool,
running: bool,
canceled: bool,
closed: bool,
sum_funding_fees: i64,
#[serde(with = "serde_util::client_id_option")]
client_id: Option<ClientId>,
}
impl Trade {
pub fn id(&self) -> Uuid {
self.id
}
pub fn trade_type(&self) -> TradeExecutionType {
self.trade_type
}
pub fn side(&self) -> TradeSide {
self.side
}
pub fn opening_fee(&self) -> u64 {
self.opening_fee
}
pub fn closing_fee(&self) -> u64 {
self.closing_fee
}
pub fn maintenance_margin(&self) -> i64 {
self.maintenance_margin
}
pub fn quantity(&self) -> Quantity {
self.quantity
}
pub fn margin(&self) -> Margin {
self.margin
}
pub fn leverage(&self) -> Leverage {
self.leverage
}
pub fn price(&self) -> Price {
self.price
}
pub fn liquidation(&self) -> Price {
self.liquidation
}
pub fn stoploss(&self) -> Option<Price> {
self.stoploss
}
pub fn takeprofit(&self) -> Option<Price> {
self.takeprofit
}
pub fn exit_price(&self) -> Option<Price> {
self.exit_price
}
pub fn pl(&self) -> i64 {
self.pl
}
pub fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
pub fn filled_at(&self) -> Option<DateTime<Utc>> {
self.filled_at
}
pub fn closed_at(&self) -> Option<DateTime<Utc>> {
self.closed_at
}
pub fn entry_price(&self) -> Option<Price> {
self.entry_price
}
pub fn entry_margin(&self) -> Option<Margin> {
self.entry_margin
}
pub fn open(&self) -> bool {
self.open
}
pub fn running(&self) -> bool {
self.running
}
pub fn canceled(&self) -> bool {
self.canceled
}
pub fn closed(&self) -> bool {
self.closed
}
pub fn sum_funding_fees(&self) -> i64 {
self.sum_funding_fees
}
pub fn client_id(&self) -> Option<&ClientId> {
self.client_id.as_ref()
}
pub fn as_data_str(&self) -> String {
let mut data_str = format!(
"id: {}\nside: {}\nopen: {}\nrunning: {}\ncanceled: {}\nclosed: {}\nquantity: {}\nmargin: {}\nleverage: {}\nprice: {}\nliquidation: {}\npl: {}\ncreated_at: {}",
self.id,
self.side,
self.open,
self.running,
self.canceled,
self.closed,
self.quantity,
self.margin,
self.leverage,
self.price,
self.liquidation,
self.pl,
self.created_at.to_rfc3339()
);
if let Some(entry_price) = self.entry_price {
data_str.push_str(&format!("\nentry_price: {entry_price}"));
}
if let Some(exit_price) = self.exit_price {
data_str.push_str(&format!("\nexit_price: {exit_price}"));
}
if let Some(stoploss) = self.stoploss {
data_str.push_str(&format!("\nstoploss: {stoploss}"));
}
if let Some(takeprofit) = self.takeprofit {
data_str.push_str(&format!("\ntakeprofit: {takeprofit}"));
}
if let Some(client_id) = &self.client_id {
data_str.push_str(&format!("\nclient_id: {client_id}"));
}
data_str
}
}
impl fmt::Display for Trade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Trade:")?;
for line in self.as_data_str().lines() {
write!(f, "\n {line}")?;
}
Ok(())
}
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub(in crate::api_v3) struct FuturesCrossOrderBody {
side: TradeSide,
quantity: Quantity,
#[serde(rename = "type")]
trade_type: TradeExecutionType,
#[serde(skip_serializing_if = "Option::is_none")]
price: Option<Price>,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<ClientId>,
}
impl FuturesCrossOrderBody {
pub fn new(
side: TradeSide,
quantity: Quantity,
execution: TradeExecution,
client_id: Option<ClientId>,
) -> Self {
let (trade_type, price) = match execution {
TradeExecution::Market => (TradeExecutionType::Market, None),
TradeExecution::Limit(price) => (TradeExecutionType::Limit, Some(price)),
};
Self {
side,
quantity,
trade_type,
price,
client_id,
}
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CrossOrder {
id: Uuid,
#[serde(rename = "type")]
trade_type: TradeExecutionType,
side: TradeSide,
quantity: Quantity,
price: Price,
trading_fee: u64,
created_at: DateTime<Utc>,
filled_at: Option<DateTime<Utc>>,
canceled_at: Option<DateTime<Utc>>,
open: bool,
filled: bool,
canceled: bool,
#[serde(with = "serde_util::client_id_option")]
client_id: Option<ClientId>,
}
impl CrossOrder {
pub fn id(&self) -> Uuid {
self.id
}
pub fn trade_type(&self) -> TradeExecutionType {
self.trade_type
}
pub fn side(&self) -> TradeSide {
self.side
}
pub fn quantity(&self) -> Quantity {
self.quantity
}
pub fn price(&self) -> Price {
self.price
}
pub fn trading_fee(&self) -> u64 {
self.trading_fee
}
pub fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
pub fn filled_at(&self) -> Option<DateTime<Utc>> {
self.filled_at
}
pub fn canceled_at(&self) -> Option<DateTime<Utc>> {
self.canceled_at
}
pub fn open(&self) -> bool {
self.open
}
pub fn filled(&self) -> bool {
self.filled
}
pub fn canceled(&self) -> bool {
self.canceled
}
pub fn client_id(&self) -> Option<&ClientId> {
self.client_id.as_ref()
}
pub fn as_data_str(&self) -> String {
let mut result = format!(
"id: {}\nside: {}\nopen: {}\nfilled: {}\ncanceled: {}\nquantity: {}\nprice: {}\ntrading_fee: {}\ncreated_at: {}",
self.id,
self.side,
self.open,
self.filled,
self.canceled,
self.quantity,
self.price,
self.trading_fee,
self.created_at.to_rfc3339()
);
if let Some(filled_at) = self.filled_at {
result.push_str(&format!("\nfilled_at: {}", filled_at.to_rfc3339()));
}
if let Some(canceled_at) = self.canceled_at {
result.push_str(&format!("\ncanceled_at: {}", canceled_at.to_rfc3339()));
}
if let Some(client_id) = &self.client_id {
result.push_str(&format!("\nclient_id: {}", client_id));
}
result
}
}
impl fmt::Display for CrossOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cross Order:")?;
for line in self.as_data_str().lines() {
write!(f, "\n {line}")?;
}
Ok(())
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CrossPosition {
id: Uuid,
margin: u64,
quantity: i64,
leverage: CrossLeverage,
entry_price: Option<Price>,
running_margin: u64,
initial_margin: u64,
maintenance_margin: u64,
liquidation: Option<Price>,
trading_fees: u64,
funding_fees: u64,
total_pl: i64,
delta_pl: i64,
}
impl CrossPosition {
pub fn id(&self) -> Uuid {
self.id
}
pub fn margin(&self) -> u64 {
self.margin
}
pub fn quantity(&self) -> i64 {
self.quantity
}
pub fn leverage(&self) -> CrossLeverage {
self.leverage
}
pub fn entry_price(&self) -> Option<Price> {
self.entry_price
}
pub fn running_margin(&self) -> u64 {
self.running_margin
}
pub fn initial_margin(&self) -> u64 {
self.initial_margin
}
pub fn maintenance_margin(&self) -> u64 {
self.maintenance_margin
}
pub fn liquidation(&self) -> Option<Price> {
self.liquidation
}
pub fn trading_fees(&self) -> u64 {
self.trading_fees
}
pub fn funding_fees(&self) -> u64 {
self.funding_fees
}
pub fn total_pl(&self) -> i64 {
self.total_pl
}
pub fn delta_pl(&self) -> i64 {
self.delta_pl
}
pub fn as_data_str(&self) -> String {
let mut data_str = format!(
"id: {}\nmargin: {}\nquantity: {}\nleverage: {}\nrunning_margin: {}\ninitial_margin: {}\nmaintenance_margin: {}\ntrading_fees: {}\nfunding_fees: {}\ntotal_pl: {}\ndelta_pl: {}",
self.id,
self.margin,
self.quantity,
self.leverage,
self.running_margin,
self.initial_margin,
self.maintenance_margin,
self.trading_fees,
self.funding_fees,
self.total_pl,
self.delta_pl
);
if let Some(entry_price) = self.entry_price {
data_str.push_str(&format!("\nentry_price: {entry_price}"));
}
if let Some(liquidation) = self.liquidation {
data_str.push_str(&format!("\nliquidation: {liquidation}"));
}
data_str
}
}
impl fmt::Display for CrossPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cross Position:")?;
for line in self.as_data_str().lines() {
write!(f, "\n {line}")?;
}
Ok(())
}
}