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)]
137mod tests {
138 use nautilus_core::UnixNanos;
139 use rstest::*;
140
141 use super::*;
142 use crate::{
143 enums::{LiquiditySide, OrderSide},
144 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, VenueOrderId},
145 types::{Currency, Money, Price, Quantity},
146 };
147
148 fn test_fill_report() -> FillReport {
149 FillReport::new(
150 AccountId::from("SIM-001"),
151 InstrumentId::from("AUDUSD.SIM"),
152 VenueOrderId::from("1"),
153 TradeId::from("1"),
154 OrderSide::Buy,
155 Quantity::from("100"),
156 Price::from("0.80000"),
157 Money::new(5.0, Currency::USD()),
158 LiquiditySide::Taker,
159 Some(ClientOrderId::from("O-19700101-000000-001-001-1")),
160 Some(PositionId::from("P-001")),
161 UnixNanos::from(1_000_000_000),
162 UnixNanos::from(2_000_000_000),
163 None,
164 )
165 }
166
167 #[rstest]
168 fn test_fill_report_new() {
169 let report = test_fill_report();
170
171 assert_eq!(report.account_id, AccountId::from("SIM-001"));
172 assert_eq!(report.instrument_id, InstrumentId::from("AUDUSD.SIM"));
173 assert_eq!(report.venue_order_id, VenueOrderId::from("1"));
174 assert_eq!(report.trade_id, TradeId::from("1"));
175 assert_eq!(report.order_side, OrderSide::Buy);
176 assert_eq!(report.last_qty, Quantity::from("100"));
177 assert_eq!(report.last_px, Price::from("0.80000"));
178 assert_eq!(report.commission, Money::new(5.0, Currency::USD()));
179 assert_eq!(report.liquidity_side, LiquiditySide::Taker);
180 assert_eq!(
181 report.client_order_id,
182 Some(ClientOrderId::from("O-19700101-000000-001-001-1"))
183 );
184 assert_eq!(report.venue_position_id, Some(PositionId::from("P-001")));
185 assert_eq!(report.ts_event, UnixNanos::from(1_000_000_000));
186 assert_eq!(report.ts_init, UnixNanos::from(2_000_000_000));
187 }
188
189 #[rstest]
190 fn test_fill_report_new_with_generated_report_id() {
191 let report = FillReport::new(
192 AccountId::from("SIM-001"),
193 InstrumentId::from("AUDUSD.SIM"),
194 VenueOrderId::from("1"),
195 TradeId::from("1"),
196 OrderSide::Buy,
197 Quantity::from("100"),
198 Price::from("0.80000"),
199 Money::new(5.0, Currency::USD()),
200 LiquiditySide::Taker,
201 None,
202 None,
203 UnixNanos::from(1_000_000_000),
204 UnixNanos::from(2_000_000_000),
205 None, );
207
208 assert_ne!(
210 report.report_id.to_string(),
211 "00000000-0000-0000-0000-000000000000"
212 );
213 }
214
215 #[rstest]
216 fn test_has_client_order_id() {
217 let mut report = test_fill_report();
218 assert!(report.has_client_order_id());
219
220 report.client_order_id = None;
221 assert!(!report.has_client_order_id());
222 }
223
224 #[rstest]
225 fn test_has_venue_position_id() {
226 let mut report = test_fill_report();
227 assert!(report.has_venue_position_id());
228
229 report.venue_position_id = None;
230 assert!(!report.has_venue_position_id());
231 }
232
233 #[rstest]
234 fn test_display() {
235 let report = test_fill_report();
236 let display_str = format!("{report}");
237
238 assert!(display_str.contains("FillReport"));
239 assert!(display_str.contains("AUDUSD.SIM"));
240 assert!(display_str.contains("BUY"));
241 assert!(display_str.contains("100"));
242 assert!(display_str.contains("0.80000"));
243 assert!(display_str.contains("5.00 USD"));
244 assert!(display_str.contains("TAKER"));
245 }
246
247 #[rstest]
248 fn test_clone_and_equality() {
249 let report1 = test_fill_report();
250 let report2 = report1.clone();
251
252 assert_eq!(report1, report2);
253 }
254
255 #[rstest]
256 fn test_serialization_roundtrip() {
257 let original = test_fill_report();
258
259 let json = serde_json::to_string(&original).unwrap();
261 let deserialized: FillReport = serde_json::from_str(&json).unwrap();
262 assert_eq!(original, deserialized);
263 }
264
265 #[rstest]
266 fn test_fill_report_with_different_liquidity_sides() {
267 let maker_report = FillReport::new(
268 AccountId::from("SIM-001"),
269 InstrumentId::from("AUDUSD.SIM"),
270 VenueOrderId::from("1"),
271 TradeId::from("1"),
272 OrderSide::Buy,
273 Quantity::from("100"),
274 Price::from("0.80000"),
275 Money::new(2.0, Currency::USD()),
276 LiquiditySide::Maker,
277 None,
278 None,
279 UnixNanos::from(1_000_000_000),
280 UnixNanos::from(2_000_000_000),
281 None,
282 );
283
284 let taker_report = FillReport::new(
285 AccountId::from("SIM-001"),
286 InstrumentId::from("AUDUSD.SIM"),
287 VenueOrderId::from("2"),
288 TradeId::from("2"),
289 OrderSide::Sell,
290 Quantity::from("100"),
291 Price::from("0.80000"),
292 Money::new(5.0, Currency::USD()),
293 LiquiditySide::Taker,
294 None,
295 None,
296 UnixNanos::from(1_000_000_000),
297 UnixNanos::from(2_000_000_000),
298 None,
299 );
300
301 assert_eq!(maker_report.liquidity_side, LiquiditySide::Maker);
302 assert_eq!(taker_report.liquidity_side, LiquiditySide::Taker);
303 assert_ne!(maker_report, taker_report);
304 }
305
306 #[rstest]
307 fn test_fill_report_with_different_order_sides() {
308 let buy_report = FillReport::new(
309 AccountId::from("SIM-001"),
310 InstrumentId::from("AUDUSD.SIM"),
311 VenueOrderId::from("1"),
312 TradeId::from("1"),
313 OrderSide::Buy,
314 Quantity::from("100"),
315 Price::from("0.80000"),
316 Money::new(5.0, Currency::USD()),
317 LiquiditySide::Taker,
318 None,
319 None,
320 UnixNanos::from(1_000_000_000),
321 UnixNanos::from(2_000_000_000),
322 None,
323 );
324
325 let sell_report = FillReport::new(
326 AccountId::from("SIM-001"),
327 InstrumentId::from("AUDUSD.SIM"),
328 VenueOrderId::from("1"),
329 TradeId::from("1"),
330 OrderSide::Sell,
331 Quantity::from("100"),
332 Price::from("0.80000"),
333 Money::new(5.0, Currency::USD()),
334 LiquiditySide::Taker,
335 None,
336 None,
337 UnixNanos::from(1_000_000_000),
338 UnixNanos::from(2_000_000_000),
339 None,
340 );
341
342 assert_eq!(buy_report.order_side, OrderSide::Buy);
343 assert_eq!(sell_report.order_side, OrderSide::Sell);
344 assert_ne!(buy_report, sell_report);
345 }
346}