nautilus_model/python/data/
option_chain.rs1use std::collections::BTreeMap;
17
18use nautilus_core::UnixNanos;
19use pyo3::prelude::*;
20
21use crate::{
22 data::{
23 QuoteTick,
24 greeks::OptionGreekValues,
25 option_chain::{OptionChainSlice, OptionGreeks, OptionStrikeData, StrikeRange},
26 },
27 enums::GreeksConvention,
28 identifiers::{InstrumentId, OptionSeriesId},
29 types::Price,
30};
31
32#[pyclass(
34 name = "StrikeRange",
35 module = "nautilus_trader.core.nautilus_pyo3.model",
36 from_py_object
37)]
38#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
39#[derive(Clone, Debug)]
40pub struct PyStrikeRange {
41 pub inner: StrikeRange,
42}
43
44#[pymethods]
45#[pyo3_stub_gen::derive::gen_stub_pymethods]
46impl PyStrikeRange {
47 #[staticmethod]
49 #[pyo3(name = "fixed")]
50 fn py_fixed(strikes: Vec<Price>) -> Self {
51 Self {
52 inner: StrikeRange::Fixed(strikes),
53 }
54 }
55
56 #[staticmethod]
58 #[pyo3(name = "atm_relative")]
59 fn py_atm_relative(strikes_above: usize, strikes_below: usize) -> Self {
60 Self {
61 inner: StrikeRange::AtmRelative {
62 strikes_above,
63 strikes_below,
64 },
65 }
66 }
67
68 #[staticmethod]
70 #[pyo3(name = "atm_percent")]
71 fn py_atm_percent(pct: f64) -> Self {
72 Self {
73 inner: StrikeRange::AtmPercent { pct },
74 }
75 }
76
77 #[getter]
79 #[pyo3(name = "kind")]
80 fn py_kind(&self) -> &'static str {
81 match self.inner {
82 StrikeRange::Fixed(_) => "Fixed",
83 StrikeRange::AtmRelative { .. } => "AtmRelative",
84 StrikeRange::AtmPercent { .. } => "AtmPercent",
85 }
86 }
87
88 fn __repr__(&self) -> String {
89 format!("{:?}", self.inner)
90 }
91
92 fn __str__(&self) -> String {
93 format!("{:?}", self.inner)
94 }
95}
96
97#[pymethods]
98#[pyo3_stub_gen::derive::gen_stub_pymethods]
99impl OptionGreeks {
100 #[new]
102 #[pyo3(signature = (instrument_id, delta, gamma, vega, theta, rho=0.0, mark_iv=None, bid_iv=None, ask_iv=None, underlying_price=None, open_interest=None, ts_event=0, ts_init=0, convention=None))]
103 #[expect(clippy::too_many_arguments)]
104 fn py_new(
105 instrument_id: InstrumentId,
106 delta: f64,
107 gamma: f64,
108 vega: f64,
109 theta: f64,
110 rho: f64,
111 mark_iv: Option<f64>,
112 bid_iv: Option<f64>,
113 ask_iv: Option<f64>,
114 underlying_price: Option<f64>,
115 open_interest: Option<f64>,
116 ts_event: u64,
117 ts_init: u64,
118 convention: Option<GreeksConvention>,
119 ) -> Self {
120 Self {
121 instrument_id,
122 convention: convention.unwrap_or_default(),
123 greeks: OptionGreekValues {
124 delta,
125 gamma,
126 vega,
127 theta,
128 rho,
129 },
130 mark_iv,
131 bid_iv,
132 ask_iv,
133 underlying_price,
134 open_interest,
135 ts_event: UnixNanos::from(ts_event),
136 ts_init: UnixNanos::from(ts_init),
137 }
138 }
139
140 #[getter]
141 #[pyo3(name = "convention")]
142 fn py_convention(&self) -> GreeksConvention {
143 self.convention
144 }
145
146 #[getter]
147 #[pyo3(name = "instrument_id")]
148 fn py_instrument_id(&self) -> InstrumentId {
149 self.instrument_id
150 }
151
152 #[getter]
153 #[pyo3(name = "delta")]
154 fn py_delta(&self) -> f64 {
155 self.greeks.delta
156 }
157
158 #[getter]
159 #[pyo3(name = "gamma")]
160 fn py_gamma(&self) -> f64 {
161 self.greeks.gamma
162 }
163
164 #[getter]
165 #[pyo3(name = "vega")]
166 fn py_vega(&self) -> f64 {
167 self.greeks.vega
168 }
169
170 #[getter]
171 #[pyo3(name = "theta")]
172 fn py_theta(&self) -> f64 {
173 self.greeks.theta
174 }
175
176 #[getter]
177 #[pyo3(name = "rho")]
178 fn py_rho(&self) -> f64 {
179 self.greeks.rho
180 }
181
182 #[getter]
183 #[pyo3(name = "mark_iv")]
184 fn py_mark_iv(&self) -> Option<f64> {
185 self.mark_iv
186 }
187
188 #[getter]
189 #[pyo3(name = "bid_iv")]
190 fn py_bid_iv(&self) -> Option<f64> {
191 self.bid_iv
192 }
193
194 #[getter]
195 #[pyo3(name = "ask_iv")]
196 fn py_ask_iv(&self) -> Option<f64> {
197 self.ask_iv
198 }
199
200 #[getter]
201 #[pyo3(name = "underlying_price")]
202 fn py_underlying_price(&self) -> Option<f64> {
203 self.underlying_price
204 }
205
206 #[getter]
207 #[pyo3(name = "open_interest")]
208 fn py_open_interest(&self) -> Option<f64> {
209 self.open_interest
210 }
211
212 #[getter]
213 #[pyo3(name = "ts_event")]
214 fn py_ts_event(&self) -> u64 {
215 self.ts_event.as_u64()
216 }
217
218 #[getter]
219 #[pyo3(name = "ts_init")]
220 fn py_ts_init(&self) -> u64 {
221 self.ts_init.as_u64()
222 }
223
224 fn __repr__(&self) -> String {
225 format!("{self}")
226 }
227
228 fn __str__(&self) -> String {
229 format!("{self}")
230 }
231}
232
233impl OptionGreeks {
234 pub fn from_pyobject(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
240 let instrument_id = obj.getattr("instrument_id")?.extract::<InstrumentId>()?;
241 let delta = obj.getattr("delta")?.extract::<f64>()?;
242 let gamma = obj.getattr("gamma")?.extract::<f64>()?;
243 let vega = obj.getattr("vega")?.extract::<f64>()?;
244 let theta = obj.getattr("theta")?.extract::<f64>()?;
245 let rho = obj.getattr("rho")?.extract::<f64>()?;
246 let mark_iv = obj.getattr("mark_iv")?.extract::<Option<f64>>()?;
247 let bid_iv = obj.getattr("bid_iv")?.extract::<Option<f64>>()?;
248 let ask_iv = obj.getattr("ask_iv")?.extract::<Option<f64>>()?;
249 let underlying_price = obj.getattr("underlying_price")?.extract::<Option<f64>>()?;
250 let open_interest = obj.getattr("open_interest")?.extract::<Option<f64>>()?;
251 let ts_event = obj.getattr("ts_event")?.extract::<u64>()?;
252 let ts_init = obj.getattr("ts_init")?.extract::<u64>()?;
253 let convention = obj
254 .getattr("convention")
255 .ok()
256 .and_then(|v| v.extract::<GreeksConvention>().ok())
257 .unwrap_or_default();
258
259 Ok(Self {
260 instrument_id,
261 convention,
262 greeks: OptionGreekValues {
263 delta,
264 gamma,
265 vega,
266 theta,
267 rho,
268 },
269 mark_iv,
270 bid_iv,
271 ask_iv,
272 underlying_price,
273 open_interest,
274 ts_event: UnixNanos::from(ts_event),
275 ts_init: UnixNanos::from(ts_init),
276 })
277 }
278}
279
280#[pymethods]
281#[pyo3_stub_gen::derive::gen_stub_pymethods]
282impl OptionStrikeData {
283 #[new]
285 #[pyo3(signature = (quote, greeks=None))]
286 fn py_new(quote: QuoteTick, greeks: Option<OptionGreeks>) -> Self {
287 Self { quote, greeks }
288 }
289
290 #[getter]
291 #[pyo3(name = "quote")]
292 fn py_quote(&self) -> QuoteTick {
293 self.quote
294 }
295
296 #[getter]
297 #[pyo3(name = "greeks")]
298 fn py_greeks(&self) -> Option<OptionGreeks> {
299 self.greeks
300 }
301
302 fn __repr__(&self) -> String {
303 format!(
304 "OptionStrikeData(quote={}, greeks={:?})",
305 self.quote, self.greeks
306 )
307 }
308}
309
310#[pymethods]
311#[pyo3_stub_gen::derive::gen_stub_pymethods]
312impl OptionChainSlice {
313 #[new]
315 #[pyo3(signature = (series_id, atm_strike=None, ts_event=0, ts_init=0))]
316 fn py_new(
317 series_id: OptionSeriesId,
318 atm_strike: Option<Price>,
319 ts_event: u64,
320 ts_init: u64,
321 ) -> Self {
322 Self {
323 series_id,
324 atm_strike,
325 calls: BTreeMap::new(),
326 puts: BTreeMap::new(),
327 ts_event: UnixNanos::from(ts_event),
328 ts_init: UnixNanos::from(ts_init),
329 }
330 }
331
332 #[getter]
333 #[pyo3(name = "series_id")]
334 fn py_series_id(&self) -> OptionSeriesId {
335 self.series_id
336 }
337
338 #[getter]
339 #[pyo3(name = "atm_strike")]
340 fn py_atm_strike(&self) -> Option<Price> {
341 self.atm_strike
342 }
343
344 #[getter]
345 #[pyo3(name = "ts_event")]
346 fn py_ts_event(&self) -> u64 {
347 self.ts_event.as_u64()
348 }
349
350 #[getter]
351 #[pyo3(name = "ts_init")]
352 fn py_ts_init(&self) -> u64 {
353 self.ts_init.as_u64()
354 }
355
356 #[pyo3(name = "call_count")]
358 fn py_call_count(&self) -> usize {
359 self.call_count()
360 }
361
362 #[pyo3(name = "put_count")]
364 fn py_put_count(&self) -> usize {
365 self.put_count()
366 }
367
368 #[pyo3(name = "strike_count")]
370 fn py_strike_count(&self) -> usize {
371 self.strike_count()
372 }
373
374 #[pyo3(name = "is_empty")]
376 fn py_is_empty(&self) -> bool {
377 self.is_empty()
378 }
379
380 #[pyo3(name = "strikes")]
382 fn py_strikes(&self) -> Vec<Price> {
383 self.strikes()
384 }
385
386 #[pyo3(name = "get_call")]
388 fn py_get_call(&self, strike: Price) -> Option<OptionStrikeData> {
389 self.get_call(&strike).cloned()
390 }
391
392 #[pyo3(name = "get_put")]
394 fn py_get_put(&self, strike: Price) -> Option<OptionStrikeData> {
395 self.get_put(&strike).cloned()
396 }
397
398 #[pyo3(name = "get_call_quote")]
400 fn py_get_call_quote(&self, strike: Price) -> Option<QuoteTick> {
401 self.get_call_quote(&strike).copied()
402 }
403
404 #[pyo3(name = "get_put_quote")]
406 fn py_get_put_quote(&self, strike: Price) -> Option<QuoteTick> {
407 self.get_put_quote(&strike).copied()
408 }
409
410 #[pyo3(name = "get_call_greeks")]
412 fn py_get_call_greeks(&self, strike: Price) -> Option<OptionGreeks> {
413 self.get_call_greeks(&strike).copied()
414 }
415
416 #[pyo3(name = "get_put_greeks")]
418 fn py_get_put_greeks(&self, strike: Price) -> Option<OptionGreeks> {
419 self.get_put_greeks(&strike).copied()
420 }
421
422 fn __repr__(&self) -> String {
423 format!("{self}")
424 }
425
426 fn __str__(&self) -> String {
427 format!("{self}")
428 }
429}