Skip to main content

nautilus_model/python/instruments/
tick_scheme.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//! Python access to named fixed and tiered tick schemes.
17
18use nautilus_core::{
19    correctness::check_in_range_inclusive_u8,
20    python::{to_pytype_err, to_pyvalue_err},
21};
22use pyo3::{prelude::*, types::PyAnyMethods};
23
24use crate::{
25    instruments::{
26        FixedTickScheme, TickScheme, TickSchemeRule, TieredTickScheme, get_tick_scheme,
27        list_tick_schemes, register_tick_scheme, tick_scheme::CRYPTO_0_01_TICK_SCHEME,
28    },
29    types::{Price, fixed::FIXED_PRECISION, price::PriceRaw},
30};
31
32#[derive(Clone, Debug)]
33#[pyo3::pyclass(
34    frozen,
35    name = "FixedTickScheme",
36    module = "nautilus_trader.model",
37    skip_from_py_object
38)]
39#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
40pub struct PyFixedTickScheme {
41    name: String,
42    scheme: FixedTickScheme,
43}
44
45#[pymethods]
46#[pyo3_stub_gen::derive::gen_stub_pymethods]
47impl PyFixedTickScheme {
48    #[new]
49    #[pyo3(signature = (name, price_precision, increment=None))]
50    fn py_new(name: String, price_precision: u8, increment: Option<Price>) -> PyResult<Self> {
51        check_in_range_inclusive_u8(price_precision, 0, FIXED_PRECISION, "precision")
52            .map_err(to_pyvalue_err)?;
53
54        let tick = increment.unwrap_or_else(|| {
55            let raw = PriceRaw::pow(10, u32::from(FIXED_PRECISION - price_precision));
56            Price::from_raw(raw, price_precision)
57        });
58
59        let scheme =
60            FixedTickScheme::new_with_precision(tick, price_precision).map_err(to_pyvalue_err)?;
61        Ok(Self { name, scheme })
62    }
63
64    #[getter]
65    fn name(&self) -> &str {
66        &self.name
67    }
68
69    #[getter]
70    fn price_precision(&self) -> u8 {
71        self.scheme.precision()
72    }
73
74    #[getter]
75    fn increment(&self) -> Price {
76        self.scheme.tick()
77    }
78
79    #[pyo3(signature = (value, n=0))]
80    fn next_bid_price(&self, value: f64, n: i32) -> PyResult<Option<Price>> {
81        check_tick_offset(n)?;
82        Ok(self
83            .scheme
84            .next_bid_price(value, n, self.scheme.precision()))
85    }
86
87    #[pyo3(signature = (value, n=0))]
88    fn next_ask_price(&self, value: f64, n: i32) -> PyResult<Option<Price>> {
89        check_tick_offset(n)?;
90        Ok(self
91            .scheme
92            .next_ask_price(value, n, self.scheme.precision()))
93    }
94}
95
96#[derive(Clone, Debug)]
97#[pyo3::pyclass(
98    frozen,
99    name = "TieredTickScheme",
100    module = "nautilus_trader.model",
101    skip_from_py_object
102)]
103#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
104pub struct PyTieredTickScheme {
105    name: String,
106    scheme: TieredTickScheme,
107}
108
109#[pymethods]
110#[pyo3_stub_gen::derive::gen_stub_pymethods]
111impl PyTieredTickScheme {
112    #[new]
113    #[pyo3(signature = (name, tiers, price_precision, max_ticks_per_tier=100))]
114    #[allow(
115        clippy::needless_pass_by_value,
116        reason = "PyO3 extracts tier definitions into an owned vector"
117    )]
118    fn py_new(
119        name: String,
120        tiers: Vec<(f64, f64, f64)>,
121        price_precision: u8,
122        max_ticks_per_tier: usize,
123    ) -> PyResult<Self> {
124        let scheme = TieredTickScheme::new(&tiers, price_precision, max_ticks_per_tier)
125            .map_err(to_pyvalue_err)?;
126        Ok(Self { name, scheme })
127    }
128
129    #[getter]
130    fn name(&self) -> &str {
131        &self.name
132    }
133
134    #[getter]
135    fn price_precision(&self) -> u8 {
136        self.scheme.precision()
137    }
138
139    #[getter]
140    fn min_price(&self) -> Price {
141        self.scheme.min_price()
142    }
143
144    #[getter]
145    fn max_price(&self) -> Price {
146        self.scheme.max_price()
147    }
148
149    #[getter]
150    fn ticks(&self) -> Vec<Price> {
151        self.scheme.ticks()
152    }
153
154    #[getter]
155    fn tick_count(&self) -> usize {
156        self.scheme.tick_count()
157    }
158
159    #[pyo3(signature = (value, n=0))]
160    fn next_bid_price(&self, value: f64, n: i32) -> PyResult<Option<Price>> {
161        check_tick_offset(n)?;
162        Ok(self
163            .scheme
164            .next_bid_price(value, n, self.scheme.precision()))
165    }
166
167    #[pyo3(signature = (value, n=0))]
168    fn next_ask_price(&self, value: f64, n: i32) -> PyResult<Option<Price>> {
169        check_tick_offset(n)?;
170        Ok(self
171            .scheme
172            .next_ask_price(value, n, self.scheme.precision()))
173    }
174}
175
176/// Registers a named tick scheme for the lifetime of the process.
177///
178/// Names are trimmed and matched without regard to ASCII case. Registered names,
179/// including built-in names and aliases, cannot be replaced or removed.
180///
181/// # Errors
182///
183/// Returns an error if the name is invalid or already registered.
184#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
185#[pyfunction(name = "register_tick_scheme")]
186pub fn py_register_tick_scheme(
187    #[gen_stub(override_type(type_repr = "FixedTickScheme | TieredTickScheme"))]
188    tick_scheme: &Bound<'_, PyAny>,
189) -> PyResult<()> {
190    if let Ok(fixed) = tick_scheme.extract::<PyRef<'_, PyFixedTickScheme>>() {
191        register_tick_scheme(&fixed.name, TickScheme::Fixed(fixed.scheme)).map_err(to_pyvalue_err)
192    } else if let Ok(tiered) = tick_scheme.extract::<PyRef<'_, PyTieredTickScheme>>() {
193        register_tick_scheme(&tiered.name, TickScheme::Tiered(tiered.scheme.clone()))
194            .map_err(to_pyvalue_err)
195    } else {
196        Err(to_pytype_err(
197            "tick_scheme must be a FixedTickScheme or TieredTickScheme",
198        ))
199    }
200}
201
202/// Returns a copy of a registered scheme, matching trimmed names without regard to ASCII case.
203#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
204#[pyfunction(name = "get_tick_scheme")]
205#[gen_stub(override_return_type(type_repr = "FixedTickScheme | TieredTickScheme"))]
206pub fn py_get_tick_scheme(py: Python<'_>, name: &str) -> PyResult<Py<PyAny>> {
207    let scheme = get_tick_scheme(name)
208        .ok_or_else(|| to_pyvalue_err(format!("unknown tick scheme {name}")))?;
209    let name = name.trim().to_ascii_uppercase();
210
211    match scheme {
212        TickScheme::Fixed(scheme) => {
213            Ok(Py::new(py, PyFixedTickScheme { name, scheme })?.into_any())
214        }
215        TickScheme::Tiered(scheme) => {
216            Ok(Py::new(py, PyTieredTickScheme { name, scheme })?.into_any())
217        }
218        TickScheme::Betfair => Ok(Py::new(
219            py,
220            PyTieredTickScheme {
221                name,
222                scheme: TieredTickScheme::betfair(),
223            },
224        )?
225        .into_any()),
226        TickScheme::Crypto => Ok(Py::new(
227            py,
228            PyFixedTickScheme {
229                name,
230                scheme: *CRYPTO_0_01_TICK_SCHEME,
231            },
232        )?
233        .into_any()),
234    }
235}
236
237/// Returns all registered names in uppercase, sorted lexicographically.
238#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
239#[pyfunction(name = "list_tick_schemes")]
240#[must_use]
241pub fn py_list_tick_schemes() -> Vec<String> {
242    list_tick_schemes()
243}
244
245fn check_tick_offset(n: i32) -> PyResult<()> {
246    if n < 0 {
247        return Err(to_pyvalue_err(format!("n must be >= 0, was {n}")));
248    }
249
250    Ok(())
251}