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