Skip to main content

nautilus_plugin/surfaces/
actor.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//! Actor plug point.
17//!
18//! Plug-in authors implement [`PluginActor`] on their struct; the generated
19//! [`ActorVTable`] adapts that trait to the C-ABI boundary so the host's
20//! `DataActor`-shaped engine can dispatch callbacks into the cdylib.
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, no allocation per event.
29//!
30//! Lifecycle callbacks (`on_start`, `on_stop`, ...) carry no payload.
31//!
32//! # Scope (v1)
33//!
34//! Phase 1 covers the lifecycle callbacks plus the data callbacks whose
35//! payload types are `#[repr(C)]`-clean end-to-end:
36//!
37//! - Lifecycle: `start`, `stop`, `resume`, `reset`, `dispose`, `degrade`, `fault`
38//! - Market data: instruments
39//!   (via [`crate::surfaces::instrument::InstrumentAnyHandle`]),
40//!   book snapshots (via [`crate::surfaces::book::OrderBookHandle`]),
41//!   book deltas (via [`crate::surfaces::book::OrderBookDeltasHandle`]),
42//!   quotes, trades, bars, mark/index/funding prices, option greeks, option chain slices
43//!   (via [`crate::surfaces::option_chain::OptionChainSliceHandle`]),
44//!   instrument status, and instrument close
45//! - Historical market data: bulk historical book deltas, book depth,
46//!   quotes, trades, bars, mark prices, index prices, funding rates
47//!   delivered as [`crate::boundary::Slice`] payloads
48//! - Order events: filled, canceled
49//! - Custom data: values registered through `PluginCustomData`, including
50//!   historical custom-data responses routed through `on_data`
51//! - Other: signal, time event
52//!
53//! Deferred: generic non-plugin `on_historical_data` (`&dyn Any` payload),
54//! DeFi pool/block events. The authoritative list lives in
55//! `tests/surface_alignment.rs`.
56
57#![allow(unsafe_code)]
58
59use std::marker::PhantomData;
60
61use nautilus_common::{signal::Signal, timer::TimeEvent};
62use nautilus_model::{
63    data::{
64        Bar, FundingRateUpdate, IndexPriceUpdate, InstrumentClose, InstrumentStatus,
65        MarkPriceUpdate, OptionChainSlice, OptionGreeks, OrderBookDelta, OrderBookDeltas,
66        OrderBookDepth10, QuoteTick, TradeTick,
67    },
68    events::{OrderCanceled, OrderFilled},
69    instruments::InstrumentAny,
70    orderbook::OrderBook,
71};
72
73use crate::{
74    boundary::{BorrowedStr, PluginError, PluginErrorCode, PluginResult, Slice},
75    host::{HostContext, HostVTable},
76    normalize::BoundaryNormalize,
77    panic::{guard, guard_infallible},
78    surfaces::{
79        book::{OrderBookDeltasHandle, OrderBookHandle},
80        custom_data::PluginCustomDataRef,
81        instrument::InstrumentAnyHandle,
82        option_chain::OptionChainSliceHandle,
83    },
84};
85
86/// Opaque handle to a plug-in actor instance owned by the cdylib.
87///
88/// The host never deref's this pointer; it only passes it back to the vtable
89/// through the lifecycle and data callbacks until releasing it via `drop`.
90#[repr(C)]
91pub struct PluginActorHandle {
92    _opaque: [u8; 0],
93}
94
95/// Function table for a single plug-in actor type.
96///
97/// One static vtable per concrete type, generated by
98/// [`nautilus_plugin!`](crate::nautilus_plugin) via the `Tag<T>` pattern that
99/// also powers [`custom_data_vtable`](crate::surfaces::custom_data::custom_data_vtable).
100///
101/// Every callback that returns [`PluginResult<()>`] runs inside
102/// [`crate::panic::guard`]. The infallible `create`/`drop` callbacks run
103/// inside [`crate::panic::guard_infallible`], which aborts the host on
104/// panic since their signatures cannot carry an error.
105///
106/// Slots are nullable at the ABI type level so the host can reject malformed
107/// manifests with null callbacks before constructing an actor. Macro-generated
108/// vtables fill every required slot.
109#[repr(C)]
110pub struct ActorVTable {
111    /// Constructs a fresh actor instance bound to the supplied host vtable
112    /// and instance context, and returns a non-null handle.
113    ///
114    /// `config_json` carries the per-instance configuration the host
115    /// constructed from the user's TOML or builder API. Empty when the
116    /// host has no instance-specific configuration to pass.
117    pub create: Option<
118        unsafe extern "C" fn(
119            host: *const HostVTable,
120            ctx: *const HostContext,
121            config_json: BorrowedStr<'_>,
122        ) -> *mut PluginActorHandle,
123    >,
124
125    /// Drops the actor instance and releases all of its resources.
126    pub drop_handle: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle)>,
127
128    /// Returns the canonical type name for this actor.
129    pub type_name: Option<unsafe extern "C" fn() -> BorrowedStr<'static>>,
130
131    pub on_start: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
132    pub on_stop: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
133    pub on_resume: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
134    pub on_reset: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
135    pub on_dispose:
136        Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
137    pub on_degrade:
138        Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
139    pub on_fault: Option<unsafe extern "C" fn(handle: *mut PluginActorHandle) -> PluginResult<()>>,
140
141    pub on_time_event: Option<
142        unsafe extern "C" fn(
143            handle: *mut PluginActorHandle,
144            event: *const TimeEvent,
145        ) -> PluginResult<()>,
146    >,
147    pub on_data: Option<
148        unsafe extern "C" fn(
149            handle: *mut PluginActorHandle,
150            data: PluginCustomDataRef,
151        ) -> PluginResult<()>,
152    >,
153
154    pub on_instrument: Option<
155        unsafe extern "C" fn(
156            handle: *mut PluginActorHandle,
157            instrument: *const InstrumentAnyHandle,
158        ) -> PluginResult<()>,
159    >,
160    pub on_book_deltas: Option<
161        unsafe extern "C" fn(
162            handle: *mut PluginActorHandle,
163            deltas: *const OrderBookDeltasHandle,
164        ) -> PluginResult<()>,
165    >,
166    pub on_book: Option<
167        unsafe extern "C" fn(
168            handle: *mut PluginActorHandle,
169            book: *const OrderBookHandle,
170        ) -> PluginResult<()>,
171    >,
172    pub on_quote: Option<
173        unsafe extern "C" fn(
174            handle: *mut PluginActorHandle,
175            quote: *const QuoteTick,
176        ) -> PluginResult<()>,
177    >,
178    pub on_trade: Option<
179        unsafe extern "C" fn(
180            handle: *mut PluginActorHandle,
181            trade: *const TradeTick,
182        ) -> PluginResult<()>,
183    >,
184    pub on_bar: Option<
185        unsafe extern "C" fn(handle: *mut PluginActorHandle, bar: *const Bar) -> PluginResult<()>,
186    >,
187    pub on_mark_price: Option<
188        unsafe extern "C" fn(
189            handle: *mut PluginActorHandle,
190            mark_price: *const MarkPriceUpdate,
191        ) -> PluginResult<()>,
192    >,
193    pub on_index_price: Option<
194        unsafe extern "C" fn(
195            handle: *mut PluginActorHandle,
196            index_price: *const IndexPriceUpdate,
197        ) -> PluginResult<()>,
198    >,
199    pub on_funding_rate: Option<
200        unsafe extern "C" fn(
201            handle: *mut PluginActorHandle,
202            funding_rate: *const FundingRateUpdate,
203        ) -> PluginResult<()>,
204    >,
205    pub on_option_greeks: Option<
206        unsafe extern "C" fn(
207            handle: *mut PluginActorHandle,
208            greeks: *const OptionGreeks,
209        ) -> PluginResult<()>,
210    >,
211    pub on_option_chain: Option<
212        unsafe extern "C" fn(
213            handle: *mut PluginActorHandle,
214            chain: *const OptionChainSliceHandle,
215        ) -> PluginResult<()>,
216    >,
217    pub on_instrument_status: Option<
218        unsafe extern "C" fn(
219            handle: *mut PluginActorHandle,
220            status: *const InstrumentStatus,
221        ) -> PluginResult<()>,
222    >,
223    pub on_instrument_close: Option<
224        unsafe extern "C" fn(
225            handle: *mut PluginActorHandle,
226            close: *const InstrumentClose,
227        ) -> PluginResult<()>,
228    >,
229
230    pub on_order_filled: Option<
231        unsafe extern "C" fn(
232            handle: *mut PluginActorHandle,
233            event: *const OrderFilled,
234        ) -> PluginResult<()>,
235    >,
236    pub on_order_canceled: Option<
237        unsafe extern "C" fn(
238            handle: *mut PluginActorHandle,
239            event: *const OrderCanceled,
240        ) -> PluginResult<()>,
241    >,
242
243    pub on_signal: Option<
244        unsafe extern "C" fn(
245            handle: *mut PluginActorHandle,
246            signal: *const Signal,
247        ) -> PluginResult<()>,
248    >,
249
250    pub on_historical_book_deltas: Option<
251        unsafe extern "C" fn(
252            handle: *mut PluginActorHandle,
253            deltas: Slice<'_, OrderBookDelta>,
254        ) -> PluginResult<()>,
255    >,
256    pub on_historical_book_depth: Option<
257        unsafe extern "C" fn(
258            handle: *mut PluginActorHandle,
259            depths: Slice<'_, OrderBookDepth10>,
260        ) -> PluginResult<()>,
261    >,
262    pub on_historical_quotes: Option<
263        unsafe extern "C" fn(
264            handle: *mut PluginActorHandle,
265            quotes: Slice<'_, QuoteTick>,
266        ) -> PluginResult<()>,
267    >,
268    pub on_historical_trades: Option<
269        unsafe extern "C" fn(
270            handle: *mut PluginActorHandle,
271            trades: Slice<'_, TradeTick>,
272        ) -> PluginResult<()>,
273    >,
274    pub on_historical_bars: Option<
275        unsafe extern "C" fn(
276            handle: *mut PluginActorHandle,
277            bars: Slice<'_, Bar>,
278        ) -> PluginResult<()>,
279    >,
280    pub on_historical_mark_prices: Option<
281        unsafe extern "C" fn(
282            handle: *mut PluginActorHandle,
283            mark_prices: Slice<'_, MarkPriceUpdate>,
284        ) -> PluginResult<()>,
285    >,
286    pub on_historical_index_prices: Option<
287        unsafe extern "C" fn(
288            handle: *mut PluginActorHandle,
289            index_prices: Slice<'_, IndexPriceUpdate>,
290        ) -> PluginResult<()>,
291    >,
292    pub on_historical_funding_rates: Option<
293        unsafe extern "C" fn(
294            handle: *mut PluginActorHandle,
295            funding_rates: Slice<'_, FundingRateUpdate>,
296        ) -> PluginResult<()>,
297    >,
298}
299
300/// Author-facing trait for a plug-in actor.
301///
302/// Every callback has a no-op default; authors override the ones they care
303/// about. Callbacks receive borrowed references to host-owned values; the
304/// references are only valid for the duration of the call.
305pub trait PluginActor: 'static + Send + Sized {
306    /// Canonical type name. Must be unique across a Nautilus deployment.
307    const TYPE_NAME: &'static str;
308
309    /// Constructs a fresh actor instance bound to the supplied host vtable
310    /// and instance context.
311    ///
312    /// `config_json` is the per-instance JSON configuration the host
313    /// constructed from the user's TOML or builder API. The string is
314    /// empty when no instance-specific configuration is supplied.
315    fn new(host: *const HostVTable, ctx: *const HostContext, config_json: &str) -> Self;
316
317    #[allow(unused_variables)]
318    fn on_start(&mut self) -> anyhow::Result<()> {
319        Ok(())
320    }
321
322    #[allow(unused_variables)]
323    fn on_stop(&mut self) -> anyhow::Result<()> {
324        Ok(())
325    }
326
327    #[allow(unused_variables)]
328    fn on_resume(&mut self) -> anyhow::Result<()> {
329        Ok(())
330    }
331
332    #[allow(unused_variables)]
333    fn on_reset(&mut self) -> anyhow::Result<()> {
334        Ok(())
335    }
336
337    #[allow(unused_variables)]
338    fn on_dispose(&mut self) -> anyhow::Result<()> {
339        Ok(())
340    }
341
342    #[allow(unused_variables)]
343    fn on_degrade(&mut self) -> anyhow::Result<()> {
344        Ok(())
345    }
346
347    #[allow(unused_variables)]
348    fn on_fault(&mut self) -> anyhow::Result<()> {
349        Ok(())
350    }
351
352    #[allow(unused_variables)]
353    fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
354        Ok(())
355    }
356
357    #[allow(unused_variables)]
358    fn on_data(&mut self, data: PluginCustomDataRef) -> anyhow::Result<()> {
359        Ok(())
360    }
361
362    #[allow(unused_variables)]
363    fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
364        Ok(())
365    }
366
367    #[allow(unused_variables)]
368    fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
369        Ok(())
370    }
371
372    #[allow(unused_variables)]
373    fn on_book(&mut self, book: &OrderBook) -> anyhow::Result<()> {
374        Ok(())
375    }
376
377    #[allow(unused_variables)]
378    fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
379        Ok(())
380    }
381
382    #[allow(unused_variables)]
383    fn on_trade(&mut self, trade: &TradeTick) -> anyhow::Result<()> {
384        Ok(())
385    }
386
387    #[allow(unused_variables)]
388    fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
389        Ok(())
390    }
391
392    #[allow(unused_variables)]
393    fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
394        Ok(())
395    }
396
397    #[allow(unused_variables)]
398    fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
399        Ok(())
400    }
401
402    #[allow(unused_variables)]
403    fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
404        Ok(())
405    }
406
407    #[allow(unused_variables)]
408    fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
409        Ok(())
410    }
411
412    #[allow(unused_variables)]
413    fn on_option_chain(&mut self, chain: &OptionChainSlice) -> anyhow::Result<()> {
414        Ok(())
415    }
416
417    #[allow(unused_variables)]
418    fn on_instrument_status(&mut self, status: &InstrumentStatus) -> anyhow::Result<()> {
419        Ok(())
420    }
421
422    #[allow(unused_variables)]
423    fn on_instrument_close(&mut self, close: &InstrumentClose) -> anyhow::Result<()> {
424        Ok(())
425    }
426
427    #[allow(unused_variables)]
428    fn on_order_filled(&mut self, event: &OrderFilled) -> anyhow::Result<()> {
429        Ok(())
430    }
431
432    #[allow(unused_variables)]
433    fn on_order_canceled(&mut self, event: &OrderCanceled) -> anyhow::Result<()> {
434        Ok(())
435    }
436
437    #[allow(unused_variables)]
438    fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
439        Ok(())
440    }
441
442    #[allow(unused_variables)]
443    fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
444        Ok(())
445    }
446
447    #[allow(unused_variables)]
448    fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth10]) -> anyhow::Result<()> {
449        Ok(())
450    }
451
452    #[allow(unused_variables)]
453    fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
454        Ok(())
455    }
456
457    #[allow(unused_variables)]
458    fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
459        Ok(())
460    }
461
462    #[allow(unused_variables)]
463    fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
464        Ok(())
465    }
466
467    #[allow(unused_variables)]
468    fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
469        Ok(())
470    }
471
472    #[allow(unused_variables)]
473    fn on_historical_index_prices(
474        &mut self,
475        index_prices: &[IndexPriceUpdate],
476    ) -> anyhow::Result<()> {
477        Ok(())
478    }
479
480    #[allow(unused_variables)]
481    fn on_historical_funding_rates(
482        &mut self,
483        funding_rates: &[FundingRateUpdate],
484    ) -> anyhow::Result<()> {
485        Ok(())
486    }
487}
488
489/// Returns a `*const ActorVTable` for the given [`PluginActor`] type.
490///
491/// One static vtable per monomorphisation via the `Tag<T>`-shaped pattern
492/// proven by [`custom_data_vtable`](crate::surfaces::custom_data::custom_data_vtable).
493#[must_use]
494pub fn actor_vtable<T>() -> *const ActorVTable
495where
496    T: PluginActor,
497{
498    &VTableTag::<T>::VTABLE
499}
500
501struct VTableTag<T>(PhantomData<T>);
502
503impl<T> VTableTag<T>
504where
505    T: PluginActor,
506{
507    const VTABLE: ActorVTable = ActorVTable {
508        create: Some(create_thunk::<T>),
509        drop_handle: Some(drop_handle_thunk::<T>),
510        type_name: Some(type_name_thunk::<T>),
511        on_start: Some(on_start_thunk::<T>),
512        on_stop: Some(on_stop_thunk::<T>),
513        on_resume: Some(on_resume_thunk::<T>),
514        on_reset: Some(on_reset_thunk::<T>),
515        on_dispose: Some(on_dispose_thunk::<T>),
516        on_degrade: Some(on_degrade_thunk::<T>),
517        on_fault: Some(on_fault_thunk::<T>),
518        on_time_event: Some(on_time_event_thunk::<T>),
519        on_data: Some(on_data_thunk::<T>),
520        on_instrument: Some(on_instrument_thunk::<T>),
521        on_book_deltas: Some(on_book_deltas_thunk::<T>),
522        on_book: Some(on_book_thunk::<T>),
523        on_quote: Some(on_quote_thunk::<T>),
524        on_trade: Some(on_trade_thunk::<T>),
525        on_bar: Some(on_bar_thunk::<T>),
526        on_mark_price: Some(on_mark_price_thunk::<T>),
527        on_index_price: Some(on_index_price_thunk::<T>),
528        on_funding_rate: Some(on_funding_rate_thunk::<T>),
529        on_option_greeks: Some(on_option_greeks_thunk::<T>),
530        on_option_chain: Some(on_option_chain_thunk::<T>),
531        on_instrument_status: Some(on_instrument_status_thunk::<T>),
532        on_instrument_close: Some(on_instrument_close_thunk::<T>),
533        on_order_filled: Some(on_order_filled_thunk::<T>),
534        on_order_canceled: Some(on_order_canceled_thunk::<T>),
535        on_signal: Some(on_signal_thunk::<T>),
536        on_historical_book_deltas: Some(on_historical_book_deltas_thunk::<T>),
537        on_historical_book_depth: Some(on_historical_book_depth_thunk::<T>),
538        on_historical_quotes: Some(on_historical_quotes_thunk::<T>),
539        on_historical_trades: Some(on_historical_trades_thunk::<T>),
540        on_historical_bars: Some(on_historical_bars_thunk::<T>),
541        on_historical_mark_prices: Some(on_historical_mark_prices_thunk::<T>),
542        on_historical_index_prices: Some(on_historical_index_prices_thunk::<T>),
543        on_historical_funding_rates: Some(on_historical_funding_rates_thunk::<T>),
544    };
545}
546
547unsafe extern "C" fn create_thunk<T: PluginActor>(
548    host: *const HostVTable,
549    ctx: *const HostContext,
550    config_json: BorrowedStr<'_>,
551) -> *mut PluginActorHandle {
552    guard_infallible("actor::create", || {
553        // SAFETY: host promises `config_json` borrows storage that is live
554        // for the duration of this call.
555        let cfg = unsafe { config_json.as_str() };
556        Box::into_raw(Box::new(T::new(host, ctx, cfg))).cast::<PluginActorHandle>()
557    })
558}
559
560unsafe extern "C" fn drop_handle_thunk<T: PluginActor>(handle: *mut PluginActorHandle) {
561    if handle.is_null() {
562        return;
563    }
564    guard_infallible("actor::drop", || {
565        // SAFETY: handle was allocated via `Box::into_raw(Box::new(T))`.
566        unsafe {
567            drop(Box::from_raw(handle.cast::<T>()));
568        }
569    });
570}
571
572unsafe extern "C" fn type_name_thunk<T: PluginActor>() -> BorrowedStr<'static> {
573    BorrowedStr::from_str(T::TYPE_NAME)
574}
575
576fn handle_as_mut<'a, T: PluginActor>(handle: *mut PluginActorHandle) -> &'a mut T {
577    // SAFETY: handle is non-null and originates from a `Box::into_raw` of a
578    // `T`. The host promises exclusive access while a callback is running.
579    unsafe { &mut *handle.cast::<T>() }
580}
581
582fn ok_or_err<E: ::core::fmt::Display>(r: Result<(), E>) -> Result<(), PluginError> {
583    r.map_err(|e| PluginError::new(PluginErrorCode::Generic, e.to_string()))
584}
585
586macro_rules! lifecycle_thunk {
587    ($name:ident, $method:ident) => {
588        unsafe extern "C" fn $name<T: PluginActor>(
589            handle: *mut PluginActorHandle,
590        ) -> PluginResult<()> {
591            guard(|| {
592                let actor = handle_as_mut::<T>(handle);
593                ok_or_err(actor.$method())
594            })
595        }
596    };
597}
598
599lifecycle_thunk!(on_start_thunk, on_start);
600lifecycle_thunk!(on_stop_thunk, on_stop);
601lifecycle_thunk!(on_resume_thunk, on_resume);
602lifecycle_thunk!(on_reset_thunk, on_reset);
603lifecycle_thunk!(on_dispose_thunk, on_dispose);
604lifecycle_thunk!(on_degrade_thunk, on_degrade);
605lifecycle_thunk!(on_fault_thunk, on_fault);
606
607macro_rules! event_thunk {
608    ($name:ident, $method:ident, $ty:ty) => {
609        unsafe extern "C" fn $name<T: PluginActor>(
610            handle: *mut PluginActorHandle,
611            value: *const $ty,
612        ) -> PluginResult<()> {
613            guard(|| {
614                // SAFETY: host keeps `value` live for the duration of the call;
615                // the plug-in only borrows it for the trait-method invocation.
616                let v = unsafe { &*value }.boundary_normalized();
617                let actor = handle_as_mut::<T>(handle);
618                ok_or_err(actor.$method(&v))
619            })
620        }
621    };
622}
623
624event_thunk!(on_time_event_thunk, on_time_event, TimeEvent);
625
626unsafe extern "C" fn on_data_thunk<T: PluginActor>(
627    handle: *mut PluginActorHandle,
628    data: PluginCustomDataRef,
629) -> PluginResult<()> {
630    guard(|| {
631        let actor = handle_as_mut::<T>(handle);
632        ok_or_err(actor.on_data(data))
633    })
634}
635
636unsafe extern "C" fn on_instrument_thunk<T: PluginActor>(
637    handle: *mut PluginActorHandle,
638    instrument: *const InstrumentAnyHandle,
639) -> PluginResult<()> {
640    guard(|| {
641        // SAFETY: host keeps the handle live for the duration of the call;
642        // the plug-in only borrows the wrapped instrument via the trait method.
643        let v: InstrumentAny = unsafe { (*instrument).instrument() }.boundary_normalized();
644        let actor = handle_as_mut::<T>(handle);
645        ok_or_err(actor.on_instrument(&v))
646    })
647}
648
649unsafe extern "C" fn on_book_deltas_thunk<T: PluginActor>(
650    handle: *mut PluginActorHandle,
651    deltas: *const OrderBookDeltasHandle,
652) -> PluginResult<()> {
653    guard(|| {
654        // SAFETY: host keeps the handle live for the duration of the call;
655        // the plug-in only borrows the wrapped deltas via the trait method.
656        let v: OrderBookDeltas = unsafe { (*deltas).deltas() }.boundary_normalized();
657        let actor = handle_as_mut::<T>(handle);
658        ok_or_err(actor.on_book_deltas(&v))
659    })
660}
661
662unsafe extern "C" fn on_book_thunk<T: PluginActor>(
663    handle: *mut PluginActorHandle,
664    book: *const OrderBookHandle,
665) -> PluginResult<()> {
666    guard(|| {
667        // SAFETY: host keeps the handle live for the duration of the call;
668        // the plug-in only borrows the wrapped book via the trait method.
669        let v: OrderBook = unsafe { (*book).book() }.boundary_normalized();
670        let actor = handle_as_mut::<T>(handle);
671        ok_or_err(actor.on_book(&v))
672    })
673}
674
675event_thunk!(on_quote_thunk, on_quote, QuoteTick);
676event_thunk!(on_trade_thunk, on_trade, TradeTick);
677event_thunk!(on_bar_thunk, on_bar, Bar);
678event_thunk!(on_mark_price_thunk, on_mark_price, MarkPriceUpdate);
679event_thunk!(on_index_price_thunk, on_index_price, IndexPriceUpdate);
680event_thunk!(on_funding_rate_thunk, on_funding_rate, FundingRateUpdate);
681event_thunk!(on_option_greeks_thunk, on_option_greeks, OptionGreeks);
682
683unsafe extern "C" fn on_option_chain_thunk<T: PluginActor>(
684    handle: *mut PluginActorHandle,
685    chain: *const OptionChainSliceHandle,
686) -> PluginResult<()> {
687    guard(|| {
688        // SAFETY: host keeps the handle live for the duration of the call;
689        // the plug-in only borrows the wrapped chain via the trait method.
690        let v: OptionChainSlice = unsafe { (*chain).chain() }.boundary_normalized();
691        let actor = handle_as_mut::<T>(handle);
692        ok_or_err(actor.on_option_chain(&v))
693    })
694}
695
696event_thunk!(
697    on_instrument_status_thunk,
698    on_instrument_status,
699    InstrumentStatus
700);
701event_thunk!(
702    on_instrument_close_thunk,
703    on_instrument_close,
704    InstrumentClose
705);
706event_thunk!(on_order_filled_thunk, on_order_filled, OrderFilled);
707event_thunk!(on_order_canceled_thunk, on_order_canceled, OrderCanceled);
708event_thunk!(on_signal_thunk, on_signal, Signal);
709
710macro_rules! slice_thunk {
711    ($name:ident, $method:ident, $ty:ty) => {
712        unsafe extern "C" fn $name<T: PluginActor>(
713            handle: *mut PluginActorHandle,
714            values: Slice<'_, $ty>,
715        ) -> PluginResult<()> {
716            guard(|| {
717                // SAFETY: host keeps the slice storage live for the call;
718                // the plug-in only borrows it for the trait-method invocation.
719                let v: Vec<$ty> = unsafe { values.as_slice() }
720                    .iter()
721                    .map(BoundaryNormalize::boundary_normalized)
722                    .collect();
723                let actor = handle_as_mut::<T>(handle);
724                ok_or_err(actor.$method(&v))
725            })
726        }
727    };
728}
729
730slice_thunk!(
731    on_historical_book_deltas_thunk,
732    on_historical_book_deltas,
733    OrderBookDelta
734);
735slice_thunk!(
736    on_historical_book_depth_thunk,
737    on_historical_book_depth,
738    OrderBookDepth10
739);
740slice_thunk!(on_historical_quotes_thunk, on_historical_quotes, QuoteTick);
741slice_thunk!(on_historical_trades_thunk, on_historical_trades, TradeTick);
742slice_thunk!(on_historical_bars_thunk, on_historical_bars, Bar);
743slice_thunk!(
744    on_historical_mark_prices_thunk,
745    on_historical_mark_prices,
746    MarkPriceUpdate
747);
748slice_thunk!(
749    on_historical_index_prices_thunk,
750    on_historical_index_prices,
751    IndexPriceUpdate
752);
753slice_thunk!(
754    on_historical_funding_rates_thunk,
755    on_historical_funding_rates,
756    FundingRateUpdate
757);