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