Skip to main content

nautilus_plugin/surfaces/commands/
submit.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//! Submit commands and their boundary-owned handles.
17//!
18//! The plug-in constructs a [`SubmitOrderCommand`] or
19//! [`SubmitOrderListCommand`], wraps it in the matching `*Handle`, and
20//! hands the host a pointer via
21//! [`HostVTable::submit_order`](crate::host::HostVTable::submit_order) or
22//! [`HostVTable::submit_order_list`](crate::host::HostVTable::submit_order_list).
23//! The host derefs the handle once and routes the borrowed command into
24//! the calling strategy's submit path. The plug-in owns the box and frees
25//! it when the call returns.
26
27#![allow(unsafe_code)]
28
29use std::ops::Deref;
30
31use nautilus_core::Params;
32use nautilus_model::{
33    identifiers::{ClientId, PositionId},
34    orders::OrderAny,
35};
36
37/// Submit-order command. Mirrors the arguments to `Strategy::submit_order`.
38#[repr(C)]
39#[derive(Debug, Clone)]
40pub struct SubmitOrderCommand {
41    /// The order to submit.
42    pub order: OrderAny,
43
44    /// Optional position the order is associated with.
45    pub position_id: Option<PositionId>,
46
47    /// Optional client routing identifier.
48    pub client_id: Option<ClientId>,
49
50    /// Optional venue-specific parameters.
51    pub params: Option<Params>,
52}
53
54impl SubmitOrderCommand {
55    /// Creates a new [`SubmitOrderCommand`] instance.
56    #[must_use]
57    pub const fn new(
58        order: OrderAny,
59        position_id: Option<PositionId>,
60        client_id: Option<ClientId>,
61        params: Option<Params>,
62    ) -> Self {
63        Self {
64            order,
65            position_id,
66            client_id,
67            params,
68        }
69    }
70}
71
72/// Boundary-owned wrapper that lets [`SubmitOrderCommand`] cross the cdylib
73/// FFI boundary by reference.
74///
75/// `SubmitOrderCommand` carries an `OrderAny` whose variant payloads are
76/// heap-owned (e.g. tag vectors, exec-algorithm params), so the plug-in
77/// wraps the whole command in this `#[repr(C)]` handle and passes a
78/// borrowed pointer to the host. Equivalent layout on both sides relies
79/// on operator-side pinning (plug-in cdylibs rebuilt to match each
80/// Nautilus version); `PluginBuildId` is recorded for load diagnostics,
81/// and precision mode is enforced during manifest validation.
82#[repr(C)]
83#[derive(Debug, Clone)]
84pub struct SubmitOrderHandle(Box<SubmitOrderCommand>);
85
86impl SubmitOrderHandle {
87    /// Wraps `command` in a boundary-owned handle.
88    #[must_use]
89    pub fn new(command: SubmitOrderCommand) -> Self {
90        Self(Box::new(command))
91    }
92
93    /// Returns a reference to the wrapped command.
94    #[must_use]
95    pub fn command(&self) -> &SubmitOrderCommand {
96        &self.0
97    }
98
99    /// Consumes the wrapper and returns the inner command.
100    #[must_use]
101    pub fn into_inner(self) -> SubmitOrderCommand {
102        *self.0
103    }
104}
105
106impl Deref for SubmitOrderHandle {
107    type Target = SubmitOrderCommand;
108
109    fn deref(&self) -> &Self::Target {
110        &self.0
111    }
112}
113
114/// Submit-order-list command. Mirrors the arguments to `Strategy::submit_order_list`.
115#[repr(C)]
116#[derive(Debug, Clone)]
117pub struct SubmitOrderListCommand {
118    /// The orders to submit as a batched list.
119    pub orders: Vec<OrderAny>,
120
121    /// Optional position the orders are associated with.
122    pub position_id: Option<PositionId>,
123
124    /// Optional client routing identifier.
125    pub client_id: Option<ClientId>,
126
127    /// Optional venue-specific parameters.
128    pub params: Option<Params>,
129}
130
131impl SubmitOrderListCommand {
132    /// Creates a new [`SubmitOrderListCommand`] instance.
133    #[must_use]
134    pub const fn new(
135        orders: Vec<OrderAny>,
136        position_id: Option<PositionId>,
137        client_id: Option<ClientId>,
138        params: Option<Params>,
139    ) -> Self {
140        Self {
141            orders,
142            position_id,
143            client_id,
144            params,
145        }
146    }
147}
148
149/// Boundary-owned wrapper that lets [`SubmitOrderListCommand`] cross the
150/// cdylib FFI boundary by reference.
151///
152/// The `Vec<OrderAny>` payload is the largest of any execution command;
153/// the outer `Box` pins the Vec header and the heap allocation stays on
154/// the plug-in side for the duration of the call.
155#[repr(C)]
156#[derive(Debug, Clone)]
157pub struct SubmitOrderListHandle(Box<SubmitOrderListCommand>);
158
159impl SubmitOrderListHandle {
160    /// Wraps `command` in a boundary-owned handle.
161    #[must_use]
162    pub fn new(command: SubmitOrderListCommand) -> Self {
163        Self(Box::new(command))
164    }
165
166    /// Returns a reference to the wrapped command.
167    #[must_use]
168    pub fn command(&self) -> &SubmitOrderListCommand {
169        &self.0
170    }
171
172    /// Consumes the wrapper and returns the inner command.
173    #[must_use]
174    pub fn into_inner(self) -> SubmitOrderListCommand {
175        *self.0
176    }
177}
178
179impl Deref for SubmitOrderListHandle {
180    type Target = SubmitOrderListCommand;
181
182    fn deref(&self) -> &Self::Target {
183        &self.0
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use nautilus_core::{UUID4, UnixNanos};
190    use nautilus_model::{
191        enums::{OrderSide, TimeInForce},
192        identifiers::{ClientOrderId, InstrumentId, StrategyId, TraderId},
193        orders::{MarketOrder, Order},
194        types::Quantity,
195    };
196    use rstest::rstest;
197
198    use super::*;
199
200    fn market_order(client_order_id: &str) -> OrderAny {
201        OrderAny::Market(MarketOrder::new(
202            TraderId::from("TRADER-001"),
203            StrategyId::from("S-001"),
204            InstrumentId::from("ETH-USDT.BINANCE"),
205            ClientOrderId::from(client_order_id),
206            OrderSide::Buy,
207            Quantity::from("1.0"),
208            TimeInForce::Gtc,
209            UUID4::new(),
210            UnixNanos::default(),
211            false,
212            false,
213            None,
214            None,
215            None,
216            None,
217            None,
218            None,
219            None,
220            None,
221        ))
222    }
223
224    // SubmitOrderCommand carries an `OrderAny` which is not `Eq`, so identity is
225    // asserted via the underlying client_order_id rather than via `==` on the
226    // full struct.
227    #[rstest]
228    fn submit_order_handle_round_trips_command() {
229        let order = market_order("O-1");
230        let order_id = order.client_order_id();
231        let handle = SubmitOrderHandle::new(SubmitOrderCommand::new(order, None, None, None));
232        assert_eq!(handle.command().order.client_order_id(), order_id);
233        // Exercise the Deref impl explicitly so a regression in the Deref
234        // target binding (e.g. returning a default) fails the test.
235        assert_eq!(Deref::deref(&handle).order.client_order_id(), order_id,);
236        let recovered = handle.into_inner();
237        assert_eq!(recovered.order.client_order_id(), order_id);
238    }
239
240    #[rstest]
241    fn submit_order_list_handle_round_trips_command() {
242        let handle = SubmitOrderListHandle::new(SubmitOrderListCommand::new(
243            vec![market_order("O-1"), market_order("O-2")],
244            None,
245            None,
246            None,
247        ));
248        assert_eq!(handle.command().orders.len(), 2);
249        // Exercise the Deref impl explicitly.
250        assert_eq!(Deref::deref(&handle).orders.len(), 2);
251        let recovered = handle.into_inner();
252        assert_eq!(recovered.orders.len(), 2);
253        assert_eq!(
254            recovered.orders[0].client_order_id(),
255            ClientOrderId::from("O-1")
256        );
257    }
258}