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    /// contracts without builder attribution, and on close/decrease fills
47    /// before contract v1.1.7.5 (which charges every size-changing fill,
48    /// exits included) - even when [`Self::builder`] is set.
49    #[debug("{builder_fee}")]
50    pub builder_fee: UD64,
51}
52
53/// A complete trade event: one taker matched against one or more makers.
54///
55/// Each `TakerTrade` represents a single taker order execution that may have
56/// matched against multiple maker orders. The `maker_fills` vector contains
57/// all individual maker fills that occurred as part of this trade.
58#[derive(Clone, derive_more::Debug)]
59pub struct Trade {
60    /// Perpetual contract ID.
61    pub perpetual_id: super::PerpetualId,
62
63    /// Taker account ID.
64    pub taker_account_id: super::AccountId,
65
66    /// Taker request ID.
67    pub taker_request_id: super::RequestId,
68
69    /// Taker side (Bid = buying, Ask = selling).
70    pub taker_side: super::OrderSide,
71
72    /// Taker fee paid (normalized decimal, in collateral token).
73    #[debug("{taker_fee}")]
74    pub taker_fee: UD64,
75
76    /// Builder the taker order is attributed to, with the fee rate it charges,
77    /// if any.
78    pub taker_builder: Option<BuilderAttribution>,
79
80    /// Builder fee earned on the taker side (normalized decimal, in collateral
81    /// token).
82    ///
83    /// Included in [`Self::taker_fee`], so consumers must not add it on top.
84    #[debug("{taker_builder_fee}")]
85    pub taker_builder_fee: UD64,
86
87    /// All maker fills matched by this taker order.
88    pub maker_fills: Vec<MakerFill>,
89}
90
91impl Trade {
92    /// Total size filled across all makers.
93    pub fn total_size(&self) -> UD64 { self.maker_fills.iter().map(|f| f.size).sum() }
94
95    /// Volume-weighted average price across all maker fills.
96    ///
97    /// Returns `None` if there are no fills.
98    pub fn avg_price(&self) -> Option<UD64> {
99        if self.maker_fills.is_empty() {
100            return None;
101        }
102        let total_value: UD64 = self.maker_fills.iter().map(|f| f.price * f.size).sum();
103        let total_size = self.total_size();
104        if total_size == UD64::ZERO {
105            return None;
106        }
107        Some(total_value / total_size)
108    }
109
110    /// Total maker fees paid across all fills.
111    pub fn total_maker_fees(&self) -> UD64 { self.maker_fills.iter().map(|f| f.fee).sum() }
112
113    /// Total builder fees earned on this trade, taker and maker sides combined.
114    ///
115    /// Part of [`Self::taker_fee`] and the maker fees, not additional to them.
116    pub fn total_builder_fees(&self) -> UD64 {
117        self.taker_builder_fee + self.maker_fills.iter().map(|f| f.builder_fee).sum::<UD64>()
118    }
119
120    /// Total builder fees earned by a specific builder on this trade.
121    pub fn builder_total(&self, builder_id: super::BuilderId) -> UD64 {
122        let taker = self
123            .taker_builder
124            .filter(|b| b.builder_id() == builder_id)
125            .map(|_| self.taker_builder_fee)
126            .unwrap_or(UD64::ZERO);
127        taker
128            + self
129                .maker_fills
130                .iter()
131                .filter(|f| f.builder.is_some_and(|b| b.builder_id() == builder_id))
132                .map(|f| f.builder_fee)
133                .sum::<UD64>()
134    }
135
136    /// Volume-weighted average price, total size and total fees for a specific
137    /// maker.
138    ///
139    /// Returns `None` if the maker has no fills in this trade.
140    pub fn maker_total(&self, account_id: super::AccountId) -> Option<(UD64, UD64, UD64)> {
141        let mut total_value = UD64::ZERO;
142        let mut total_size = UD64::ZERO;
143        let mut total_fee = UD64::ZERO;
144        for fill in self
145            .maker_fills
146            .iter()
147            .filter(|f| f.maker_account_id == account_id)
148        {
149            total_value += fill.price * fill.size;
150            total_size += fill.size;
151            total_fee += fill.fee
152        }
153        if total_size == UD64::ZERO {
154            return None;
155        }
156        Some((total_value / total_size, total_size, total_fee))
157    }
158}