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