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}