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