Skip to main content

perpl_sdk/types/
trade.rs

1use fastnum::UD64;
2
3use super::BuilderAttribution;
4
5/// A single maker fill within a taker trade.
6#[derive(Clone, derive_more::Debug)]
7pub struct MakerFill {
8    /// Log index of this maker fill event.
9    pub log_index: u64,
10
11    /// Maker account ID.
12    pub maker_account_id: super::AccountId,
13
14    /// Maker order ID.
15    pub maker_order_id: super::OrderId,
16
17    /// Maker client order ID, if known.
18    ///
19    /// Available only when the order placement was observed in processed
20    /// events, not for orders loaded from the initial snapshot.
21    pub maker_client_order_id: Option<super::RequestId>,
22
23    /// Fill price (normalized decimal).
24    #[debug("{price}")]
25    pub price: UD64,
26
27    /// Fill size (normalized decimal).
28    #[debug("{size}")]
29    pub size: UD64,
30
31    /// Maker fee paid (normalized decimal, in collateral token).
32    #[debug("{fee}")]
33    pub fee: UD64,
34
35    /// Builder the maker order is attributed to, with the fee rate it charges,
36    /// if any.
37    ///
38    /// Available only when the order placement was observed in processed events
39    /// or recovered from the initial snapshot, on contract v1.1.7.4+.
40    pub builder: Option<super::BuilderAttribution>,
41
42    /// Builder fee earned on this fill (normalized decimal, in collateral
43    /// token).
44    ///
45    /// Included in [`Self::fee`], so consumers must not add it on top. Zero on
46    /// close/decrease fills and on contracts without builder attribution, even
47    /// when [`Self::builder`] is set.
48    #[debug("{builder_fee}")]
49    pub builder_fee: UD64,
50}
51
52/// A complete trade event: one taker matched against one or more makers.
53///
54/// Each `TakerTrade` represents a single taker order execution that may have
55/// matched against multiple maker orders. The `maker_fills` vector contains
56/// all individual maker fills that occurred as part of this trade.
57#[derive(Clone, derive_more::Debug)]
58pub struct Trade {
59    /// Perpetual contract ID.
60    pub perpetual_id: super::PerpetualId,
61
62    /// Taker account ID.
63    pub taker_account_id: super::AccountId,
64
65    /// Taker request ID.
66    pub taker_request_id: super::RequestId,
67
68    /// Taker side (Bid = buying, Ask = selling).
69    pub taker_side: super::OrderSide,
70
71    /// Taker fee paid (normalized decimal, in collateral token).
72    #[debug("{taker_fee}")]
73    pub taker_fee: UD64,
74
75    /// Builder the taker order is attributed to, with the fee rate it charges,
76    /// if any.
77    pub taker_builder: Option<BuilderAttribution>,
78
79    /// Builder fee earned on the taker side (normalized decimal, in collateral
80    /// token).
81    ///
82    /// Included in [`Self::taker_fee`], so consumers must not add it on top.
83    #[debug("{taker_builder_fee}")]
84    pub taker_builder_fee: UD64,
85
86    /// All maker fills matched by this taker order.
87    pub maker_fills: Vec<MakerFill>,
88}
89
90impl Trade {
91    /// Total size filled across all makers.
92    pub fn total_size(&self) -> UD64 { self.maker_fills.iter().map(|f| f.size).sum() }
93
94    /// Volume-weighted average price across all maker fills.
95    ///
96    /// Returns `None` if there are no fills.
97    pub fn avg_price(&self) -> Option<UD64> {
98        if self.maker_fills.is_empty() {
99            return None;
100        }
101        let total_value: UD64 = self.maker_fills.iter().map(|f| f.price * f.size).sum();
102        let total_size = self.total_size();
103        if total_size == UD64::ZERO {
104            return None;
105        }
106        Some(total_value / total_size)
107    }
108
109    /// Total maker fees paid across all fills.
110    pub fn total_maker_fees(&self) -> UD64 { self.maker_fills.iter().map(|f| f.fee).sum() }
111
112    /// Total builder fees earned on this trade, taker and maker sides combined.
113    ///
114    /// Part of [`Self::taker_fee`] and the maker fees, not additional to them.
115    pub fn total_builder_fees(&self) -> UD64 {
116        self.taker_builder_fee + self.maker_fills.iter().map(|f| f.builder_fee).sum::<UD64>()
117    }
118
119    /// Total builder fees earned by a specific builder on this trade.
120    pub fn builder_total(&self, builder_id: super::BuilderId) -> UD64 {
121        let taker = self
122            .taker_builder
123            .filter(|b| b.builder_id() == builder_id)
124            .map(|_| self.taker_builder_fee)
125            .unwrap_or(UD64::ZERO);
126        taker
127            + self
128                .maker_fills
129                .iter()
130                .filter(|f| f.builder.is_some_and(|b| b.builder_id() == builder_id))
131                .map(|f| f.builder_fee)
132                .sum::<UD64>()
133    }
134
135    /// Volume-weighted average price, total size and total fees for a specific
136    /// maker.
137    ///
138    /// Returns `None` if the maker has no fills in this trade.
139    pub fn maker_total(&self, account_id: super::AccountId) -> Option<(UD64, UD64, UD64)> {
140        let mut total_value = UD64::ZERO;
141        let mut total_size = UD64::ZERO;
142        let mut total_fee = UD64::ZERO;
143        for fill in self
144            .maker_fills
145            .iter()
146            .filter(|f| f.maker_account_id == account_id)
147        {
148            total_value += fill.price * fill.size;
149            total_size += fill.size;
150            total_fee += fill.fee
151        }
152        if total_size == UD64::ZERO {
153            return None;
154        }
155        Some((total_value / total_size, total_size, total_fee))
156    }
157}