Skip to main content

nautilus_model/events/order/spec/
filled.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
16use indexmap::IndexMap;
17use nautilus_core::{UUID4, UnixNanos};
18use ustr::Ustr;
19
20use crate::{
21    enums::{LiquiditySide, OrderSide, OrderType},
22    events::OrderFilled,
23    identifiers::{
24        AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
25        VenueOrderId,
26    },
27    stubs::{TestDefault, test_uuid},
28    types::{Currency, Money, Price, Quantity},
29};
30
31/// Test-only fluent spec for [`OrderFilled`].
32///
33/// All fields carry sensible defaults so callers only set what differs.
34/// `build()` constructs the event through [`OrderFilled::new`] so any future invariants
35/// added to the production constructor are exercised by tests built on this spec.
36#[derive(Debug, Clone, bon::Builder)]
37#[builder(finish_fn = into_spec)]
38pub struct OrderFilledSpec {
39    #[builder(default = TraderId::test_default())]
40    pub trader_id: TraderId,
41    #[builder(default = StrategyId::test_default())]
42    pub strategy_id: StrategyId,
43    #[builder(default = InstrumentId::test_default())]
44    pub instrument_id: InstrumentId,
45    #[builder(default = ClientOrderId::test_default())]
46    pub client_order_id: ClientOrderId,
47    #[builder(default = VenueOrderId::test_default())]
48    pub venue_order_id: VenueOrderId,
49    #[builder(default = AccountId::test_default())]
50    pub account_id: AccountId,
51    #[builder(default = TradeId::test_default())]
52    pub trade_id: TradeId,
53    #[builder(default = OrderSide::Buy)]
54    pub order_side: OrderSide,
55    #[builder(default = OrderType::Market)]
56    pub order_type: OrderType,
57    #[builder(default = Quantity::new(100_000.0, 0))]
58    pub last_qty: Quantity,
59    #[builder(default = Price::from("1.00000"))]
60    pub last_px: Price,
61    #[builder(default = Currency::USD())]
62    pub currency: Currency,
63    #[builder(default = LiquiditySide::Taker)]
64    pub liquidity_side: LiquiditySide,
65    #[builder(default = test_uuid())]
66    pub event_id: UUID4,
67    #[builder(default = UnixNanos::default())]
68    pub ts_event: UnixNanos,
69    #[builder(default = UnixNanos::default())]
70    pub ts_init: UnixNanos,
71    #[builder(default = false)]
72    pub reconciliation: bool,
73    pub position_id: Option<PositionId>,
74    pub commission: Option<Money>,
75    pub info: Option<IndexMap<Ustr, Ustr>>,
76}
77
78impl<S: order_filled_spec_builder::IsComplete> OrderFilledSpecBuilder<S> {
79    /// Builds the spec and constructs an [`OrderFilled`] through its production constructor.
80    #[must_use]
81    pub fn build(self) -> OrderFilled {
82        let spec = self.into_spec();
83        OrderFilled::new(
84            spec.trader_id,
85            spec.strategy_id,
86            spec.instrument_id,
87            spec.client_order_id,
88            spec.venue_order_id,
89            spec.account_id,
90            spec.trade_id,
91            spec.order_side,
92            spec.order_type,
93            spec.last_qty,
94            spec.last_px,
95            spec.currency,
96            spec.liquidity_side,
97            spec.event_id,
98            spec.ts_event,
99            spec.ts_init,
100            spec.reconciliation,
101            spec.position_id,
102            spec.commission,
103            spec.info,
104        )
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use rstest::rstest;
111
112    use super::*;
113    use crate::stubs::reset_test_uuid_rng;
114
115    #[rstest]
116    fn defaults_are_sensible() {
117        // Pin the spec's no-arg defaults so accidental drift in any individual default surfaces here,
118        // rather than as silent behavior change in downstream tests.
119        let order = OrderFilledSpec::builder().build();
120        assert_eq!(order.trader_id, TraderId::test_default());
121        assert_eq!(order.strategy_id, StrategyId::test_default());
122        assert_eq!(order.instrument_id, InstrumentId::test_default());
123        assert_eq!(order.client_order_id, ClientOrderId::test_default());
124        assert_eq!(order.venue_order_id, VenueOrderId::test_default());
125        assert_eq!(order.account_id, AccountId::test_default());
126        assert_eq!(order.trade_id, TradeId::test_default());
127        assert_eq!(order.order_side, OrderSide::Buy);
128        assert_eq!(order.order_type, OrderType::Market);
129        assert_eq!(order.last_qty, Quantity::new(100_000.0, 0));
130        assert_eq!(order.last_px, Price::from("1.00000"));
131        assert_eq!(order.currency, Currency::USD());
132        assert_eq!(order.liquidity_side, LiquiditySide::Taker);
133        assert_eq!(order.ts_event, UnixNanos::default());
134        assert_eq!(order.ts_init, UnixNanos::default());
135        assert!(!order.reconciliation);
136        assert_eq!(order.position_id, None);
137        assert_eq!(order.commission, None);
138    }
139
140    #[rstest]
141    fn overrides_apply_through_constructor() {
142        let order = OrderFilledSpec::builder()
143            .order_side(OrderSide::Sell)
144            .last_qty(Quantity::from("50"))
145            .last_px(Price::from("1.25000"))
146            .commission(Money::from("0.5 USD"))
147            .build();
148
149        assert_eq!(order.order_side, OrderSide::Sell);
150        assert_eq!(order.last_qty, Quantity::from("50"));
151        assert_eq!(order.last_px, Price::from("1.25000"));
152        assert_eq!(order.commission, Some(Money::from("0.5 USD")));
153        assert_eq!(order.trader_id, TraderId::test_default());
154    }
155
156    #[rstest]
157    fn event_ids_are_unique_within_a_run() {
158        reset_test_uuid_rng();
159        let a = OrderFilledSpec::builder().build();
160        let b = OrderFilledSpec::builder().build();
161        let c = OrderFilledSpec::builder().build();
162        assert_ne!(a.event_id, b.event_id);
163        assert_ne!(b.event_id, c.event_id);
164        assert_ne!(a.event_id, c.event_id);
165    }
166
167    #[rstest]
168    fn event_id_sequence_is_reproducible() {
169        // Reset before each draw so the comparison is run-order independent.
170        reset_test_uuid_rng();
171        let first_run: Vec<_> = (0..3)
172            .map(|_| OrderFilledSpec::builder().build().event_id)
173            .collect();
174
175        reset_test_uuid_rng();
176        let second_run: Vec<_> = (0..3)
177            .map(|_| OrderFilledSpec::builder().build().event_id)
178            .collect();
179
180        assert_eq!(first_run, second_run);
181    }
182}