Skip to main content

nautilus_live/execution/
reports.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Adapter-side filtering of venue order reports by query status and time bounds.
17//!
18//! Filtering shapes the response to a request; cache comparison and reconciliation decisions
19//! belong to [`super::manager`].
20
21use nautilus_common::messages::execution::GenerateOrderStatusReports;
22use nautilus_model::reports::OrderStatusReport;
23
24/// Retains order status reports matching the command's status and time filters.
25///
26/// Open-only requests include both open and in-flight reports. The inclusive `start` and `end`
27/// bounds apply only to closed reports.
28pub fn retain_order_status_reports(
29    reports: &mut Vec<OrderStatusReport>,
30    command: &GenerateOrderStatusReports,
31) {
32    reports.retain(|report| {
33        let status = report.order_status;
34        let matches_open = !command.open_only || status.is_open() || status.is_inflight();
35        let matches_time = !status.is_closed()
36            || (command.start.is_none_or(|start| report.ts_last >= start)
37                && command.end.is_none_or(|end| report.ts_last <= end));
38
39        matches_open && matches_time
40    });
41}
42
43#[cfg(test)]
44mod tests {
45    use nautilus_common::messages::execution::GenerateOrderStatusReports;
46    use nautilus_core::{UUID4, UnixNanos};
47    use nautilus_model::{
48        enums::{OrderSide, OrderStatus, OrderType, TimeInForce},
49        identifiers::{AccountId, InstrumentId, VenueOrderId},
50        reports::OrderStatusReport,
51        types::Quantity,
52    };
53    use rstest::rstest;
54
55    use super::retain_order_status_reports;
56
57    #[rstest]
58    #[case::accepted(OrderStatus::Accepted, true)]
59    #[case::submitted(OrderStatus::Submitted, true)]
60    #[case::initialized(OrderStatus::Initialized, false)]
61    #[case::emulated(OrderStatus::Emulated, false)]
62    #[case::released(OrderStatus::Released, false)]
63    #[case::filled(OrderStatus::Filled, false)]
64    fn test_retain_order_status_reports_filters_open_and_inflight(
65        #[case] status: OrderStatus,
66        #[case] expected_retained: bool,
67    ) {
68        let command = order_status_reports_command(true, None, None);
69        let mut reports = vec![order_status_report(status, UnixNanos::from(5))];
70
71        retain_order_status_reports(&mut reports, &command);
72
73        assert_eq!(reports.len(), usize::from(expected_retained));
74    }
75
76    #[rstest]
77    #[case::local_before(OrderStatus::Initialized, 9, true)]
78    #[case::open_before(OrderStatus::Accepted, 9, true)]
79    #[case::inflight_after(OrderStatus::Submitted, 21, true)]
80    #[case::closed_before(OrderStatus::Filled, 9, false)]
81    #[case::closed_at_start(OrderStatus::Filled, 10, true)]
82    #[case::closed_at_end(OrderStatus::Filled, 20, true)]
83    #[case::closed_after(OrderStatus::Filled, 21, false)]
84    fn test_retain_order_status_reports_filters_only_closed_reports_by_time(
85        #[case] status: OrderStatus,
86        #[case] ts_last: u64,
87        #[case] expected_retained: bool,
88    ) {
89        let command = order_status_reports_command(
90            false,
91            Some(UnixNanos::from(10)),
92            Some(UnixNanos::from(20)),
93        );
94        let mut reports = vec![order_status_report(status, UnixNanos::from(ts_last))];
95
96        retain_order_status_reports(&mut reports, &command);
97
98        assert_eq!(reports.len(), usize::from(expected_retained));
99    }
100
101    fn order_status_reports_command(
102        open_only: bool,
103        start: Option<UnixNanos>,
104        end: Option<UnixNanos>,
105    ) -> GenerateOrderStatusReports {
106        GenerateOrderStatusReports::new(
107            UUID4::new(),
108            UnixNanos::default(),
109            open_only,
110            None,
111            start,
112            end,
113            None,
114            None,
115        )
116    }
117
118    fn order_status_report(status: OrderStatus, ts_last: UnixNanos) -> OrderStatusReport {
119        OrderStatusReport::new(
120            AccountId::from("SIM-001"),
121            InstrumentId::from("AUD/USD.SIM"),
122            None,
123            VenueOrderId::from("ORDER-001"),
124            Some(OrderSide::Buy),
125            OrderType::Limit,
126            TimeInForce::Gtc,
127            status,
128            Quantity::from("1"),
129            Quantity::zero(0),
130            UnixNanos::from(1),
131            ts_last,
132            UnixNanos::from(2),
133            None,
134        )
135    }
136}