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