Skip to main content

nautilus_system/
clock_factory.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//! Clock construction for kernel and component clocks.
17
18use std::{
19    cell::{OnceCell, RefCell},
20    fmt::Debug,
21    rc::Rc,
22};
23
24use nautilus_common::{
25    clock::{Clock, TestClock},
26    enums::Environment,
27};
28
29/// Clock source for the kernel clock and fresh component clocks.
30///
31/// The primary clock is created lazily and shared by the kernel and trader timestamps. Component
32/// clocks are freshly constructed so callback registration remains isolated per component.
33#[derive(Clone)]
34pub struct ClockFactory {
35    clock: Rc<OnceCell<Rc<RefCell<dyn Clock>>>>,
36    create_clock: Rc<dyn Fn() -> Rc<RefCell<dyn Clock>>>,
37}
38
39impl ClockFactory {
40    /// Create a [`ClockFactory`] from a re-invocable closure.
41    #[must_use]
42    pub fn new<F>(factory: F) -> Self
43    where
44        F: Fn() -> Rc<RefCell<dyn Clock>> + 'static,
45    {
46        Self {
47            clock: Rc::new(OnceCell::new()),
48            create_clock: Rc::new(factory),
49        }
50    }
51
52    /// Create a test-default [`ClockFactory`].
53    #[must_use]
54    pub fn test_default() -> Self {
55        Self::for_environment(Environment::Backtest)
56    }
57
58    /// Create the default [`ClockFactory`] for an environment.
59    #[must_use]
60    pub fn for_environment(environment: Environment) -> Self {
61        match environment {
62            Environment::Backtest => Self::new(|| Rc::new(RefCell::new(TestClock::new()))),
63            Environment::Live | Environment::Sandbox => Self::live_default(),
64        }
65    }
66
67    /// Return the primary clock.
68    #[must_use]
69    pub fn clock(&self) -> Rc<RefCell<dyn Clock>> {
70        self.clock.get_or_init(|| (self.create_clock)()).clone()
71    }
72
73    /// Build a fresh component clock instance.
74    #[must_use]
75    pub fn create_component_clock(&self) -> Rc<RefCell<dyn Clock>> {
76        (self.create_clock)()
77    }
78
79    #[cfg(feature = "live")]
80    fn live_default() -> Self {
81        Self::new(|| {
82            Rc::new(RefCell::new(
83                nautilus_common::live::clock::LiveClock::default(), // nautilus-import-ok
84            ))
85        })
86    }
87
88    #[cfg(not(feature = "live"))]
89    fn live_default() -> Self {
90        Self::new(|| {
91            panic!(
92                "Live/Sandbox environment requires the 'live' feature to be enabled. \
93                 Build with `--features live` or supply a clock factory."
94            )
95        })
96    }
97}
98
99impl Debug for ClockFactory {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        f.debug_struct(stringify!(ClockFactory))
102            .finish_non_exhaustive()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use std::cell::Cell;
109
110    use rstest::rstest;
111
112    use super::*;
113
114    #[rstest]
115    fn test_clock_memoizes_primary_clock() {
116        let calls = Rc::new(Cell::new(0usize));
117        let calls_in_factory = calls.clone();
118        let factory = ClockFactory::new(move || {
119            calls_in_factory.set(calls_in_factory.get() + 1);
120            Rc::new(RefCell::new(TestClock::new()))
121        });
122
123        let first = factory.clock();
124        let second = factory.clock();
125
126        assert_eq!(calls.get(), 1);
127        assert!(Rc::ptr_eq(&first, &second));
128    }
129
130    #[rstest]
131    fn test_create_component_clock_returns_distinct_clocks() {
132        let factory = ClockFactory::test_default();
133
134        let first = factory.create_component_clock();
135        let second = factory.create_component_clock();
136
137        assert!(!Rc::ptr_eq(&first, &second));
138    }
139
140    #[rstest]
141    fn test_for_environment_backtest_uses_test_clock() {
142        let factory = ClockFactory::for_environment(Environment::Backtest);
143        let clock = factory.clock();
144
145        assert!(clock.borrow_mut().as_any_mut().is::<TestClock>());
146    }
147
148    #[rstest]
149    fn test_default_uses_test_clock() {
150        let factory = ClockFactory::test_default();
151        let clock = factory.clock();
152
153        assert!(clock.borrow_mut().as_any_mut().is::<TestClock>());
154    }
155}