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