nautilus_model/instruments/
index_instrument.rs1use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19 Params, UnixNanos,
20 correctness::{CorrectnessResult, CorrectnessResultExt, FAILED, check_equal_u8},
21};
22use serde::{Deserialize, Serialize};
23use ustr::Ustr;
24
25use super::{Instrument, any::InstrumentAny};
26use crate::{
27 enums::{AssetClass, InstrumentClass, OptionKind},
28 identifiers::{InstrumentId, Symbol},
29 types::{
30 currency::Currency,
31 money::Money,
32 price::{Price, check_positive_price},
33 quantity::{Quantity, check_positive_quantity},
34 },
35};
36
37#[repr(C)]
41#[derive(Clone, Debug, Serialize, Deserialize)]
42#[cfg_attr(
43 feature = "python",
44 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
45)]
46#[cfg_attr(
47 feature = "python",
48 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
49)]
50pub struct IndexInstrument {
51 pub id: InstrumentId,
53 pub raw_symbol: Symbol,
55 pub currency: Currency,
57 pub price_precision: u8,
59 pub size_precision: u8,
61 pub price_increment: Price,
63 pub size_increment: Quantity,
65 pub info: Option<Params>,
67 pub ts_event: UnixNanos,
69 pub ts_init: UnixNanos,
71}
72
73impl IndexInstrument {
74 #[expect(clippy::too_many_arguments)]
83 pub fn new_checked(
84 instrument_id: InstrumentId,
85 raw_symbol: Symbol,
86 currency: Currency,
87 price_precision: u8,
88 size_precision: u8,
89 price_increment: Price,
90 size_increment: Quantity,
91 info: Option<Params>,
92 ts_event: UnixNanos,
93 ts_init: UnixNanos,
94 ) -> CorrectnessResult<Self> {
95 check_equal_u8(
96 price_precision,
97 price_increment.precision,
98 stringify!(price_precision),
99 stringify!(price_increment.precision),
100 )?;
101 check_equal_u8(
102 size_precision,
103 size_increment.precision,
104 stringify!(size_precision),
105 stringify!(size_increment.precision),
106 )?;
107 check_positive_price(price_increment, stringify!(price_increment))?;
108 check_positive_quantity(size_increment, stringify!(size_increment))?;
109
110 Ok(Self {
111 id: instrument_id,
112 raw_symbol,
113 currency,
114 price_precision,
115 size_precision,
116 price_increment,
117 size_increment,
118 info,
119 ts_event,
120 ts_init,
121 })
122 }
123
124 #[expect(clippy::too_many_arguments)]
130 #[must_use]
131 pub fn new(
132 instrument_id: InstrumentId,
133 raw_symbol: Symbol,
134 currency: Currency,
135 price_precision: u8,
136 size_precision: u8,
137 price_increment: Price,
138 size_increment: Quantity,
139 info: Option<Params>,
140 ts_event: UnixNanos,
141 ts_init: UnixNanos,
142 ) -> Self {
143 Self::new_checked(
144 instrument_id,
145 raw_symbol,
146 currency,
147 price_precision,
148 size_precision,
149 price_increment,
150 size_increment,
151 info,
152 ts_event,
153 ts_init,
154 )
155 .expect_display(FAILED)
156 }
157}
158
159impl PartialEq<Self> for IndexInstrument {
160 fn eq(&self, other: &Self) -> bool {
161 self.id == other.id
162 }
163}
164
165impl Eq for IndexInstrument {}
166
167impl Hash for IndexInstrument {
168 fn hash<H: Hasher>(&self, state: &mut H) {
169 self.id.hash(state);
170 }
171}
172
173impl Instrument for IndexInstrument {
174 fn into_any(self) -> InstrumentAny {
175 InstrumentAny::IndexInstrument(self)
176 }
177
178 fn id(&self) -> InstrumentId {
179 self.id
180 }
181
182 fn raw_symbol(&self) -> Symbol {
183 self.raw_symbol
184 }
185
186 fn asset_class(&self) -> AssetClass {
187 AssetClass::Index
188 }
189
190 fn instrument_class(&self) -> InstrumentClass {
191 InstrumentClass::Spot
192 }
193
194 fn underlying(&self) -> Option<Ustr> {
195 None
196 }
197
198 fn base_currency(&self) -> Option<Currency> {
199 None
200 }
201
202 fn quote_currency(&self) -> Currency {
203 self.currency
204 }
205
206 fn settlement_currency(&self) -> Currency {
207 self.currency
208 }
209
210 fn isin(&self) -> Option<Ustr> {
211 None
212 }
213
214 fn option_kind(&self) -> Option<OptionKind> {
215 None
216 }
217
218 fn exchange(&self) -> Option<Ustr> {
219 None
220 }
221
222 fn strike_price(&self) -> Option<Price> {
223 None
224 }
225
226 fn activation_ns(&self) -> Option<UnixNanos> {
227 None
228 }
229
230 fn expiration_ns(&self) -> Option<UnixNanos> {
231 None
232 }
233
234 fn is_inverse(&self) -> bool {
235 false
236 }
237
238 fn price_precision(&self) -> u8 {
239 self.price_precision
240 }
241
242 fn size_precision(&self) -> u8 {
243 self.size_precision
244 }
245
246 fn price_increment(&self) -> Price {
247 self.price_increment
248 }
249
250 fn size_increment(&self) -> Quantity {
251 self.size_increment
252 }
253
254 fn multiplier(&self) -> Quantity {
255 Quantity::from(1)
256 }
257
258 fn lot_size(&self) -> Option<Quantity> {
259 None
260 }
261
262 fn max_quantity(&self) -> Option<Quantity> {
263 None
264 }
265
266 fn min_quantity(&self) -> Option<Quantity> {
267 None
268 }
269
270 fn max_notional(&self) -> Option<Money> {
271 None
272 }
273
274 fn min_notional(&self) -> Option<Money> {
275 None
276 }
277
278 fn max_price(&self) -> Option<Price> {
279 None
280 }
281
282 fn min_price(&self) -> Option<Price> {
283 None
284 }
285
286 fn ts_event(&self) -> UnixNanos {
287 self.ts_event
288 }
289
290 fn ts_init(&self) -> UnixNanos {
291 self.ts_init
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use rstest::rstest;
298
299 use crate::{
300 enums::{AssetClass, InstrumentClass},
301 identifiers::{InstrumentId, Symbol},
302 instruments::{IndexInstrument, Instrument, stubs::*},
303 types::{Currency, Price, Quantity},
304 };
305
306 #[rstest]
307 fn test_trait_accessors(index_instrument_spx: IndexInstrument) {
308 assert_eq!(index_instrument_spx.id(), InstrumentId::from("SPX.INDEX"));
309 assert_eq!(index_instrument_spx.asset_class(), AssetClass::Index);
310 assert_eq!(
311 index_instrument_spx.instrument_class(),
312 InstrumentClass::Spot
313 );
314 assert_eq!(index_instrument_spx.quote_currency(), Currency::USD());
315 assert!(!index_instrument_spx.is_inverse());
316 assert_eq!(index_instrument_spx.price_precision(), 2);
317 assert_eq!(index_instrument_spx.size_precision(), 0);
318 }
319
320 #[rstest]
321 fn test_new_checked_price_precision_mismatch() {
322 let result = IndexInstrument::new_checked(
323 InstrumentId::from("SPX.INDEX"),
324 Symbol::from("SPX"),
325 Currency::USD(),
326 4, 0,
328 Price::from("0.01"),
329 Quantity::from("1"),
330 None,
331 0.into(),
332 0.into(),
333 );
334 assert!(result.is_err());
335 }
336
337 #[rstest]
338 fn test_serialization_roundtrip(index_instrument_spx: IndexInstrument) {
339 let json = serde_json::to_string(&index_instrument_spx).unwrap();
340 let deserialized: IndexInstrument = serde_json::from_str(&json).unwrap();
341 assert_eq!(index_instrument_spx, deserialized);
342 }
343}