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