nautilus_model/python/instruments/
tick_scheme.rs1use 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#[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#[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#[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}