nautilus_model/instruments/
futures_spread.rs1use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19 UnixNanos,
20 correctness::{
21 FAILED, check_equal_u8, check_valid_string_ascii, check_valid_string_ascii_optional,
22 },
23};
24use rust_decimal::Decimal;
25use serde::{Deserialize, Serialize};
26use ustr::Ustr;
27
28use super::{Instrument, any::InstrumentAny};
29use crate::{
30 enums::{AssetClass, InstrumentClass, OptionKind},
31 identifiers::{InstrumentId, Symbol},
32 types::{
33 currency::Currency,
34 money::Money,
35 price::{Price, check_positive_price},
36 quantity::{Quantity, check_positive_quantity},
37 },
38};
39
40#[repr(C)]
42#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
46)]
47pub struct FuturesSpread {
48 pub id: InstrumentId,
50 pub raw_symbol: Symbol,
52 pub asset_class: AssetClass,
54 pub exchange: Option<Ustr>,
56 pub underlying: Ustr,
58 pub strategy_type: Ustr,
60 pub activation_ns: UnixNanos,
62 pub expiration_ns: UnixNanos,
64 pub currency: Currency,
66 pub price_precision: u8,
68 pub price_increment: Price,
70 pub size_increment: Quantity,
72 pub size_precision: u8,
74 pub multiplier: Quantity,
76 pub lot_size: Quantity,
78 pub margin_init: Decimal,
80 pub margin_maint: Decimal,
82 pub maker_fee: Decimal,
84 pub taker_fee: Decimal,
86 pub max_quantity: Option<Quantity>,
88 pub min_quantity: Option<Quantity>,
90 pub max_price: Option<Price>,
92 pub min_price: Option<Price>,
94 pub ts_event: UnixNanos,
96 pub ts_init: UnixNanos,
98}
99
100impl FuturesSpread {
101 #[allow(clippy::too_many_arguments)]
110 pub fn new_checked(
111 instrument_id: InstrumentId,
112 raw_symbol: Symbol,
113 asset_class: AssetClass,
114 exchange: Option<Ustr>,
115 underlying: Ustr,
116 strategy_type: Ustr,
117 activation_ns: UnixNanos,
118 expiration_ns: UnixNanos,
119 currency: Currency,
120 price_precision: u8,
121 price_increment: Price,
122 multiplier: Quantity,
123 lot_size: Quantity,
124 max_quantity: Option<Quantity>,
125 min_quantity: Option<Quantity>,
126 max_price: Option<Price>,
127 min_price: Option<Price>,
128 margin_init: Option<Decimal>,
129 margin_maint: Option<Decimal>,
130 maker_fee: Option<Decimal>,
131 taker_fee: Option<Decimal>,
132 ts_event: UnixNanos,
133 ts_init: UnixNanos,
134 ) -> anyhow::Result<Self> {
135 check_valid_string_ascii_optional(exchange.map(|u| u.as_str()), stringify!(isin))?;
136 check_valid_string_ascii(strategy_type.as_str(), stringify!(strategy_type))?;
137 check_equal_u8(
138 price_precision,
139 price_increment.precision,
140 stringify!(price_precision),
141 stringify!(price_increment.precision),
142 )?;
143 check_positive_price(price_increment, stringify!(price_increment))?;
144 check_positive_quantity(multiplier, stringify!(multiplier))?;
145 check_positive_quantity(lot_size, stringify!(lot_size))?;
146
147 Ok(Self {
148 id: instrument_id,
149 raw_symbol,
150 asset_class,
151 exchange,
152 underlying,
153 strategy_type,
154 activation_ns,
155 expiration_ns,
156 currency,
157 price_precision,
158 price_increment,
159 size_precision: 0,
160 size_increment: Quantity::from("1"),
161 multiplier,
162 lot_size,
163 margin_init: margin_init.unwrap_or_default(),
164 margin_maint: margin_maint.unwrap_or_default(),
165 maker_fee: maker_fee.unwrap_or_default(),
166 taker_fee: taker_fee.unwrap_or_default(),
167 max_quantity,
168 min_quantity: Some(min_quantity.unwrap_or(1.into())),
169 max_price,
170 min_price,
171 ts_event,
172 ts_init,
173 })
174 }
175
176 #[allow(clippy::too_many_arguments)]
182 pub fn new(
183 instrument_id: InstrumentId,
184 raw_symbol: Symbol,
185 asset_class: AssetClass,
186 exchange: Option<Ustr>,
187 underlying: Ustr,
188 strategy_type: Ustr,
189 activation_ns: UnixNanos,
190 expiration_ns: UnixNanos,
191 currency: Currency,
192 price_precision: u8,
193 price_increment: Price,
194 multiplier: Quantity,
195 lot_size: Quantity,
196 max_quantity: Option<Quantity>,
197 min_quantity: Option<Quantity>,
198 max_price: Option<Price>,
199 min_price: Option<Price>,
200 margin_init: Option<Decimal>,
201 margin_maint: Option<Decimal>,
202 maker_fee: Option<Decimal>,
203 taker_fee: Option<Decimal>,
204 ts_event: UnixNanos,
205 ts_init: UnixNanos,
206 ) -> Self {
207 Self::new_checked(
208 instrument_id,
209 raw_symbol,
210 asset_class,
211 exchange,
212 underlying,
213 strategy_type,
214 activation_ns,
215 expiration_ns,
216 currency,
217 price_precision,
218 price_increment,
219 multiplier,
220 lot_size,
221 max_quantity,
222 min_quantity,
223 max_price,
224 min_price,
225 margin_init,
226 margin_maint,
227 maker_fee,
228 taker_fee,
229 ts_event,
230 ts_init,
231 )
232 .expect(FAILED)
233 }
234}
235
236impl PartialEq<Self> for FuturesSpread {
237 fn eq(&self, other: &Self) -> bool {
238 self.id == other.id
239 }
240}
241
242impl Eq for FuturesSpread {}
243
244impl Hash for FuturesSpread {
245 fn hash<H: Hasher>(&self, state: &mut H) {
246 self.id.hash(state);
247 }
248}
249
250impl Instrument for FuturesSpread {
251 fn into_any(self) -> InstrumentAny {
252 InstrumentAny::FuturesSpread(self)
253 }
254
255 fn id(&self) -> InstrumentId {
256 self.id
257 }
258
259 fn raw_symbol(&self) -> Symbol {
260 self.raw_symbol
261 }
262
263 fn asset_class(&self) -> AssetClass {
264 self.asset_class
265 }
266
267 fn instrument_class(&self) -> InstrumentClass {
268 InstrumentClass::FuturesSpread
269 }
270 fn underlying(&self) -> Option<Ustr> {
271 Some(self.underlying)
272 }
273
274 fn base_currency(&self) -> Option<Currency> {
275 None
276 }
277
278 fn quote_currency(&self) -> Currency {
279 self.currency
280 }
281
282 fn settlement_currency(&self) -> Currency {
283 self.currency
284 }
285
286 fn isin(&self) -> Option<Ustr> {
287 None
288 }
289
290 fn option_kind(&self) -> Option<OptionKind> {
291 None
292 }
293
294 fn exchange(&self) -> Option<Ustr> {
295 self.exchange
296 }
297
298 fn strike_price(&self) -> Option<Price> {
299 None
300 }
301
302 fn activation_ns(&self) -> Option<UnixNanos> {
303 Some(self.activation_ns)
304 }
305
306 fn expiration_ns(&self) -> Option<UnixNanos> {
307 Some(self.expiration_ns)
308 }
309
310 fn is_inverse(&self) -> bool {
311 false
312 }
313
314 fn price_precision(&self) -> u8 {
315 self.price_precision
316 }
317
318 fn size_precision(&self) -> u8 {
319 0
320 }
321
322 fn price_increment(&self) -> Price {
323 self.price_increment
324 }
325
326 fn size_increment(&self) -> Quantity {
327 Quantity::from(1)
328 }
329
330 fn multiplier(&self) -> Quantity {
331 self.multiplier
332 }
333
334 fn lot_size(&self) -> Option<Quantity> {
335 Some(self.lot_size)
336 }
337
338 fn max_quantity(&self) -> Option<Quantity> {
339 self.max_quantity
340 }
341
342 fn min_quantity(&self) -> Option<Quantity> {
343 self.min_quantity
344 }
345
346 fn max_notional(&self) -> Option<Money> {
347 None
348 }
349
350 fn min_notional(&self) -> Option<Money> {
351 None
352 }
353
354 fn max_price(&self) -> Option<Price> {
355 self.max_price
356 }
357
358 fn min_price(&self) -> Option<Price> {
359 self.min_price
360 }
361
362 fn ts_event(&self) -> UnixNanos {
363 self.ts_event
364 }
365
366 fn ts_init(&self) -> UnixNanos {
367 self.ts_init
368 }
369}
370
371#[cfg(test)]
375mod tests {
376 use rstest::rstest;
377
378 use crate::instruments::{FuturesSpread, stubs::*};
379
380 #[rstest]
381 fn test_equality(futures_spread_es: FuturesSpread) {
382 assert_eq!(futures_spread_es, futures_spread_es.clone());
383 }
384}