1use std::fmt::Display;
17
18use nautilus_core::{UUID4, UnixNanos};
19use serde::{Deserialize, Serialize};
20
21use crate::{
22 enums::{LiquiditySide, OrderSide},
23 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, VenueOrderId},
24 types::{Money, Price, Quantity},
25};
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(tag = "type")]
30#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
33)]
34pub struct FillReport {
35 pub account_id: AccountId,
37 pub instrument_id: InstrumentId,
39 pub venue_order_id: VenueOrderId,
41 pub trade_id: TradeId,
43 pub order_side: OrderSide,
45 pub last_qty: Quantity,
47 pub last_px: Price,
49 pub commission: Money,
51 pub liquidity_side: LiquiditySide,
53 pub report_id: UUID4,
55 pub ts_event: UnixNanos,
57 pub ts_init: UnixNanos,
59 pub client_order_id: Option<ClientOrderId>,
61 pub venue_position_id: Option<PositionId>,
63}
64
65impl FillReport {
66 #[allow(clippy::too_many_arguments)]
68 #[must_use]
69 pub fn new(
70 account_id: AccountId,
71 instrument_id: InstrumentId,
72 venue_order_id: VenueOrderId,
73 trade_id: TradeId,
74 order_side: OrderSide,
75 last_qty: Quantity,
76 last_px: Price,
77 commission: Money,
78 liquidity_side: LiquiditySide,
79 client_order_id: Option<ClientOrderId>,
80 venue_position_id: Option<PositionId>,
81 ts_event: UnixNanos,
82 ts_init: UnixNanos,
83 report_id: Option<UUID4>,
84 ) -> Self {
85 Self {
86 account_id,
87 instrument_id,
88 venue_order_id,
89 trade_id,
90 order_side,
91 last_qty,
92 last_px,
93 commission,
94 liquidity_side,
95 report_id: report_id.unwrap_or_default(),
96 ts_event,
97 ts_init,
98 client_order_id,
99 venue_position_id,
100 }
101 }
102
103 #[must_use]
105 pub const fn has_client_order_id(&self) -> bool {
106 self.client_order_id.is_some()
107 }
108
109 #[must_use]
111 pub const fn has_venue_position_id(&self) -> bool {
112 self.venue_position_id.is_some()
113 }
114}
115
116impl Display for FillReport {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 write!(
119 f,
120 "FillReport(instrument={}, side={}, qty={}, last_px={}, trade_id={}, venue_order_id={}, commission={}, liquidity={})",
121 self.instrument_id,
122 self.order_side,
123 self.last_qty,
124 self.last_px,
125 self.trade_id,
126 self.venue_order_id,
127 self.commission,
128 self.liquidity_side,
129 )
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use nautilus_core::UnixNanos;
136 use rstest::*;
137
138 use super::*;
139 use crate::{
140 enums::{LiquiditySide, OrderSide},
141 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, VenueOrderId},
142 types::{Currency, Money, Price, Quantity},
143 };
144
145 fn test_fill_report() -> FillReport {
146 FillReport::new(
147 AccountId::from("SIM-001"),
148 InstrumentId::from("AUDUSD.SIM"),
149 VenueOrderId::from("1"),
150 TradeId::from("1"),
151 OrderSide::Buy,
152 Quantity::from("100"),
153 Price::from("0.80000"),
154 Money::new(5.0, Currency::USD()),
155 LiquiditySide::Taker,
156 Some(ClientOrderId::from("O-19700101-000000-001-001-1")),
157 Some(PositionId::from("P-001")),
158 UnixNanos::from(1_000_000_000),
159 UnixNanos::from(2_000_000_000),
160 None,
161 )
162 }
163
164 #[rstest]
165 fn test_fill_report_new() {
166 let report = test_fill_report();
167
168 assert_eq!(report.account_id, AccountId::from("SIM-001"));
169 assert_eq!(report.instrument_id, InstrumentId::from("AUDUSD.SIM"));
170 assert_eq!(report.venue_order_id, VenueOrderId::from("1"));
171 assert_eq!(report.trade_id, TradeId::from("1"));
172 assert_eq!(report.order_side, OrderSide::Buy);
173 assert_eq!(report.last_qty, Quantity::from("100"));
174 assert_eq!(report.last_px, Price::from("0.80000"));
175 assert_eq!(report.commission, Money::new(5.0, Currency::USD()));
176 assert_eq!(report.liquidity_side, LiquiditySide::Taker);
177 assert_eq!(
178 report.client_order_id,
179 Some(ClientOrderId::from("O-19700101-000000-001-001-1"))
180 );
181 assert_eq!(report.venue_position_id, Some(PositionId::from("P-001")));
182 assert_eq!(report.ts_event, UnixNanos::from(1_000_000_000));
183 assert_eq!(report.ts_init, UnixNanos::from(2_000_000_000));
184 }
185
186 #[rstest]
187 fn test_fill_report_new_with_generated_report_id() {
188 let report = FillReport::new(
189 AccountId::from("SIM-001"),
190 InstrumentId::from("AUDUSD.SIM"),
191 VenueOrderId::from("1"),
192 TradeId::from("1"),
193 OrderSide::Buy,
194 Quantity::from("100"),
195 Price::from("0.80000"),
196 Money::new(5.0, Currency::USD()),
197 LiquiditySide::Taker,
198 None,
199 None,
200 UnixNanos::from(1_000_000_000),
201 UnixNanos::from(2_000_000_000),
202 None, );
204
205 assert_ne!(
207 report.report_id.to_string(),
208 "00000000-0000-0000-0000-000000000000"
209 );
210 }
211
212 #[rstest]
213 fn test_has_client_order_id() {
214 let mut report = test_fill_report();
215 assert!(report.has_client_order_id());
216
217 report.client_order_id = None;
218 assert!(!report.has_client_order_id());
219 }
220
221 #[rstest]
222 fn test_has_venue_position_id() {
223 let mut report = test_fill_report();
224 assert!(report.has_venue_position_id());
225
226 report.venue_position_id = None;
227 assert!(!report.has_venue_position_id());
228 }
229
230 #[rstest]
231 fn test_display() {
232 let report = test_fill_report();
233 let display_str = format!("{report}");
234
235 assert!(display_str.contains("FillReport"));
236 assert!(display_str.contains("AUDUSD.SIM"));
237 assert!(display_str.contains("BUY"));
238 assert!(display_str.contains("100"));
239 assert!(display_str.contains("0.80000"));
240 assert!(display_str.contains("5.00 USD"));
241 assert!(display_str.contains("TAKER"));
242 }
243
244 #[rstest]
245 fn test_clone_and_equality() {
246 let report1 = test_fill_report();
247 let report2 = report1.clone();
248
249 assert_eq!(report1, report2);
250 }
251
252 #[rstest]
253 fn test_serialization_roundtrip() {
254 let original = test_fill_report();
255
256 let json = serde_json::to_string(&original).unwrap();
258 let deserialized: FillReport = serde_json::from_str(&json).unwrap();
259 assert_eq!(original, deserialized);
260 }
261
262 #[rstest]
263 fn test_fill_report_with_different_liquidity_sides() {
264 let maker_report = FillReport::new(
265 AccountId::from("SIM-001"),
266 InstrumentId::from("AUDUSD.SIM"),
267 VenueOrderId::from("1"),
268 TradeId::from("1"),
269 OrderSide::Buy,
270 Quantity::from("100"),
271 Price::from("0.80000"),
272 Money::new(2.0, Currency::USD()),
273 LiquiditySide::Maker,
274 None,
275 None,
276 UnixNanos::from(1_000_000_000),
277 UnixNanos::from(2_000_000_000),
278 None,
279 );
280
281 let taker_report = FillReport::new(
282 AccountId::from("SIM-001"),
283 InstrumentId::from("AUDUSD.SIM"),
284 VenueOrderId::from("2"),
285 TradeId::from("2"),
286 OrderSide::Sell,
287 Quantity::from("100"),
288 Price::from("0.80000"),
289 Money::new(5.0, Currency::USD()),
290 LiquiditySide::Taker,
291 None,
292 None,
293 UnixNanos::from(1_000_000_000),
294 UnixNanos::from(2_000_000_000),
295 None,
296 );
297
298 assert_eq!(maker_report.liquidity_side, LiquiditySide::Maker);
299 assert_eq!(taker_report.liquidity_side, LiquiditySide::Taker);
300 assert_ne!(maker_report, taker_report);
301 }
302
303 #[rstest]
304 fn test_fill_report_with_different_order_sides() {
305 let buy_report = FillReport::new(
306 AccountId::from("SIM-001"),
307 InstrumentId::from("AUDUSD.SIM"),
308 VenueOrderId::from("1"),
309 TradeId::from("1"),
310 OrderSide::Buy,
311 Quantity::from("100"),
312 Price::from("0.80000"),
313 Money::new(5.0, Currency::USD()),
314 LiquiditySide::Taker,
315 None,
316 None,
317 UnixNanos::from(1_000_000_000),
318 UnixNanos::from(2_000_000_000),
319 None,
320 );
321
322 let sell_report = FillReport::new(
323 AccountId::from("SIM-001"),
324 InstrumentId::from("AUDUSD.SIM"),
325 VenueOrderId::from("1"),
326 TradeId::from("1"),
327 OrderSide::Sell,
328 Quantity::from("100"),
329 Price::from("0.80000"),
330 Money::new(5.0, Currency::USD()),
331 LiquiditySide::Taker,
332 None,
333 None,
334 UnixNanos::from(1_000_000_000),
335 UnixNanos::from(2_000_000_000),
336 None,
337 );
338
339 assert_eq!(buy_report.order_side, OrderSide::Buy);
340 assert_eq!(sell_report.order_side, OrderSide::Sell);
341 assert_ne!(buy_report, sell_report);
342 }
343}