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