Skip to main content

nautilus_trading/algorithm/
core.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//! Core component for execution algorithms.
17
18use std::{cell::RefCell, fmt::Debug, rc::Rc};
19
20use ahash::{AHashMap, AHashSet};
21use indexmap::IndexMap;
22use nautilus_common::{
23    actor::{DataActorConfig, DataActorCore, DataActorNative},
24    cache::Cache,
25    clock::Clock,
26    msgbus::TypedHandler,
27};
28use nautilus_core::Params;
29use nautilus_model::{
30    events::{OrderEventAny, PositionEvent},
31    identifiers::{ActorId, ClientOrderId, ExecAlgorithmId, StrategyId, TraderId},
32    orders::{OrderAny, OrderList},
33    types::Quantity,
34};
35use nautilus_portfolio::portfolio::Portfolio;
36
37use super::config::ExecutionAlgorithmConfig;
38
39/// Holds event handlers for strategy event subscriptions.
40#[derive(Clone, Debug)]
41pub struct StrategyEventHandlers {
42    /// The topic string for order events.
43    pub order_topic: String,
44    /// The handler for order events.
45    pub order_handler: TypedHandler<OrderEventAny>,
46    /// The topic string for position events.
47    pub position_topic: String,
48    /// The handler for position events.
49    pub position_handler: TypedHandler<PositionEvent>,
50}
51
52#[derive(Clone, Copy, Debug)]
53pub(crate) struct SpawnReduction {
54    /// The primary order whose budget was reduced.
55    pub primary_id: ClientOrderId,
56    /// The quantity deducted from the primary at spawn time.
57    pub deducted_qty: Quantity,
58    /// Whether the spawned quantity was quote-denominated at deduction time.
59    pub spawn_was_quote_quantity: bool,
60    /// The unfilled budget already released, including debt discharge.
61    /// `None` means restoration has not occurred; zero remains tracked for corrections.
62    pub restored_qty: Option<Quantity>,
63}
64
65/// The core component of an [`ExecutionAlgorithm`](super::ExecutionAlgorithm).
66///
67/// This struct manages the internal state for execution algorithms including
68/// spawn ID tracking and strategy subscriptions. It wraps a [`DataActorCore`]
69/// to provide data actor capabilities.
70///
71/// User algorithms should hold this as a member and use the
72/// `nautilus_execution_algorithm!` macro to provide native runtime wiring.
73/// Direct access to this core is native runtime wiring and belongs behind
74/// [`ExecutionAlgorithmNative`].
75pub struct ExecutionAlgorithmCore {
76    /// The underlying data actor core.
77    pub actor: DataActorCore,
78    /// The execution algorithm configuration.
79    pub config: ExecutionAlgorithmConfig,
80    /// The execution algorithm ID.
81    pub exec_algorithm_id: ExecAlgorithmId,
82    /// Maps primary order client IDs to their spawn sequence counter.
83    exec_spawn_ids: AHashMap<ClientOrderId, u32>,
84    /// Tracks strategies that have been subscribed to for events.
85    subscribed_strategies: AHashSet<StrategyId>,
86    /// Tracks spawn reductions through restoration and possible late-fill netting.
87    spawn_reductions: AHashMap<ClientOrderId, SpawnReduction>,
88    /// Tracks uncompensated late-fill quantity per primary order, discharged
89    /// against later spawn restorations.
90    spawn_fill_debts: AHashMap<ClientOrderId, Quantity>,
91    /// Tracks submission handoffs until the cached primary leaves local control.
92    handed_off_primaries: AHashSet<ClientOrderId>,
93    /// Maps primary order client IDs to the command params supplied at submission.
94    submit_params: AHashMap<ClientOrderId, Params>,
95    /// The portfolio shared by the trader.
96    portfolio: Option<Rc<RefCell<Portfolio>>>,
97    /// Maps strategies to their event handlers for cleanup on reset.
98    strategy_event_handlers: IndexMap<StrategyId, StrategyEventHandlers>,
99}
100
101/// Native-only access to internal execution algorithm runtime state.
102///
103/// Use this trait from engine, runtime, testkit, or opt-in native algorithm
104/// code when direct access to host runtime objects matters for an explicit
105/// latency-sensitive path, or when host integration code needs access below
106/// the facade API.
107///
108/// Do not import this trait in code intended to run through Python or the
109/// plug-in authoring surface. Native borrows, `Rc<RefCell<_>>`, and core
110/// references do not cross those boundaries.
111pub trait ExecutionAlgorithmNative: DataActorNative {
112    /// Returns the execution algorithm core.
113    fn exec_algorithm_core(&self) -> &ExecutionAlgorithmCore;
114
115    /// Returns the mutable execution algorithm core.
116    fn exec_algorithm_core_mut(&mut self) -> &mut ExecutionAlgorithmCore;
117
118    /// Returns a clone of the reference-counted portfolio.
119    ///
120    /// # Panics
121    ///
122    /// Panics if the execution algorithm has not been registered.
123    fn portfolio_rc(&self) -> Rc<RefCell<Portfolio>> {
124        self.exec_algorithm_core()
125            .portfolio
126            .as_ref()
127            .expect("ExecutionAlgorithm not registered: Portfolio not initialized")
128            .clone()
129    }
130}
131
132impl Debug for ExecutionAlgorithmCore {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        f.debug_struct(stringify!(ExecutionAlgorithmCore))
135            .field("actor", &self.actor)
136            .field("config", &self.config)
137            .field("exec_algorithm_id", &self.exec_algorithm_id)
138            .field("exec_spawn_ids", &self.exec_spawn_ids.len())
139            .field("subscribed_strategies", &self.subscribed_strategies.len())
140            .field("spawn_reductions", &self.spawn_reductions.len())
141            .field("spawn_fill_debts", &self.spawn_fill_debts.len())
142            .field("handed_off_primaries", &self.handed_off_primaries.len())
143            .field("submit_params", &self.submit_params.len())
144            .field(
145                "strategy_event_handlers",
146                &self.strategy_event_handlers.len(),
147            )
148            .finish()
149    }
150}
151
152impl ExecutionAlgorithmCore {
153    /// Creates a new [`ExecutionAlgorithmCore`] instance.
154    ///
155    /// # Panics
156    ///
157    /// Panics if `config.exec_algorithm_id` is `None`.
158    #[must_use]
159    pub fn new(config: ExecutionAlgorithmConfig) -> Self {
160        let exec_algorithm_id = config
161            .exec_algorithm_id
162            .expect("ExecutionAlgorithmConfig must have exec_algorithm_id set");
163
164        let actor_config = DataActorConfig {
165            actor_id: Some(ActorId::new(exec_algorithm_id.inner())),
166            log_events: config.log_events,
167            log_commands: config.log_commands,
168        };
169
170        Self {
171            actor: DataActorCore::new(actor_config),
172            config,
173            exec_algorithm_id,
174            exec_spawn_ids: AHashMap::new(),
175            subscribed_strategies: AHashSet::new(),
176            spawn_reductions: AHashMap::new(),
177            spawn_fill_debts: AHashMap::new(),
178            handed_off_primaries: AHashSet::new(),
179            submit_params: AHashMap::new(),
180            portfolio: None,
181            strategy_event_handlers: IndexMap::new(),
182        }
183    }
184
185    /// Registers the execution algorithm with the trading engine components.
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if registration with the actor core fails.
190    pub fn register(
191        &mut self,
192        trader_id: TraderId,
193        clock: Rc<RefCell<dyn Clock>>,
194        cache: Rc<RefCell<Cache>>,
195    ) -> anyhow::Result<()> {
196        self.actor.register(trader_id, clock, cache)
197    }
198
199    /// Returns the execution algorithm ID.
200    #[must_use]
201    pub fn id(&self) -> ExecAlgorithmId {
202        self.exec_algorithm_id
203    }
204
205    /// Sets the portfolio shared by the trader.
206    pub fn set_portfolio(&mut self, portfolio: Rc<RefCell<Portfolio>>) {
207        self.portfolio = Some(portfolio);
208    }
209
210    /// Generates the next spawn client order ID for a primary order.
211    ///
212    /// The generated ID follows the pattern: `{primary_id}-E{sequence}`.
213    #[must_use]
214    pub fn spawn_client_order_id(&mut self, primary_id: &ClientOrderId) -> ClientOrderId {
215        let sequence = self
216            .exec_spawn_ids
217            .entry(*primary_id)
218            .and_modify(|s| *s += 1)
219            .or_insert(1);
220
221        ClientOrderId::new(format!("{primary_id}-E{sequence}"))
222    }
223
224    /// Returns the current spawn sequence for a primary order, if any.
225    #[must_use]
226    pub fn spawn_sequence(&self, primary_id: &ClientOrderId) -> Option<u32> {
227        self.exec_spawn_ids.get(primary_id).copied()
228    }
229
230    /// Checks if a strategy has been subscribed to for events.
231    #[must_use]
232    pub fn is_strategy_subscribed(&self, strategy_id: &StrategyId) -> bool {
233        self.subscribed_strategies.contains(strategy_id)
234    }
235
236    /// Marks a strategy as subscribed for events.
237    pub fn add_subscribed_strategy(&mut self, strategy_id: StrategyId) {
238        self.subscribed_strategies.insert(strategy_id);
239    }
240
241    /// Stores the event handlers for a strategy subscription.
242    pub fn store_strategy_event_handlers(
243        &mut self,
244        strategy_id: StrategyId,
245        handlers: StrategyEventHandlers,
246    ) {
247        self.strategy_event_handlers.insert(strategy_id, handlers);
248    }
249
250    /// Takes and returns all stored strategy event handlers, clearing the internal map.
251    pub fn take_strategy_event_handlers(&mut self) -> IndexMap<StrategyId, StrategyEventHandlers> {
252        std::mem::take(&mut self.strategy_event_handlers)
253    }
254
255    /// Clears spawn ID tracking state.
256    pub fn clear_spawn_ids(&mut self) {
257        self.exec_spawn_ids.clear();
258    }
259
260    /// Clears all strategy subscriptions.
261    pub fn clear_subscribed_strategies(&mut self) {
262        self.subscribed_strategies.clear();
263    }
264
265    /// Tracks a pending spawn reduction for potential restoration.
266    ///
267    /// Associates `spawn_id` with the `primary_id` whose quantity was reduced.
268    pub fn track_pending_spawn_reduction(
269        &mut self,
270        spawn_id: ClientOrderId,
271        primary_id: ClientOrderId,
272        quantity: Quantity,
273        spawn_was_quote_quantity: bool,
274    ) {
275        self.spawn_reductions.insert(
276            spawn_id,
277            SpawnReduction {
278                primary_id,
279                deducted_qty: quantity,
280                spawn_was_quote_quantity,
281                restored_qty: None,
282            },
283        );
284    }
285
286    /// Returns the spawn reduction lifecycle record for an order, if any.
287    #[must_use]
288    pub(crate) fn spawn_reduction(&self, spawn_id: ClientOrderId) -> Option<SpawnReduction> {
289        self.spawn_reductions.get(&spawn_id).copied()
290    }
291
292    /// Updates the spawn reduction lifecycle record for an order.
293    pub(crate) fn set_spawn_reduction(
294        &mut self,
295        spawn_id: ClientOrderId,
296        reduction: SpawnReduction,
297    ) {
298        self.spawn_reductions.insert(spawn_id, reduction);
299    }
300
301    /// Removes and returns the spawn reduction lifecycle record for an order, if any.
302    pub(crate) fn take_pending_spawn_reduction(
303        &mut self,
304        spawn_id: ClientOrderId,
305    ) -> Option<SpawnReduction> {
306        self.spawn_reductions.remove(&spawn_id)
307    }
308
309    /// Returns the uncompensated late-fill debt for a primary order, if any.
310    #[must_use]
311    pub(crate) fn spawn_fill_debt(&self, primary_id: ClientOrderId) -> Option<Quantity> {
312        self.spawn_fill_debts.get(&primary_id).copied()
313    }
314
315    /// Adds uncompensated late-fill debt against a primary order.
316    pub(crate) fn add_spawn_fill_debt(&mut self, primary_id: ClientOrderId, quantity: Quantity) {
317        self.spawn_fill_debts
318            .entry(primary_id)
319            .and_modify(|debt| {
320                let precision = debt.precision;
321                *debt = *debt + quantity;
322                debt.precision = precision;
323            })
324            .or_insert(quantity);
325    }
326
327    /// Sets the uncompensated late-fill debt for a primary order, removing it at zero.
328    pub(crate) fn set_spawn_fill_debt(&mut self, primary_id: ClientOrderId, quantity: Quantity) {
329        if quantity.is_zero() {
330            self.spawn_fill_debts.remove(&primary_id);
331        } else {
332            self.spawn_fill_debts.insert(primary_id, quantity);
333        }
334    }
335
336    /// Marks a primary order as handed off for submission and clears its spawn accounting.
337    pub(crate) fn mark_primary_handed_off(&mut self, primary_id: ClientOrderId) {
338        self.clear_primary_spawn_state(primary_id);
339        self.handed_off_primaries.insert(primary_id);
340    }
341
342    /// Clears accounting once the primary can no longer be changed locally.
343    pub(crate) fn clear_primary_spawn_state(&mut self, primary_id: ClientOrderId) {
344        self.spawn_reductions
345            .retain(|_, reduction| reduction.primary_id != primary_id);
346        self.spawn_fill_debts.remove(&primary_id);
347        self.handed_off_primaries.remove(&primary_id);
348    }
349
350    /// Returns whether a primary submission is awaiting a non-local cached status.
351    #[must_use]
352    pub(crate) fn primary_was_handed_off(&self, primary_id: ClientOrderId) -> bool {
353        self.handed_off_primaries.contains(&primary_id)
354    }
355
356    /// Discards debt that can no longer be discharged after primary submission.
357    pub(crate) fn discard_spawn_fill_debt(&mut self, primary_id: ClientOrderId) {
358        self.spawn_fill_debts.remove(&primary_id);
359    }
360
361    /// Clears all pending spawn reductions.
362    pub fn clear_pending_spawn_reductions(&mut self) {
363        self.spawn_reductions.clear();
364        self.spawn_fill_debts.clear();
365    }
366
367    /// Stores the command params supplied with a primary order submission.
368    ///
369    /// A `None` or empty params map is ignored, so no lookup is created for orders without params.
370    pub fn remember_submit_params(&mut self, primary_id: ClientOrderId, params: Option<Params>) {
371        if let Some(params) = params
372            && !params.is_empty()
373        {
374            self.submit_params.insert(primary_id, params);
375        }
376    }
377
378    /// Returns a clone of the submit command params stored for a primary order, if any.
379    #[must_use]
380    pub fn submit_params(&self, primary_id: &ClientOrderId) -> Option<Params> {
381        self.submit_params.get(primary_id).cloned()
382    }
383
384    /// Removes the stored submit command params for a primary order.
385    pub fn remove_submit_params(&mut self, primary_id: &ClientOrderId) {
386        self.submit_params.remove(primary_id);
387    }
388
389    /// Clears all stored submit command params.
390    pub fn clear_submit_params(&mut self) {
391        self.submit_params.clear();
392    }
393
394    /// Resets the core to its initial state.
395    ///
396    /// Note: This clears handler storage but does NOT unsubscribe from msgbus.
397    /// Call `unsubscribe_all_strategy_events` first to properly unsubscribe.
398    pub fn reset(&mut self) {
399        self.exec_spawn_ids.clear();
400        self.subscribed_strategies.clear();
401        self.spawn_reductions.clear();
402        self.spawn_fill_debts.clear();
403        self.handed_off_primaries.clear();
404        self.submit_params.clear();
405        self.strategy_event_handlers.clear();
406    }
407
408    /// Returns the order for the given client order ID from the cache.
409    ///
410    /// # Errors
411    ///
412    /// Returns an error if the order is not found in the cache.
413    pub fn get_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<OrderAny> {
414        Ok(self.cache_ref().try_order_owned(client_order_id)?)
415    }
416
417    /// Returns all orders for the given order list from the cache.
418    ///
419    /// # Errors
420    ///
421    /// Returns an error if any order is not found in the cache.
422    pub fn get_orders_for_list(&self, order_list: &OrderList) -> anyhow::Result<Vec<OrderAny>> {
423        order_list
424            .client_order_ids
425            .iter()
426            .map(|id| self.get_order(id))
427            .collect()
428    }
429}
430
431impl DataActorNative for ExecutionAlgorithmCore {
432    fn core(&self) -> &DataActorCore {
433        &self.actor
434    }
435
436    fn core_mut(&mut self) -> &mut DataActorCore {
437        &mut self.actor
438    }
439}
440
441impl ExecutionAlgorithmNative for ExecutionAlgorithmCore {
442    fn exec_algorithm_core(&self) -> &ExecutionAlgorithmCore {
443        self
444    }
445
446    fn exec_algorithm_core_mut(&mut self) -> &mut ExecutionAlgorithmCore {
447        self
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use rstest::rstest;
454
455    use super::*;
456
457    fn create_test_config() -> ExecutionAlgorithmConfig {
458        ExecutionAlgorithmConfig {
459            exec_algorithm_id: Some(ExecAlgorithmId::new("TWAP")),
460            ..Default::default()
461        }
462    }
463
464    #[rstest]
465    fn test_core_new() {
466        let config = create_test_config();
467        let core = ExecutionAlgorithmCore::new(config.clone());
468
469        assert_eq!(core.exec_algorithm_id, ExecAlgorithmId::new("TWAP"));
470        assert_eq!(core.config.log_events, config.log_events);
471        assert!(core.exec_spawn_ids.is_empty());
472        assert!(core.subscribed_strategies.is_empty());
473    }
474
475    #[rstest]
476    fn test_spawn_client_order_id_sequence() {
477        let config = create_test_config();
478        let mut core = ExecutionAlgorithmCore::new(config);
479
480        let primary_id = ClientOrderId::new("O-001");
481
482        let spawn1 = core.spawn_client_order_id(&primary_id);
483        assert_eq!(spawn1.as_str(), "O-001-E1");
484
485        let spawn2 = core.spawn_client_order_id(&primary_id);
486        assert_eq!(spawn2.as_str(), "O-001-E2");
487
488        let spawn3 = core.spawn_client_order_id(&primary_id);
489        assert_eq!(spawn3.as_str(), "O-001-E3");
490    }
491
492    #[rstest]
493    fn test_spawn_client_order_id_different_primaries() {
494        let config = create_test_config();
495        let mut core = ExecutionAlgorithmCore::new(config);
496
497        let primary1 = ClientOrderId::new("O-001");
498        let primary2 = ClientOrderId::new("O-002");
499
500        let spawn1_1 = core.spawn_client_order_id(&primary1);
501        let spawn2_1 = core.spawn_client_order_id(&primary2);
502        let spawn1_2 = core.spawn_client_order_id(&primary1);
503
504        assert_eq!(spawn1_1.as_str(), "O-001-E1");
505        assert_eq!(spawn2_1.as_str(), "O-002-E1");
506        assert_eq!(spawn1_2.as_str(), "O-001-E2");
507    }
508
509    #[rstest]
510    fn test_spawn_sequence() {
511        let config = create_test_config();
512        let mut core = ExecutionAlgorithmCore::new(config);
513
514        let primary_id = ClientOrderId::new("O-001");
515
516        assert_eq!(core.spawn_sequence(&primary_id), None);
517
518        let _ = core.spawn_client_order_id(&primary_id);
519        assert_eq!(core.spawn_sequence(&primary_id), Some(1));
520
521        let _ = core.spawn_client_order_id(&primary_id);
522        assert_eq!(core.spawn_sequence(&primary_id), Some(2));
523    }
524
525    #[rstest]
526    fn test_strategy_subscription_tracking() {
527        let config = create_test_config();
528        let mut core = ExecutionAlgorithmCore::new(config);
529
530        let strategy_id = StrategyId::new("TEST-001");
531
532        assert!(!core.is_strategy_subscribed(&strategy_id));
533
534        core.add_subscribed_strategy(strategy_id);
535        assert!(core.is_strategy_subscribed(&strategy_id));
536    }
537
538    #[rstest]
539    fn test_clear_spawn_ids() {
540        let config = create_test_config();
541        let mut core = ExecutionAlgorithmCore::new(config);
542
543        let primary_id = ClientOrderId::new("O-001");
544        let _ = core.spawn_client_order_id(&primary_id);
545
546        assert!(core.spawn_sequence(&primary_id).is_some());
547
548        core.clear_spawn_ids();
549        assert!(core.spawn_sequence(&primary_id).is_none());
550    }
551
552    #[rstest]
553    fn test_remove_submit_params_only_removes_requested_primary() {
554        let config = create_test_config();
555        let mut core = ExecutionAlgorithmCore::new(config);
556        let primary1 = ClientOrderId::new("O-001");
557        let primary2 = ClientOrderId::new("O-002");
558        let mut params1 = Params::new();
559        params1.insert(
560            "route".to_string(),
561            serde_json::Value::String("A".to_string()),
562        );
563        let mut params2 = Params::new();
564        params2.insert(
565            "route".to_string(),
566            serde_json::Value::String("B".to_string()),
567        );
568
569        core.remember_submit_params(primary1, Some(params1));
570        core.remember_submit_params(primary2, Some(params2.clone()));
571        core.remove_submit_params(&primary1);
572
573        assert_eq!(core.submit_params(&primary1), None);
574        assert_eq!(core.submit_params(&primary2), Some(params2));
575    }
576
577    #[rstest]
578    fn test_primary_handoff_clears_only_its_spawn_state() {
579        let mut core = ExecutionAlgorithmCore::new(create_test_config());
580        let primary_a = ClientOrderId::from("A");
581        let primary_b = ClientOrderId::from("B");
582        let primary_c = ClientOrderId::from("C");
583        let child_a = ClientOrderId::from("A-E1");
584        let child_b = ClientOrderId::from("B-E1");
585        core.track_pending_spawn_reduction(child_a, primary_a, Quantity::from("0.3"), false);
586        core.track_pending_spawn_reduction(child_b, primary_b, Quantity::from("0.7"), true);
587        core.add_spawn_fill_debt(primary_a, Quantity::from("0.1"));
588        core.add_spawn_fill_debt(primary_b, Quantity::from("0.2"));
589
590        core.mark_primary_handed_off(primary_c);
591        core.mark_primary_handed_off(primary_a);
592
593        assert!(core.spawn_reduction(child_a).is_none());
594        assert!(core.spawn_fill_debt(primary_a).is_none());
595        assert!(core.primary_was_handed_off(primary_a));
596        assert!(!core.primary_was_handed_off(primary_b));
597        assert!(core.primary_was_handed_off(primary_c));
598        let retained = core.spawn_reduction(child_b).unwrap();
599        assert_eq!(retained.primary_id, primary_b);
600        assert_eq!(retained.deducted_qty, Quantity::from("0.7"));
601        assert!(retained.spawn_was_quote_quantity);
602        assert_eq!(retained.restored_qty, None);
603        assert_eq!(core.spawn_fill_debt(primary_b), Some(Quantity::from("0.2")));
604    }
605
606    #[rstest]
607    fn test_reset() {
608        let config = create_test_config();
609        let mut core = ExecutionAlgorithmCore::new(config);
610
611        let primary_id = ClientOrderId::new("O-001");
612        let strategy_id = StrategyId::new("TEST-001");
613
614        let _ = core.spawn_client_order_id(&primary_id);
615        core.add_subscribed_strategy(strategy_id);
616        core.mark_primary_handed_off(primary_id);
617
618        core.reset();
619
620        assert!(core.spawn_sequence(&primary_id).is_none());
621        assert!(!core.is_strategy_subscribed(&strategy_id));
622        assert!(!core.primary_was_handed_off(primary_id));
623    }
624
625    #[rstest]
626    fn test_data_actor_core_available_through_native_trait() {
627        let config = create_test_config();
628        let core = ExecutionAlgorithmCore::new(config);
629
630        assert!(DataActorNative::core(&core).trader_id().is_none());
631    }
632}