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