use std::{fmt, result::Result};
use chrono::{
DateTime, Utc,
serde::{ts_milliseconds, ts_milliseconds_option},
};
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::{error::FuturesTradeRequestValidationError, serde_util as serde_util_api_v2};
#[derive(Serialize, Debug)]
pub(in crate::api_v2) struct FuturesTradeRequestBody {
leverage: Leverage,
#[serde(skip_serializing_if = "Option::is_none")]
stoploss: Option<Price>,
#[serde(skip_serializing_if = "Option::is_none")]
takeprofit: Option<Price>,
#[serde(with = "serde_util_api_v2::trade_side")]
side: TradeSide,
#[serde(flatten)]
size: TradeSize,
#[serde(rename = "type", with = "serde_util_api_v2::trade_execution_type")]
trade_type: TradeExecutionType,
#[serde(skip_serializing_if = "Option::is_none")]
price: Option<Price>,
}
impl FuturesTradeRequestBody {
pub fn new(
leverage: Leverage,
stoploss: Option<Price>,
takeprofit: Option<Price>,
side: TradeSide,
size: TradeSize,
trade_execution: TradeExecution,
) -> Result<Self, FuturesTradeRequestValidationError> {
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(FuturesTradeRequestValidationError::StopLossHigherThanPrice);
}
if let Some(takeprofit) = takeprofit
&& takeprofit <= price
{
return Err(FuturesTradeRequestValidationError::TakeProfitLowerThanPrice);
}
}
let (trade_type, price) = match trade_execution {
TradeExecution::Market => (TradeExecutionType::Market, None),
TradeExecution::Limit(price) => (TradeExecutionType::Limit, Some(price)),
};
Ok(FuturesTradeRequestBody {
leverage,
stoploss,
takeprofit,
side,
size,
trade_type,
price,
})
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Trade {
id: Uuid,
uid: Uuid,
#[serde(rename = "type", with = "serde_util_api_v2::trade_execution_type")]
trade_type: TradeExecutionType,
#[serde(with = "serde_util_api_v2::trade_side")]
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,
#[serde(with = "ts_milliseconds")]
creation_ts: DateTime<Utc>,
#[serde(with = "ts_milliseconds_option")]
market_filled_ts: Option<DateTime<Utc>>,
#[serde(with = "ts_milliseconds_option")]
closed_ts: 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_carry_fees: i64,
}
impl Trade {
pub fn id(&self) -> Uuid {
self.id
}
pub fn uid(&self) -> Uuid {
self.uid
}
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 creation_ts(&self) -> DateTime<Utc> {
self.creation_ts
}
pub fn market_filled_ts(&self) -> Option<DateTime<Utc>> {
self.market_filled_ts
}
pub fn closed_ts(&self) -> Option<DateTime<Utc>> {
self.closed_ts
}
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_carry_fees(&self) -> i64 {
self.sum_carry_fees
}
pub fn as_data_str(&self) -> String {
let mut data_str = format!(
"id: {}\nuid: {}\nside: {}\nopen: {}\nrunning: {}\ncanceled: {}\nclosed: {}\nquantity: {}\nmargin: {}\nleverage: {}\nprice: {}\nliquidation: {}\npl: {}\ncreation_ts: {}",
self.id,
self.uid,
self.side,
self.open,
self.running,
self.canceled,
self.closed,
self.quantity,
self.margin,
self.leverage,
self.price,
self.liquidation,
self.pl,
self.creation_ts.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}"));
}
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(Deserialize)]
pub(in crate::api_v2) struct NestedTradesResponse {
pub trades: Vec<Trade>,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub(in crate::api_v2) enum TradeUpdateType {
Stoploss,
Takeprofit,
}
#[derive(Serialize, Debug)]
pub(in crate::api_v2) struct FuturesUpdateTradeRequestBody {
id: Uuid,
#[serde(rename = "type")]
update_type: TradeUpdateType,
value: Price,
}
impl FuturesUpdateTradeRequestBody {
pub fn new(id: Uuid, update_type: TradeUpdateType, value: Price) -> Self {
Self {
id,
update_type,
value,
}
}
}