nautilus_model/instruments/
futures_contract.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 FuturesContract {
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 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 FuturesContract {
99 #[allow(clippy::too_many_arguments)]
108 pub fn new_checked(
109 instrument_id: InstrumentId,
110 raw_symbol: Symbol,
111 asset_class: AssetClass,
112 exchange: Option<Ustr>,
113 underlying: Ustr,
114 activation_ns: UnixNanos,
115 expiration_ns: UnixNanos,
116 currency: Currency,
117 price_precision: u8,
118 price_increment: Price,
119 multiplier: Quantity,
120 lot_size: Quantity,
121 max_quantity: Option<Quantity>,
122 min_quantity: Option<Quantity>,
123 max_price: Option<Price>,
124 min_price: Option<Price>,
125 margin_init: Option<Decimal>,
126 margin_maint: Option<Decimal>,
127 maker_fee: Option<Decimal>,
128 taker_fee: Option<Decimal>,
129 ts_event: UnixNanos,
130 ts_init: UnixNanos,
131 ) -> anyhow::Result<Self> {
132 check_valid_string_ascii_optional(exchange.map(|u| u.as_str()), stringify!(isin))?;
133 check_valid_string_ascii(underlying.as_str(), stringify!(underlying))?;
134 check_equal_u8(
135 price_precision,
136 price_increment.precision,
137 stringify!(price_precision),
138 stringify!(price_increment.precision),
139 )?;
140 check_positive_price(price_increment, stringify!(price_increment))?;
141 check_positive_quantity(multiplier, stringify!(multiplier))?;
142 check_positive_quantity(lot_size, stringify!(lot_size))?;
143
144 Ok(Self {
145 id: instrument_id,
146 raw_symbol,
147 asset_class,
148 exchange,
149 underlying,
150 activation_ns,
151 expiration_ns,
152 currency,
153 price_precision,
154 price_increment,
155 size_precision: 0,
156 size_increment: Quantity::from(1),
157 multiplier,
158 lot_size,
159 max_quantity,
160 min_quantity: Some(min_quantity.unwrap_or(1.into())),
161 max_price,
162 min_price,
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 ts_event,
168 ts_init,
169 })
170 }
171
172 #[allow(clippy::too_many_arguments)]
178 pub fn new(
179 instrument_id: InstrumentId,
180 raw_symbol: Symbol,
181 asset_class: AssetClass,
182 exchange: Option<Ustr>,
183 underlying: Ustr,
184 activation_ns: UnixNanos,
185 expiration_ns: UnixNanos,
186 currency: Currency,
187 price_precision: u8,
188 price_increment: Price,
189 multiplier: Quantity,
190 lot_size: Quantity,
191 max_quantity: Option<Quantity>,
192 min_quantity: Option<Quantity>,
193 max_price: Option<Price>,
194 min_price: Option<Price>,
195 margin_init: Option<Decimal>,
196 margin_maint: Option<Decimal>,
197 maker_fee: Option<Decimal>,
198 taker_fee: Option<Decimal>,
199 ts_event: UnixNanos,
200 ts_init: UnixNanos,
201 ) -> Self {
202 Self::new_checked(
203 instrument_id,
204 raw_symbol,
205 asset_class,
206 exchange,
207 underlying,
208 activation_ns,
209 expiration_ns,
210 currency,
211 price_precision,
212 price_increment,
213 multiplier,
214 lot_size,
215 max_quantity,
216 min_quantity,
217 max_price,
218 min_price,
219 margin_init,
220 margin_maint,
221 maker_fee,
222 taker_fee,
223 ts_event,
224 ts_init,
225 )
226 .expect(FAILED)
227 }
228}
229
230impl PartialEq<Self> for FuturesContract {
231 fn eq(&self, other: &Self) -> bool {
232 self.id == other.id
233 }
234}
235
236impl Eq for FuturesContract {}
237
238impl Hash for FuturesContract {
239 fn hash<H: Hasher>(&self, state: &mut H) {
240 self.id.hash(state);
241 }
242}
243
244impl Instrument for FuturesContract {
245 fn into_any(self) -> InstrumentAny {
246 InstrumentAny::FuturesContract(self)
247 }
248
249 fn id(&self) -> InstrumentId {
250 self.id
251 }
252
253 fn raw_symbol(&self) -> Symbol {
254 self.raw_symbol
255 }
256
257 fn asset_class(&self) -> AssetClass {
258 self.asset_class
259 }
260
261 fn instrument_class(&self) -> InstrumentClass {
262 InstrumentClass::Future
263 }
264 fn underlying(&self) -> Option<Ustr> {
265 Some(self.underlying)
266 }
267
268 fn base_currency(&self) -> Option<Currency> {
269 None
270 }
271
272 fn quote_currency(&self) -> Currency {
273 self.currency
274 }
275
276 fn settlement_currency(&self) -> Currency {
277 self.currency
278 }
279
280 fn isin(&self) -> Option<Ustr> {
281 None
282 }
283
284 fn option_kind(&self) -> Option<OptionKind> {
285 None
286 }
287
288 fn exchange(&self) -> Option<Ustr> {
289 self.exchange
290 }
291
292 fn strike_price(&self) -> Option<Price> {
293 None
294 }
295
296 fn activation_ns(&self) -> Option<UnixNanos> {
297 Some(self.activation_ns)
298 }
299
300 fn expiration_ns(&self) -> Option<UnixNanos> {
301 Some(self.expiration_ns)
302 }
303
304 fn is_inverse(&self) -> bool {
305 false
306 }
307
308 fn price_precision(&self) -> u8 {
309 self.price_precision
310 }
311
312 fn size_precision(&self) -> u8 {
313 0
314 }
315
316 fn price_increment(&self) -> Price {
317 self.price_increment
318 }
319
320 fn size_increment(&self) -> Quantity {
321 Quantity::from(1)
322 }
323
324 fn multiplier(&self) -> Quantity {
325 self.multiplier
326 }
327
328 fn lot_size(&self) -> Option<Quantity> {
329 Some(self.lot_size)
330 }
331
332 fn max_quantity(&self) -> Option<Quantity> {
333 self.max_quantity
334 }
335
336 fn min_quantity(&self) -> Option<Quantity> {
337 self.min_quantity
338 }
339
340 fn max_notional(&self) -> Option<Money> {
341 None
342 }
343
344 fn min_notional(&self) -> Option<Money> {
345 None
346 }
347
348 fn max_price(&self) -> Option<Price> {
349 self.max_price
350 }
351
352 fn min_price(&self) -> Option<Price> {
353 self.min_price
354 }
355
356 fn ts_event(&self) -> UnixNanos {
357 self.ts_event
358 }
359
360 fn ts_init(&self) -> UnixNanos {
361 self.ts_init
362 }
363}
364
365#[cfg(test)]
369mod tests {
370 use rstest::rstest;
371
372 use crate::instruments::stubs::*;
373
374 #[rstest]
375 fn test_equality() {
376 let futures_contract = futures_contract_es(None, None);
377 assert_eq!(futures_contract, futures_contract.clone());
378 }
379}