Skip to main content

rustrade_execution/
trade.rs

1use crate::order::id::{OrderId, StrategyId};
2use chrono::{DateTime, Utc};
3use derive_more::{Constructor, From};
4use rust_decimal::Decimal;
5use rustrade_instrument::{Side, asset::QuoteAsset};
6use serde::{Deserialize, Serialize};
7use smol_str::SmolStr;
8use std::fmt::{Display, Formatter};
9
10#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, From)]
11pub struct TradeId<T = SmolStr>(pub T);
12
13impl TradeId {
14    pub fn new<S: AsRef<str>>(id: S) -> Self {
15        Self(SmolStr::new(id))
16    }
17}
18
19#[derive(
20    Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Constructor,
21)]
22pub struct Trade<AssetKey, InstrumentKey> {
23    pub id: TradeId,
24    pub order_id: OrderId,
25    pub instrument: InstrumentKey,
26    pub strategy: StrategyId,
27    pub time_exchange: DateTime<Utc>,
28    pub side: Side,
29    pub price: Decimal,
30    pub quantity: Decimal,
31    pub fees: AssetFees<AssetKey>,
32}
33
34impl<AssetKey, InstrumentKey> Trade<AssetKey, InstrumentKey> {
35    pub fn value_quote(&self) -> Decimal {
36        self.price * self.quantity.abs()
37    }
38}
39
40impl<AssetKey, InstrumentKey> Display for Trade<AssetKey, InstrumentKey>
41where
42    AssetKey: Display,
43    InstrumentKey: Display,
44{
45    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46        write!(
47            f,
48            "{{ instrument: {}, side: {}, price: {}, quantity: {}, time: {} }}",
49            self.instrument, self.side, self.price, self.quantity, self.time_exchange
50        )
51    }
52}
53
54#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
55pub struct AssetFees<AssetKey> {
56    pub asset: AssetKey,
57    pub fees: Decimal,
58    /// Fee value in quote currency when computable.
59    /// - `Some(fees)` if fee asset == quote asset
60    /// - `Some(fees * price)` if fee asset == base asset (computed by indexer)
61    /// - `None` if fee asset is third-party (e.g., BNB) — requires external price data
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub fees_quote: Option<Decimal>,
64}
65
66impl<AssetKey> AssetFees<AssetKey> {
67    pub fn new(asset: AssetKey, fees: Decimal, fees_quote: Option<Decimal>) -> Self {
68        Self {
69            asset,
70            fees,
71            fees_quote,
72        }
73    }
74}
75
76impl AssetFees<QuoteAsset> {
77    /// Construct fees already denominated in quote asset.
78    /// Sets `fees_quote = Some(fees)` since no conversion needed.
79    pub fn quote_fees(fees: Decimal) -> Self {
80        Self {
81            asset: QuoteAsset,
82            fees,
83            fees_quote: Some(fees),
84        }
85    }
86}
87
88impl Default for AssetFees<QuoteAsset> {
89    fn default() -> Self {
90        Self {
91            asset: QuoteAsset,
92            fees: Decimal::ZERO,
93            fees_quote: Some(Decimal::ZERO),
94        }
95    }
96}
97
98impl<AssetKey> Default for AssetFees<Option<AssetKey>> {
99    fn default() -> Self {
100        Self {
101            asset: None,
102            fees: Decimal::ZERO,
103            fees_quote: None,
104        }
105    }
106}