Skip to main content

nautilus_plugin/surfaces/commands/
cancel.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//! Cancel commands and their boundary-owned handles.
17//!
18//! The plug-in constructs a [`CancelOrderCommand`], [`CancelOrdersCommand`],
19//! or [`CancelAllOrdersCommand`], wraps it in the matching `*Handle`, and
20//! hands the host a `*const XHandle` via the corresponding `HostVTable`
21//! slot. The host derefs the handle once and routes the borrowed command
22//! into the calling strategy's cancel path. The plug-in owns the box and
23//! frees it when the call returns.
24
25#![allow(unsafe_code)]
26
27use std::ops::Deref;
28
29use nautilus_core::Params;
30use nautilus_model::{
31    enums::OrderSide,
32    identifiers::{ClientId, ClientOrderId, InstrumentId},
33};
34
35/// Cancel-order command. Mirrors the arguments to `Strategy::cancel_order`.
36#[repr(C)]
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct CancelOrderCommand {
39    /// The client order identifier of the order to cancel.
40    pub client_order_id: ClientOrderId,
41
42    /// Optional client routing identifier.
43    pub client_id: Option<ClientId>,
44
45    /// Optional venue-specific parameters.
46    pub params: Option<Params>,
47}
48
49impl CancelOrderCommand {
50    /// Creates a new [`CancelOrderCommand`] instance.
51    #[must_use]
52    pub const fn new(
53        client_order_id: ClientOrderId,
54        client_id: Option<ClientId>,
55        params: Option<Params>,
56    ) -> Self {
57        Self {
58            client_order_id,
59            client_id,
60            params,
61        }
62    }
63}
64
65/// Boundary-owned wrapper that lets [`CancelOrderCommand`] cross the cdylib
66/// FFI boundary by reference.
67///
68/// The plug-in constructs an instance, hands a
69/// `*const CancelOrderHandle` to the host for the duration of the
70/// `cancel_order` call, and drops the handle when the call returns. The
71/// host only borrows the handle and never owns it.
72#[repr(C)]
73#[derive(Debug, Clone)]
74pub struct CancelOrderHandle(Box<CancelOrderCommand>);
75
76impl CancelOrderHandle {
77    /// Wraps `command` in a boundary-owned handle.
78    #[must_use]
79    pub fn new(command: CancelOrderCommand) -> Self {
80        Self(Box::new(command))
81    }
82
83    /// Returns a reference to the wrapped command.
84    #[must_use]
85    pub fn command(&self) -> &CancelOrderCommand {
86        &self.0
87    }
88
89    /// Consumes the wrapper and returns the inner command.
90    #[must_use]
91    pub fn into_inner(self) -> CancelOrderCommand {
92        *self.0
93    }
94}
95
96impl Deref for CancelOrderHandle {
97    type Target = CancelOrderCommand;
98
99    fn deref(&self) -> &Self::Target {
100        &self.0
101    }
102}
103
104/// Cancel-orders (batched) command. Mirrors the arguments to `Strategy::cancel_orders`.
105#[repr(C)]
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct CancelOrdersCommand {
108    /// The client order identifiers of the orders to cancel.
109    pub client_order_ids: Vec<ClientOrderId>,
110
111    /// Optional client routing identifier.
112    pub client_id: Option<ClientId>,
113
114    /// Optional venue-specific parameters.
115    pub params: Option<Params>,
116}
117
118impl CancelOrdersCommand {
119    /// Creates a new [`CancelOrdersCommand`] instance.
120    #[must_use]
121    pub const fn new(
122        client_order_ids: Vec<ClientOrderId>,
123        client_id: Option<ClientId>,
124        params: Option<Params>,
125    ) -> Self {
126        Self {
127            client_order_ids,
128            client_id,
129            params,
130        }
131    }
132}
133
134/// Boundary-owned wrapper that lets [`CancelOrdersCommand`] cross the cdylib
135/// FFI boundary by reference.
136#[repr(C)]
137#[derive(Debug, Clone)]
138pub struct CancelOrdersHandle(Box<CancelOrdersCommand>);
139
140impl CancelOrdersHandle {
141    /// Wraps `command` in a boundary-owned handle.
142    #[must_use]
143    pub fn new(command: CancelOrdersCommand) -> Self {
144        Self(Box::new(command))
145    }
146
147    /// Returns a reference to the wrapped command.
148    #[must_use]
149    pub fn command(&self) -> &CancelOrdersCommand {
150        &self.0
151    }
152
153    /// Consumes the wrapper and returns the inner command.
154    #[must_use]
155    pub fn into_inner(self) -> CancelOrdersCommand {
156        *self.0
157    }
158}
159
160impl Deref for CancelOrdersHandle {
161    type Target = CancelOrdersCommand;
162
163    fn deref(&self) -> &Self::Target {
164        &self.0
165    }
166}
167
168/// Cancel-all-orders command. Mirrors the arguments to `Strategy::cancel_all_orders`.
169#[repr(C)]
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct CancelAllOrdersCommand {
172    /// The instrument identifier filtering which orders to cancel.
173    pub instrument_id: InstrumentId,
174
175    /// Optional order side filter.
176    pub order_side: Option<OrderSide>,
177
178    /// Optional client routing identifier.
179    pub client_id: Option<ClientId>,
180
181    /// Optional venue-specific parameters.
182    pub params: Option<Params>,
183}
184
185impl CancelAllOrdersCommand {
186    /// Creates a new [`CancelAllOrdersCommand`] instance.
187    #[must_use]
188    pub const fn new(
189        instrument_id: InstrumentId,
190        order_side: Option<OrderSide>,
191        client_id: Option<ClientId>,
192        params: Option<Params>,
193    ) -> Self {
194        Self {
195            instrument_id,
196            order_side,
197            client_id,
198            params,
199        }
200    }
201}
202
203/// Boundary-owned wrapper that lets [`CancelAllOrdersCommand`] cross the
204/// cdylib FFI boundary by reference.
205#[repr(C)]
206#[derive(Debug, Clone)]
207pub struct CancelAllOrdersHandle(Box<CancelAllOrdersCommand>);
208
209impl CancelAllOrdersHandle {
210    /// Wraps `command` in a boundary-owned handle.
211    #[must_use]
212    pub fn new(command: CancelAllOrdersCommand) -> Self {
213        Self(Box::new(command))
214    }
215
216    /// Returns a reference to the wrapped command.
217    #[must_use]
218    pub fn command(&self) -> &CancelAllOrdersCommand {
219        &self.0
220    }
221
222    /// Consumes the wrapper and returns the inner command.
223    #[must_use]
224    pub fn into_inner(self) -> CancelAllOrdersCommand {
225        *self.0
226    }
227}
228
229impl Deref for CancelAllOrdersHandle {
230    type Target = CancelAllOrdersCommand;
231
232    fn deref(&self) -> &Self::Target {
233        &self.0
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use nautilus_model::{
240        enums::OrderSide,
241        identifiers::{ClientOrderId, InstrumentId},
242    };
243    use rstest::rstest;
244
245    use super::*;
246
247    #[rstest]
248    fn cancel_order_handle_round_trips_command() {
249        let cmd = CancelOrderCommand::new(ClientOrderId::from("O-1"), None, None);
250        let handle = CancelOrderHandle::new(cmd.clone());
251        assert_eq!(handle.command(), &cmd);
252        assert_eq!(&*handle, &cmd);
253        assert_eq!(handle.into_inner(), cmd);
254    }
255
256    #[rstest]
257    fn cancel_orders_handle_round_trips_command() {
258        let cmd = CancelOrdersCommand::new(
259            vec![ClientOrderId::from("O-1"), ClientOrderId::from("O-2")],
260            None,
261            None,
262        );
263        let handle = CancelOrdersHandle::new(cmd.clone());
264        assert_eq!(handle.command(), &cmd);
265        assert_eq!(&*handle, &cmd);
266        assert_eq!(handle.into_inner(), cmd);
267    }
268
269    #[rstest]
270    fn cancel_all_orders_handle_round_trips_command() {
271        let cmd = CancelAllOrdersCommand::new(
272            InstrumentId::from("ETH-USDT.BINANCE"),
273            Some(OrderSide::Buy),
274            None,
275            None,
276        );
277        let handle = CancelAllOrdersHandle::new(cmd.clone());
278        assert_eq!(handle.command(), &cmd);
279        assert_eq!(&*handle, &cmd);
280        assert_eq!(handle.into_inner(), cmd);
281    }
282}