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(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#[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#[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#[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}