Skip to main content

nautilus_plugin/surfaces/
strategy.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//! Strategy plug point.
17//!
18//! Strategies extend the [`crate::surfaces::actor::PluginActor`] surface with
19//! position events, order lifecycle events, and an order-command surface
20//! that calls back into the host through [`crate::HostVTable`].
21//!
22//! # Boundary shape
23//!
24//! Data-bearing callbacks cross the boundary as `*const T` borrowed
25//! pointers into the host's already-`#[repr(C)]` model types. The host
26//! keeps the value alive for the duration of the call; the plug-in's
27//! thunk dereferences once and hands an `&T` to the trait method. No
28//! serialisation per event.
29//!
30//! Order commands go the other direction as boundary-owned handles. The
31//! plug-in constructs a command struct under
32//! [`crate::surfaces::commands`], wraps it in the matching `*Handle`,
33//! and hands the host a `*const XHandle`. The host derefs the handle
34//! once and routes the borrowed command into the calling strategy. The
35//! host attributes each command to the calling strategy via the
36//! [`HostContext`] pointer that was supplied at `create`. No JSON
37//! crosses the boundary for any per-call command path.
38//!
39//! # Scope (v1)
40//!
41//! Phase 1 covers the full Phase 1 actor callback set plus:
42//!
43//! - Custom data: values registered through `PluginCustomData`, including
44//!   historical custom-data responses routed through `on_data`.
45//! - Position events: opened, closed, changed.
46//! - Order lifecycle events: initialized, submitted, accepted, rejected,
47//!   expired, triggered, denied, emulated, released, pending update,
48//!   pending cancel, modify rejected, cancel rejected, updated.
49//! - Market exit lifecycle: `on_market_exit`.
50//! - Historical market data: bulk historical book deltas, book depth,
51//!   quotes, trades, bars, mark prices, index prices, funding rates
52//!   delivered as [`crate::boundary::Slice`] payloads.
53//! - Order commands via [`HostVTable`]: `submit_order`, `cancel_order`,
54//!   `modify_order`, `submit_order_list`, `cancel_orders`,
55//!   `cancel_all_orders`, `close_position`, `close_all_positions`,
56//!   `query_account`, `query_order`. Each slot takes a boundary-owned
57//!   `*const XHandle` and the live adapter routes the borrowed command
58//!   into the matching `Strategy::*` call so the production cache /
59//!   risk / event pipeline runs unchanged; `close_position` and
60//!   `query_order` resolve their `&Position` / `&OrderAny` arguments
61//!   via the host cache before dispatch.
62//!
63//! Order books and book deltas cross as
64//! [`*const OrderBookHandle`](crate::surfaces::book::OrderBookHandle) and
65//! [`*const OrderBookDeltasHandle`](crate::surfaces::book::OrderBookDeltasHandle)
66//! since [`OrderBook`] and [`OrderBookDeltas`] own Rust collection state
67//! and cannot be `#[repr(C)]`. The host wraps each payload in a handle
68//! for the duration of the call. Instruments cross as
69//! [`*const InstrumentAnyHandle`](crate::surfaces::instrument::InstrumentAnyHandle)
70//! for the same reason: [`InstrumentAny`] is a non-`#[repr(C)]` enum
71//! whose variants own heap-allocated fields. Option chain snapshots
72//! cross as
73//! [`*const OptionChainSliceHandle`](crate::surfaces::option_chain::OptionChainSliceHandle)
74//! because [`OptionChainSlice`] owns `BTreeMap<Price, OptionStrikeData>`
75//! call and put maps.
76//!
77//! Deferred: generic non-plugin `on_historical_data` (`&dyn Any` payload),
78//! DeFi pool/block events, and the cache-state-mutation methods
79//! (`mark_order_pending_*`, `generate_order_pending_*`). The authoritative
80//! list lives in `tests/surface_alignment.rs`.
81
82#![allow(unsafe_code)]
83
84use std::marker::PhantomData;
85
86use nautilus_common::{signal::Signal, timer::TimeEvent};
87use nautilus_model::{
88    data::{
89        Bar, FundingRateUpdate, IndexPriceUpdate, InstrumentClose, InstrumentStatus,
90        MarkPriceUpdate, OptionChainSlice, OptionGreeks, OrderBookDelta, OrderBookDeltas,
91        OrderBookDepth10, QuoteTick, TradeTick,
92    },
93    events::{
94        OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
95        OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected, OrderPendingCancel,
96        OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted, OrderTriggered,
97        OrderUpdated, PositionChanged, PositionClosed, PositionOpened,
98    },
99    instruments::InstrumentAny,
100    orderbook::OrderBook,
101};
102
103use crate::{
104    boundary::{BorrowedStr, PluginError, PluginErrorCode, PluginResult, Slice},
105    host::{HostContext, HostVTable},
106    normalize::BoundaryNormalize,
107    panic::{guard, guard_infallible},
108    surfaces::{
109        book::{OrderBookDeltasHandle, OrderBookHandle},
110        custom_data::PluginCustomDataRef,
111        instrument::InstrumentAnyHandle,
112        option_chain::OptionChainSliceHandle,
113    },
114};
115
116/// Opaque handle to a plug-in strategy instance owned by the cdylib.
117#[repr(C)]
118pub struct PluginStrategyHandle {
119    _opaque: [u8; 0],
120}
121
122/// Function table for a single plug-in strategy type.
123///
124/// One static vtable per concrete type, generated by
125/// [`nautilus_plugin!`](crate::nautilus_plugin) via the same `Tag<T>` pattern
126/// that powers
127/// [`actor_vtable`](crate::surfaces::actor::actor_vtable) and
128/// [`custom_data_vtable`](crate::surfaces::custom_data::custom_data_vtable).
129///
130/// The strategy duplicates the actor callback set directly rather than
131/// embedding an `ActorVTable` so dispatch stays a single function-pointer
132/// call per event and so the layout is independent of any future actor
133/// vtable changes.
134///
135/// Slots are nullable at the ABI type level so the host can reject malformed
136/// manifests with null callbacks before constructing a strategy. Macro-generated
137/// vtables fill every required slot.
138#[repr(C)]
139pub struct StrategyVTable {
140    /// Constructs a fresh strategy instance bound to the supplied host
141    /// vtable and instance context, and returns a non-null handle. The
142    /// strategy stashes both pointers so it can route order commands
143    /// (`submit_order`, `cancel_order`, `modify_order`) back through the
144    /// host. The host uses the context to attribute each command.
145    ///
146    /// `config_json` carries the per-instance configuration the host
147    /// constructed from the user's TOML or builder API. Empty when the
148    /// host has no instance-specific configuration to pass.
149    pub create: Option<
150        unsafe extern "C" fn(
151            host: *const HostVTable,
152            ctx: *const HostContext,
153            config_json: BorrowedStr<'_>,
154        ) -> *mut PluginStrategyHandle,
155    >,
156
157    /// Drops the strategy instance and releases all of its resources.
158    pub drop_handle: Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle)>,
159
160    /// Returns the canonical type name for this strategy.
161    pub type_name: Option<unsafe extern "C" fn() -> BorrowedStr<'static>>,
162
163    pub on_start:
164        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
165    pub on_stop:
166        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
167    pub on_resume:
168        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
169    pub on_reset:
170        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
171    pub on_dispose:
172        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
173    pub on_degrade:
174        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
175    pub on_fault:
176        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
177
178    pub on_time_event: Option<
179        unsafe extern "C" fn(
180            handle: *mut PluginStrategyHandle,
181            event: *const TimeEvent,
182        ) -> PluginResult<()>,
183    >,
184    pub on_data: Option<
185        unsafe extern "C" fn(
186            handle: *mut PluginStrategyHandle,
187            data: PluginCustomDataRef,
188        ) -> PluginResult<()>,
189    >,
190
191    pub on_instrument: Option<
192        unsafe extern "C" fn(
193            handle: *mut PluginStrategyHandle,
194            instrument: *const InstrumentAnyHandle,
195        ) -> PluginResult<()>,
196    >,
197    pub on_book_deltas: Option<
198        unsafe extern "C" fn(
199            handle: *mut PluginStrategyHandle,
200            deltas: *const OrderBookDeltasHandle,
201        ) -> PluginResult<()>,
202    >,
203    pub on_book: Option<
204        unsafe extern "C" fn(
205            handle: *mut PluginStrategyHandle,
206            book: *const OrderBookHandle,
207        ) -> PluginResult<()>,
208    >,
209    pub on_quote: Option<
210        unsafe extern "C" fn(
211            handle: *mut PluginStrategyHandle,
212            quote: *const QuoteTick,
213        ) -> PluginResult<()>,
214    >,
215    pub on_trade: Option<
216        unsafe extern "C" fn(
217            handle: *mut PluginStrategyHandle,
218            trade: *const TradeTick,
219        ) -> PluginResult<()>,
220    >,
221    pub on_bar: Option<
222        unsafe extern "C" fn(
223            handle: *mut PluginStrategyHandle,
224            bar: *const Bar,
225        ) -> PluginResult<()>,
226    >,
227    pub on_mark_price: Option<
228        unsafe extern "C" fn(
229            handle: *mut PluginStrategyHandle,
230            mark_price: *const MarkPriceUpdate,
231        ) -> PluginResult<()>,
232    >,
233    pub on_index_price: Option<
234        unsafe extern "C" fn(
235            handle: *mut PluginStrategyHandle,
236            index_price: *const IndexPriceUpdate,
237        ) -> PluginResult<()>,
238    >,
239    pub on_funding_rate: Option<
240        unsafe extern "C" fn(
241            handle: *mut PluginStrategyHandle,
242            funding_rate: *const FundingRateUpdate,
243        ) -> PluginResult<()>,
244    >,
245    pub on_option_greeks: Option<
246        unsafe extern "C" fn(
247            handle: *mut PluginStrategyHandle,
248            greeks: *const OptionGreeks,
249        ) -> PluginResult<()>,
250    >,
251    pub on_option_chain: Option<
252        unsafe extern "C" fn(
253            handle: *mut PluginStrategyHandle,
254            chain: *const OptionChainSliceHandle,
255        ) -> PluginResult<()>,
256    >,
257    pub on_instrument_status: Option<
258        unsafe extern "C" fn(
259            handle: *mut PluginStrategyHandle,
260            status: *const InstrumentStatus,
261        ) -> PluginResult<()>,
262    >,
263    pub on_instrument_close: Option<
264        unsafe extern "C" fn(
265            handle: *mut PluginStrategyHandle,
266            close: *const InstrumentClose,
267        ) -> PluginResult<()>,
268    >,
269    pub on_signal: Option<
270        unsafe extern "C" fn(
271            handle: *mut PluginStrategyHandle,
272            signal: *const Signal,
273        ) -> PluginResult<()>,
274    >,
275
276    pub on_order_initialized: Option<
277        unsafe extern "C" fn(
278            handle: *mut PluginStrategyHandle,
279            event: *const OrderInitialized,
280        ) -> PluginResult<()>,
281    >,
282    pub on_order_submitted: Option<
283        unsafe extern "C" fn(
284            handle: *mut PluginStrategyHandle,
285            event: *const OrderSubmitted,
286        ) -> PluginResult<()>,
287    >,
288    pub on_order_accepted: Option<
289        unsafe extern "C" fn(
290            handle: *mut PluginStrategyHandle,
291            event: *const OrderAccepted,
292        ) -> PluginResult<()>,
293    >,
294    pub on_order_rejected: Option<
295        unsafe extern "C" fn(
296            handle: *mut PluginStrategyHandle,
297            event: *const OrderRejected,
298        ) -> PluginResult<()>,
299    >,
300    pub on_order_filled: Option<
301        unsafe extern "C" fn(
302            handle: *mut PluginStrategyHandle,
303            event: *const OrderFilled,
304        ) -> PluginResult<()>,
305    >,
306    pub on_order_canceled: Option<
307        unsafe extern "C" fn(
308            handle: *mut PluginStrategyHandle,
309            event: *const OrderCanceled,
310        ) -> PluginResult<()>,
311    >,
312    pub on_order_expired: Option<
313        unsafe extern "C" fn(
314            handle: *mut PluginStrategyHandle,
315            event: *const OrderExpired,
316        ) -> PluginResult<()>,
317    >,
318    pub on_order_triggered: Option<
319        unsafe extern "C" fn(
320            handle: *mut PluginStrategyHandle,
321            event: *const OrderTriggered,
322        ) -> PluginResult<()>,
323    >,
324    pub on_order_denied: Option<
325        unsafe extern "C" fn(
326            handle: *mut PluginStrategyHandle,
327            event: *const OrderDenied,
328        ) -> PluginResult<()>,
329    >,
330    pub on_order_emulated: Option<
331        unsafe extern "C" fn(
332            handle: *mut PluginStrategyHandle,
333            event: *const OrderEmulated,
334        ) -> PluginResult<()>,
335    >,
336    pub on_order_released: Option<
337        unsafe extern "C" fn(
338            handle: *mut PluginStrategyHandle,
339            event: *const OrderReleased,
340        ) -> PluginResult<()>,
341    >,
342    pub on_order_pending_update: Option<
343        unsafe extern "C" fn(
344            handle: *mut PluginStrategyHandle,
345            event: *const OrderPendingUpdate,
346        ) -> PluginResult<()>,
347    >,
348    pub on_order_pending_cancel: Option<
349        unsafe extern "C" fn(
350            handle: *mut PluginStrategyHandle,
351            event: *const OrderPendingCancel,
352        ) -> PluginResult<()>,
353    >,
354    pub on_order_modify_rejected: Option<
355        unsafe extern "C" fn(
356            handle: *mut PluginStrategyHandle,
357            event: *const OrderModifyRejected,
358        ) -> PluginResult<()>,
359    >,
360    pub on_order_cancel_rejected: Option<
361        unsafe extern "C" fn(
362            handle: *mut PluginStrategyHandle,
363            event: *const OrderCancelRejected,
364        ) -> PluginResult<()>,
365    >,
366    pub on_order_updated: Option<
367        unsafe extern "C" fn(
368            handle: *mut PluginStrategyHandle,
369            event: *const OrderUpdated,
370        ) -> PluginResult<()>,
371    >,
372
373    pub on_position_opened: Option<
374        unsafe extern "C" fn(
375            handle: *mut PluginStrategyHandle,
376            event: *const PositionOpened,
377        ) -> PluginResult<()>,
378    >,
379    pub on_position_changed: Option<
380        unsafe extern "C" fn(
381            handle: *mut PluginStrategyHandle,
382            event: *const PositionChanged,
383        ) -> PluginResult<()>,
384    >,
385    pub on_position_closed: Option<
386        unsafe extern "C" fn(
387            handle: *mut PluginStrategyHandle,
388            event: *const PositionClosed,
389        ) -> PluginResult<()>,
390    >,
391
392    pub on_market_exit:
393        Option<unsafe extern "C" fn(handle: *mut PluginStrategyHandle) -> PluginResult<()>>,
394
395    pub on_historical_book_deltas: Option<
396        unsafe extern "C" fn(
397            handle: *mut PluginStrategyHandle,
398            deltas: Slice<'_, OrderBookDelta>,
399        ) -> PluginResult<()>,
400    >,
401    pub on_historical_book_depth: Option<
402        unsafe extern "C" fn(
403            handle: *mut PluginStrategyHandle,
404            depths: Slice<'_, OrderBookDepth10>,
405        ) -> PluginResult<()>,
406    >,
407    pub on_historical_quotes: Option<
408        unsafe extern "C" fn(
409            handle: *mut PluginStrategyHandle,
410            quotes: Slice<'_, QuoteTick>,
411        ) -> PluginResult<()>,
412    >,
413    pub on_historical_trades: Option<
414        unsafe extern "C" fn(
415            handle: *mut PluginStrategyHandle,
416            trades: Slice<'_, TradeTick>,
417        ) -> PluginResult<()>,
418    >,
419    pub on_historical_bars: Option<
420        unsafe extern "C" fn(
421            handle: *mut PluginStrategyHandle,
422            bars: Slice<'_, Bar>,
423        ) -> PluginResult<()>,
424    >,
425    pub on_historical_mark_prices: Option<
426        unsafe extern "C" fn(
427            handle: *mut PluginStrategyHandle,
428            mark_prices: Slice<'_, MarkPriceUpdate>,
429        ) -> PluginResult<()>,
430    >,
431    pub on_historical_index_prices: Option<
432        unsafe extern "C" fn(
433            handle: *mut PluginStrategyHandle,
434            index_prices: Slice<'_, IndexPriceUpdate>,
435        ) -> PluginResult<()>,
436    >,
437    pub on_historical_funding_rates: Option<
438        unsafe extern "C" fn(
439            handle: *mut PluginStrategyHandle,
440            funding_rates: Slice<'_, FundingRateUpdate>,
441        ) -> PluginResult<()>,
442    >,
443}
444
445/// Author-facing trait for a plug-in strategy.
446///
447/// Strategies receive every callback an actor does plus position and order
448/// lifecycle events, and they have access to a [`HostContext`] pointer
449/// they can use to issue order commands through the host.
450///
451/// Every callback has a no-op default. Override only what you need.
452pub trait PluginStrategy: 'static + Send + Sized {
453    /// Canonical type name. Must be unique across a Nautilus deployment.
454    const TYPE_NAME: &'static str;
455
456    /// Constructs a fresh strategy instance bound to the supplied host
457    /// vtable and instance context. Implementations typically store both
458    /// pointers in fields so that order-command callbacks (`submit_order`,
459    /// `cancel_order`, `modify_order`) can be issued through
460    /// `host.submit_order(ctx, ...)` etc.
461    ///
462    /// `config_json` is the per-instance JSON configuration the host
463    /// constructed from the user's TOML or builder API. The string is
464    /// empty when no instance-specific configuration is supplied.
465    fn new(host: *const HostVTable, ctx: *const HostContext, config_json: &str) -> Self;
466
467    #[allow(unused_variables)]
468    fn on_start(&mut self) -> anyhow::Result<()> {
469        Ok(())
470    }
471
472    #[allow(unused_variables)]
473    fn on_stop(&mut self) -> anyhow::Result<()> {
474        Ok(())
475    }
476
477    #[allow(unused_variables)]
478    fn on_resume(&mut self) -> anyhow::Result<()> {
479        Ok(())
480    }
481
482    #[allow(unused_variables)]
483    fn on_reset(&mut self) -> anyhow::Result<()> {
484        Ok(())
485    }
486
487    #[allow(unused_variables)]
488    fn on_dispose(&mut self) -> anyhow::Result<()> {
489        Ok(())
490    }
491
492    #[allow(unused_variables)]
493    fn on_degrade(&mut self) -> anyhow::Result<()> {
494        Ok(())
495    }
496
497    #[allow(unused_variables)]
498    fn on_fault(&mut self) -> anyhow::Result<()> {
499        Ok(())
500    }
501
502    #[allow(unused_variables)]
503    fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
504        Ok(())
505    }
506
507    #[allow(unused_variables)]
508    fn on_data(&mut self, data: PluginCustomDataRef) -> anyhow::Result<()> {
509        Ok(())
510    }
511
512    #[allow(unused_variables)]
513    fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
514        Ok(())
515    }
516
517    #[allow(unused_variables)]
518    fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
519        Ok(())
520    }
521
522    #[allow(unused_variables)]
523    fn on_book(&mut self, book: &OrderBook) -> anyhow::Result<()> {
524        Ok(())
525    }
526
527    #[allow(unused_variables)]
528    fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
529        Ok(())
530    }
531
532    #[allow(unused_variables)]
533    fn on_trade(&mut self, trade: &TradeTick) -> anyhow::Result<()> {
534        Ok(())
535    }
536
537    #[allow(unused_variables)]
538    fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
539        Ok(())
540    }
541
542    #[allow(unused_variables)]
543    fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
544        Ok(())
545    }
546
547    #[allow(unused_variables)]
548    fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
549        Ok(())
550    }
551
552    #[allow(unused_variables)]
553    fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
554        Ok(())
555    }
556
557    #[allow(unused_variables)]
558    fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
559        Ok(())
560    }
561
562    #[allow(unused_variables)]
563    fn on_option_chain(&mut self, chain: &OptionChainSlice) -> anyhow::Result<()> {
564        Ok(())
565    }
566
567    #[allow(unused_variables)]
568    fn on_instrument_status(&mut self, status: &InstrumentStatus) -> anyhow::Result<()> {
569        Ok(())
570    }
571
572    #[allow(unused_variables)]
573    fn on_instrument_close(&mut self, close: &InstrumentClose) -> anyhow::Result<()> {
574        Ok(())
575    }
576
577    #[allow(unused_variables)]
578    fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
579        Ok(())
580    }
581
582    #[allow(unused_variables)]
583    fn on_order_initialized(&mut self, event: &OrderInitialized) -> anyhow::Result<()> {
584        Ok(())
585    }
586
587    #[allow(unused_variables)]
588    fn on_order_submitted(&mut self, event: &OrderSubmitted) -> anyhow::Result<()> {
589        Ok(())
590    }
591
592    #[allow(unused_variables)]
593    fn on_order_accepted(&mut self, event: &OrderAccepted) -> anyhow::Result<()> {
594        Ok(())
595    }
596
597    #[allow(unused_variables)]
598    fn on_order_rejected(&mut self, event: &OrderRejected) -> anyhow::Result<()> {
599        Ok(())
600    }
601
602    #[allow(unused_variables)]
603    fn on_order_filled(&mut self, event: &OrderFilled) -> anyhow::Result<()> {
604        Ok(())
605    }
606
607    #[allow(unused_variables)]
608    fn on_order_canceled(&mut self, event: &OrderCanceled) -> anyhow::Result<()> {
609        Ok(())
610    }
611
612    #[allow(unused_variables)]
613    fn on_order_expired(&mut self, event: &OrderExpired) -> anyhow::Result<()> {
614        Ok(())
615    }
616
617    #[allow(unused_variables)]
618    fn on_order_triggered(&mut self, event: &OrderTriggered) -> anyhow::Result<()> {
619        Ok(())
620    }
621
622    #[allow(unused_variables)]
623    fn on_order_denied(&mut self, event: &OrderDenied) -> anyhow::Result<()> {
624        Ok(())
625    }
626
627    #[allow(unused_variables)]
628    fn on_order_emulated(&mut self, event: &OrderEmulated) -> anyhow::Result<()> {
629        Ok(())
630    }
631
632    #[allow(unused_variables)]
633    fn on_order_released(&mut self, event: &OrderReleased) -> anyhow::Result<()> {
634        Ok(())
635    }
636
637    #[allow(unused_variables)]
638    fn on_order_pending_update(&mut self, event: &OrderPendingUpdate) -> anyhow::Result<()> {
639        Ok(())
640    }
641
642    #[allow(unused_variables)]
643    fn on_order_pending_cancel(&mut self, event: &OrderPendingCancel) -> anyhow::Result<()> {
644        Ok(())
645    }
646
647    #[allow(unused_variables)]
648    fn on_order_modify_rejected(&mut self, event: &OrderModifyRejected) -> anyhow::Result<()> {
649        Ok(())
650    }
651
652    #[allow(unused_variables)]
653    fn on_order_cancel_rejected(&mut self, event: &OrderCancelRejected) -> anyhow::Result<()> {
654        Ok(())
655    }
656
657    #[allow(unused_variables)]
658    fn on_order_updated(&mut self, event: &OrderUpdated) -> anyhow::Result<()> {
659        Ok(())
660    }
661
662    #[allow(unused_variables)]
663    fn on_position_opened(&mut self, event: &PositionOpened) -> anyhow::Result<()> {
664        Ok(())
665    }
666
667    #[allow(unused_variables)]
668    fn on_position_changed(&mut self, event: &PositionChanged) -> anyhow::Result<()> {
669        Ok(())
670    }
671
672    #[allow(unused_variables)]
673    fn on_position_closed(&mut self, event: &PositionClosed) -> anyhow::Result<()> {
674        Ok(())
675    }
676
677    #[allow(unused_variables)]
678    fn on_market_exit(&mut self) -> anyhow::Result<()> {
679        Ok(())
680    }
681
682    #[allow(unused_variables)]
683    fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
684        Ok(())
685    }
686
687    #[allow(unused_variables)]
688    fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth10]) -> anyhow::Result<()> {
689        Ok(())
690    }
691
692    #[allow(unused_variables)]
693    fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
694        Ok(())
695    }
696
697    #[allow(unused_variables)]
698    fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
699        Ok(())
700    }
701
702    #[allow(unused_variables)]
703    fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
704        Ok(())
705    }
706
707    #[allow(unused_variables)]
708    fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
709        Ok(())
710    }
711
712    #[allow(unused_variables)]
713    fn on_historical_index_prices(
714        &mut self,
715        index_prices: &[IndexPriceUpdate],
716    ) -> anyhow::Result<()> {
717        Ok(())
718    }
719
720    #[allow(unused_variables)]
721    fn on_historical_funding_rates(
722        &mut self,
723        funding_rates: &[FundingRateUpdate],
724    ) -> anyhow::Result<()> {
725        Ok(())
726    }
727}
728
729/// Returns a `*const StrategyVTable` for the given [`PluginStrategy`] type.
730///
731/// One static vtable per monomorphisation via the `Tag<T>`-shaped pattern
732/// proven by [`actor_vtable`](crate::surfaces::actor::actor_vtable).
733#[must_use]
734pub fn strategy_vtable<T>() -> *const StrategyVTable
735where
736    T: PluginStrategy,
737{
738    &VTableTag::<T>::VTABLE
739}
740
741struct VTableTag<T>(PhantomData<T>);
742
743impl<T> VTableTag<T>
744where
745    T: PluginStrategy,
746{
747    const VTABLE: StrategyVTable = StrategyVTable {
748        create: Some(create_thunk::<T>),
749        drop_handle: Some(drop_handle_thunk::<T>),
750        type_name: Some(type_name_thunk::<T>),
751        on_start: Some(on_start_thunk::<T>),
752        on_stop: Some(on_stop_thunk::<T>),
753        on_resume: Some(on_resume_thunk::<T>),
754        on_reset: Some(on_reset_thunk::<T>),
755        on_dispose: Some(on_dispose_thunk::<T>),
756        on_degrade: Some(on_degrade_thunk::<T>),
757        on_fault: Some(on_fault_thunk::<T>),
758        on_time_event: Some(on_time_event_thunk::<T>),
759        on_data: Some(on_data_thunk::<T>),
760        on_instrument: Some(on_instrument_thunk::<T>),
761        on_book_deltas: Some(on_book_deltas_thunk::<T>),
762        on_book: Some(on_book_thunk::<T>),
763        on_quote: Some(on_quote_thunk::<T>),
764        on_trade: Some(on_trade_thunk::<T>),
765        on_bar: Some(on_bar_thunk::<T>),
766        on_mark_price: Some(on_mark_price_thunk::<T>),
767        on_index_price: Some(on_index_price_thunk::<T>),
768        on_funding_rate: Some(on_funding_rate_thunk::<T>),
769        on_option_greeks: Some(on_option_greeks_thunk::<T>),
770        on_option_chain: Some(on_option_chain_thunk::<T>),
771        on_instrument_status: Some(on_instrument_status_thunk::<T>),
772        on_instrument_close: Some(on_instrument_close_thunk::<T>),
773        on_signal: Some(on_signal_thunk::<T>),
774        on_order_initialized: Some(on_order_initialized_thunk::<T>),
775        on_order_submitted: Some(on_order_submitted_thunk::<T>),
776        on_order_accepted: Some(on_order_accepted_thunk::<T>),
777        on_order_rejected: Some(on_order_rejected_thunk::<T>),
778        on_order_filled: Some(on_order_filled_thunk::<T>),
779        on_order_canceled: Some(on_order_canceled_thunk::<T>),
780        on_order_expired: Some(on_order_expired_thunk::<T>),
781        on_order_triggered: Some(on_order_triggered_thunk::<T>),
782        on_order_denied: Some(on_order_denied_thunk::<T>),
783        on_order_emulated: Some(on_order_emulated_thunk::<T>),
784        on_order_released: Some(on_order_released_thunk::<T>),
785        on_order_pending_update: Some(on_order_pending_update_thunk::<T>),
786        on_order_pending_cancel: Some(on_order_pending_cancel_thunk::<T>),
787        on_order_modify_rejected: Some(on_order_modify_rejected_thunk::<T>),
788        on_order_cancel_rejected: Some(on_order_cancel_rejected_thunk::<T>),
789        on_order_updated: Some(on_order_updated_thunk::<T>),
790        on_position_opened: Some(on_position_opened_thunk::<T>),
791        on_position_changed: Some(on_position_changed_thunk::<T>),
792        on_position_closed: Some(on_position_closed_thunk::<T>),
793        on_market_exit: Some(on_market_exit_thunk::<T>),
794        on_historical_book_deltas: Some(on_historical_book_deltas_thunk::<T>),
795        on_historical_book_depth: Some(on_historical_book_depth_thunk::<T>),
796        on_historical_quotes: Some(on_historical_quotes_thunk::<T>),
797        on_historical_trades: Some(on_historical_trades_thunk::<T>),
798        on_historical_bars: Some(on_historical_bars_thunk::<T>),
799        on_historical_mark_prices: Some(on_historical_mark_prices_thunk::<T>),
800        on_historical_index_prices: Some(on_historical_index_prices_thunk::<T>),
801        on_historical_funding_rates: Some(on_historical_funding_rates_thunk::<T>),
802    };
803}
804
805unsafe extern "C" fn create_thunk<T: PluginStrategy>(
806    host: *const HostVTable,
807    ctx: *const HostContext,
808    config_json: BorrowedStr<'_>,
809) -> *mut PluginStrategyHandle {
810    guard_infallible("strategy::create", || {
811        // SAFETY: host promises `config_json` borrows storage that is live
812        // for the duration of this call.
813        let cfg = unsafe { config_json.as_str() };
814        Box::into_raw(Box::new(T::new(host, ctx, cfg))).cast::<PluginStrategyHandle>()
815    })
816}
817
818unsafe extern "C" fn drop_handle_thunk<T: PluginStrategy>(handle: *mut PluginStrategyHandle) {
819    if handle.is_null() {
820        return;
821    }
822    guard_infallible("strategy::drop", || {
823        // SAFETY: handle was allocated via `Box::into_raw(Box::new(T))`.
824        unsafe {
825            drop(Box::from_raw(handle.cast::<T>()));
826        }
827    });
828}
829
830unsafe extern "C" fn type_name_thunk<T: PluginStrategy>() -> BorrowedStr<'static> {
831    BorrowedStr::from_str(T::TYPE_NAME)
832}
833
834fn handle_as_mut<'a, T: PluginStrategy>(handle: *mut PluginStrategyHandle) -> &'a mut T {
835    // SAFETY: handle is non-null and originates from a `Box::into_raw` of a
836    // `T`. The host promises exclusive access while a callback is running.
837    unsafe { &mut *handle.cast::<T>() }
838}
839
840fn ok_or_err<E: ::core::fmt::Display>(r: Result<(), E>) -> Result<(), PluginError> {
841    r.map_err(|e| PluginError::new(PluginErrorCode::Generic, e.to_string()))
842}
843
844macro_rules! lifecycle_thunk {
845    ($name:ident, $method:ident) => {
846        unsafe extern "C" fn $name<T: PluginStrategy>(
847            handle: *mut PluginStrategyHandle,
848        ) -> PluginResult<()> {
849            guard(|| {
850                let strategy = handle_as_mut::<T>(handle);
851                ok_or_err(strategy.$method())
852            })
853        }
854    };
855}
856
857lifecycle_thunk!(on_start_thunk, on_start);
858lifecycle_thunk!(on_stop_thunk, on_stop);
859lifecycle_thunk!(on_resume_thunk, on_resume);
860lifecycle_thunk!(on_reset_thunk, on_reset);
861lifecycle_thunk!(on_dispose_thunk, on_dispose);
862lifecycle_thunk!(on_degrade_thunk, on_degrade);
863lifecycle_thunk!(on_fault_thunk, on_fault);
864lifecycle_thunk!(on_market_exit_thunk, on_market_exit);
865
866macro_rules! event_thunk {
867    ($name:ident, $method:ident, $ty:ty) => {
868        unsafe extern "C" fn $name<T: PluginStrategy>(
869            handle: *mut PluginStrategyHandle,
870            value: *const $ty,
871        ) -> PluginResult<()> {
872            guard(|| {
873                // SAFETY: host keeps `value` live for the duration of the call;
874                // the plug-in only borrows it for the trait-method invocation.
875                let v = unsafe { &*value }.boundary_normalized();
876                let strategy = handle_as_mut::<T>(handle);
877                ok_or_err(strategy.$method(&v))
878            })
879        }
880    };
881}
882
883event_thunk!(on_time_event_thunk, on_time_event, TimeEvent);
884
885unsafe extern "C" fn on_data_thunk<T: PluginStrategy>(
886    handle: *mut PluginStrategyHandle,
887    data: PluginCustomDataRef,
888) -> PluginResult<()> {
889    guard(|| {
890        let strategy = handle_as_mut::<T>(handle);
891        ok_or_err(strategy.on_data(data))
892    })
893}
894
895unsafe extern "C" fn on_instrument_thunk<T: PluginStrategy>(
896    handle: *mut PluginStrategyHandle,
897    instrument: *const InstrumentAnyHandle,
898) -> PluginResult<()> {
899    guard(|| {
900        // SAFETY: host keeps the handle live for the duration of the call;
901        // the plug-in only borrows the wrapped instrument via the trait method.
902        let v: InstrumentAny = unsafe { (*instrument).instrument() }.boundary_normalized();
903        let strategy = handle_as_mut::<T>(handle);
904        ok_or_err(strategy.on_instrument(&v))
905    })
906}
907
908unsafe extern "C" fn on_book_deltas_thunk<T: PluginStrategy>(
909    handle: *mut PluginStrategyHandle,
910    deltas: *const OrderBookDeltasHandle,
911) -> PluginResult<()> {
912    guard(|| {
913        // SAFETY: host keeps the handle live for the duration of the call;
914        // the plug-in only borrows the wrapped deltas via the trait method.
915        let v: OrderBookDeltas = unsafe { (*deltas).deltas() }.boundary_normalized();
916        let strategy = handle_as_mut::<T>(handle);
917        ok_or_err(strategy.on_book_deltas(&v))
918    })
919}
920
921unsafe extern "C" fn on_book_thunk<T: PluginStrategy>(
922    handle: *mut PluginStrategyHandle,
923    book: *const OrderBookHandle,
924) -> PluginResult<()> {
925    guard(|| {
926        // SAFETY: host keeps the handle live for the duration of the call;
927        // the plug-in only borrows the wrapped book via the trait method.
928        let v: OrderBook = unsafe { (*book).book() }.boundary_normalized();
929        let strategy = handle_as_mut::<T>(handle);
930        ok_or_err(strategy.on_book(&v))
931    })
932}
933
934event_thunk!(on_quote_thunk, on_quote, QuoteTick);
935event_thunk!(on_trade_thunk, on_trade, TradeTick);
936event_thunk!(on_bar_thunk, on_bar, Bar);
937event_thunk!(on_mark_price_thunk, on_mark_price, MarkPriceUpdate);
938event_thunk!(on_index_price_thunk, on_index_price, IndexPriceUpdate);
939event_thunk!(on_funding_rate_thunk, on_funding_rate, FundingRateUpdate);
940event_thunk!(on_option_greeks_thunk, on_option_greeks, OptionGreeks);
941
942unsafe extern "C" fn on_option_chain_thunk<T: PluginStrategy>(
943    handle: *mut PluginStrategyHandle,
944    chain: *const OptionChainSliceHandle,
945) -> PluginResult<()> {
946    guard(|| {
947        // SAFETY: host keeps the handle live for the duration of the call;
948        // the plug-in only borrows the wrapped chain via the trait method.
949        let v: OptionChainSlice = unsafe { (*chain).chain() }.boundary_normalized();
950        let strategy = handle_as_mut::<T>(handle);
951        ok_or_err(strategy.on_option_chain(&v))
952    })
953}
954
955event_thunk!(
956    on_instrument_status_thunk,
957    on_instrument_status,
958    InstrumentStatus
959);
960event_thunk!(
961    on_instrument_close_thunk,
962    on_instrument_close,
963    InstrumentClose
964);
965event_thunk!(on_signal_thunk, on_signal, Signal);
966
967event_thunk!(
968    on_order_initialized_thunk,
969    on_order_initialized,
970    OrderInitialized
971);
972event_thunk!(on_order_submitted_thunk, on_order_submitted, OrderSubmitted);
973event_thunk!(on_order_accepted_thunk, on_order_accepted, OrderAccepted);
974event_thunk!(on_order_rejected_thunk, on_order_rejected, OrderRejected);
975event_thunk!(on_order_filled_thunk, on_order_filled, OrderFilled);
976event_thunk!(on_order_canceled_thunk, on_order_canceled, OrderCanceled);
977event_thunk!(on_order_expired_thunk, on_order_expired, OrderExpired);
978event_thunk!(on_order_triggered_thunk, on_order_triggered, OrderTriggered);
979event_thunk!(on_order_denied_thunk, on_order_denied, OrderDenied);
980event_thunk!(on_order_emulated_thunk, on_order_emulated, OrderEmulated);
981event_thunk!(on_order_released_thunk, on_order_released, OrderReleased);
982event_thunk!(
983    on_order_pending_update_thunk,
984    on_order_pending_update,
985    OrderPendingUpdate
986);
987event_thunk!(
988    on_order_pending_cancel_thunk,
989    on_order_pending_cancel,
990    OrderPendingCancel
991);
992event_thunk!(
993    on_order_modify_rejected_thunk,
994    on_order_modify_rejected,
995    OrderModifyRejected
996);
997event_thunk!(
998    on_order_cancel_rejected_thunk,
999    on_order_cancel_rejected,
1000    OrderCancelRejected
1001);
1002event_thunk!(on_order_updated_thunk, on_order_updated, OrderUpdated);
1003
1004event_thunk!(on_position_opened_thunk, on_position_opened, PositionOpened);
1005event_thunk!(
1006    on_position_changed_thunk,
1007    on_position_changed,
1008    PositionChanged
1009);
1010event_thunk!(on_position_closed_thunk, on_position_closed, PositionClosed);
1011
1012macro_rules! slice_thunk {
1013    ($name:ident, $method:ident, $ty:ty) => {
1014        unsafe extern "C" fn $name<T: PluginStrategy>(
1015            handle: *mut PluginStrategyHandle,
1016            values: Slice<'_, $ty>,
1017        ) -> PluginResult<()> {
1018            guard(|| {
1019                // SAFETY: host keeps the slice storage live for the call;
1020                // the plug-in only borrows it for the trait-method invocation.
1021                let v: Vec<$ty> = unsafe { values.as_slice() }
1022                    .iter()
1023                    .map(BoundaryNormalize::boundary_normalized)
1024                    .collect();
1025                let strategy = handle_as_mut::<T>(handle);
1026                ok_or_err(strategy.$method(&v))
1027            })
1028        }
1029    };
1030}
1031
1032slice_thunk!(
1033    on_historical_book_deltas_thunk,
1034    on_historical_book_deltas,
1035    OrderBookDelta
1036);
1037slice_thunk!(
1038    on_historical_book_depth_thunk,
1039    on_historical_book_depth,
1040    OrderBookDepth10
1041);
1042slice_thunk!(on_historical_quotes_thunk, on_historical_quotes, QuoteTick);
1043slice_thunk!(on_historical_trades_thunk, on_historical_trades, TradeTick);
1044slice_thunk!(on_historical_bars_thunk, on_historical_bars, Bar);
1045slice_thunk!(
1046    on_historical_mark_prices_thunk,
1047    on_historical_mark_prices,
1048    MarkPriceUpdate
1049);
1050slice_thunk!(
1051    on_historical_index_prices_thunk,
1052    on_historical_index_prices,
1053    IndexPriceUpdate
1054);
1055slice_thunk!(
1056    on_historical_funding_rates_thunk,
1057    on_historical_funding_rates,
1058    FundingRateUpdate
1059);