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