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