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 #[staticmethod]
79 #[pyo3(name = "delta")]
80 fn py_delta(target: f64, tolerance: f64) -> Self {
81 Self {
82 inner: StrikeRange::Delta { target, tolerance },
83 }
84 }
85
86 #[getter]
88 #[pyo3(name = "kind")]
89 fn py_kind(&self) -> &'static str {
90 match self.inner {
91 StrikeRange::Fixed(_) => "Fixed",
92 StrikeRange::AtmRelative { .. } => "AtmRelative",
93 StrikeRange::AtmPercent { .. } => "AtmPercent",
94 StrikeRange::Delta { .. } => "Delta",
95 }
96 }
97
98 fn __repr__(&self) -> String {
99 format!("{:?}", self.inner)
100 }
101
102 fn __str__(&self) -> String {
103 format!("{:?}", self.inner)
104 }
105}
106
107#[pymethods]
108#[pyo3_stub_gen::derive::gen_stub_pymethods]
109impl OptionGreeks {
110 #[new]
112 #[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))]
113 #[expect(clippy::too_many_arguments)]
114 fn py_new(
115 instrument_id: InstrumentId,
116 delta: f64,
117 gamma: f64,
118 vega: f64,
119 theta: f64,
120 rho: f64,
121 mark_iv: Option<f64>,
122 bid_iv: Option<f64>,
123 ask_iv: Option<f64>,
124 underlying_price: Option<f64>,
125 open_interest: Option<f64>,
126 ts_event: u64,
127 ts_init: u64,
128 convention: Option<GreeksConvention>,
129 ) -> Self {
130 Self {
131 instrument_id,
132 convention: convention.unwrap_or_default(),
133 greeks: OptionGreekValues {
134 delta,
135 gamma,
136 vega,
137 theta,
138 rho,
139 },
140 mark_iv,
141 bid_iv,
142 ask_iv,
143 underlying_price,
144 open_interest,
145 ts_event: UnixNanos::from(ts_event),
146 ts_init: UnixNanos::from(ts_init),
147 }
148 }
149
150 #[getter]
151 #[pyo3(name = "convention")]
152 fn py_convention(&self) -> GreeksConvention {
153 self.convention
154 }
155
156 #[getter]
157 #[pyo3(name = "instrument_id")]
158 fn py_instrument_id(&self) -> InstrumentId {
159 self.instrument_id
160 }
161
162 #[getter]
163 #[pyo3(name = "delta")]
164 fn py_delta(&self) -> f64 {
165 self.greeks.delta
166 }
167
168 #[getter]
169 #[pyo3(name = "gamma")]
170 fn py_gamma(&self) -> f64 {
171 self.greeks.gamma
172 }
173
174 #[getter]
175 #[pyo3(name = "vega")]
176 fn py_vega(&self) -> f64 {
177 self.greeks.vega
178 }
179
180 #[getter]
181 #[pyo3(name = "theta")]
182 fn py_theta(&self) -> f64 {
183 self.greeks.theta
184 }
185
186 #[getter]
187 #[pyo3(name = "rho")]
188 fn py_rho(&self) -> f64 {
189 self.greeks.rho
190 }
191
192 #[getter]
193 #[pyo3(name = "mark_iv")]
194 fn py_mark_iv(&self) -> Option<f64> {
195 self.mark_iv
196 }
197
198 #[getter]
199 #[pyo3(name = "bid_iv")]
200 fn py_bid_iv(&self) -> Option<f64> {
201 self.bid_iv
202 }
203
204 #[getter]
205 #[pyo3(name = "ask_iv")]
206 fn py_ask_iv(&self) -> Option<f64> {
207 self.ask_iv
208 }
209
210 #[getter]
211 #[pyo3(name = "underlying_price")]
212 fn py_underlying_price(&self) -> Option<f64> {
213 self.underlying_price
214 }
215
216 #[getter]
217 #[pyo3(name = "open_interest")]
218 fn py_open_interest(&self) -> Option<f64> {
219 self.open_interest
220 }
221
222 #[getter]
223 #[pyo3(name = "ts_event")]
224 fn py_ts_event(&self) -> u64 {
225 self.ts_event.as_u64()
226 }
227
228 #[getter]
229 #[pyo3(name = "ts_init")]
230 fn py_ts_init(&self) -> u64 {
231 self.ts_init.as_u64()
232 }
233
234 fn __repr__(&self) -> String {
235 format!("{self}")
236 }
237
238 fn __str__(&self) -> String {
239 format!("{self}")
240 }
241}
242
243impl OptionGreeks {
244 pub fn from_pyobject(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
250 let instrument_id = obj.getattr("instrument_id")?.extract::<InstrumentId>()?;
251 let delta = obj.getattr("delta")?.extract::<f64>()?;
252 let gamma = obj.getattr("gamma")?.extract::<f64>()?;
253 let vega = obj.getattr("vega")?.extract::<f64>()?;
254 let theta = obj.getattr("theta")?.extract::<f64>()?;
255 let rho = obj.getattr("rho")?.extract::<f64>()?;
256 let mark_iv = obj.getattr("mark_iv")?.extract::<Option<f64>>()?;
257 let bid_iv = obj.getattr("bid_iv")?.extract::<Option<f64>>()?;
258 let ask_iv = obj.getattr("ask_iv")?.extract::<Option<f64>>()?;
259 let underlying_price = obj.getattr("underlying_price")?.extract::<Option<f64>>()?;
260 let open_interest = obj.getattr("open_interest")?.extract::<Option<f64>>()?;
261 let ts_event = obj.getattr("ts_event")?.extract::<u64>()?;
262 let ts_init = obj.getattr("ts_init")?.extract::<u64>()?;
263 let convention = obj
264 .getattr("convention")
265 .ok()
266 .and_then(|v| v.extract::<GreeksConvention>().ok())
267 .unwrap_or_default();
268
269 Ok(Self {
270 instrument_id,
271 convention,
272 greeks: OptionGreekValues {
273 delta,
274 gamma,
275 vega,
276 theta,
277 rho,
278 },
279 mark_iv,
280 bid_iv,
281 ask_iv,
282 underlying_price,
283 open_interest,
284 ts_event: UnixNanos::from(ts_event),
285 ts_init: UnixNanos::from(ts_init),
286 })
287 }
288}
289
290#[pymethods]
291#[pyo3_stub_gen::derive::gen_stub_pymethods]
292impl OptionStrikeData {
293 #[new]
295 #[pyo3(signature = (quote, greeks=None))]
296 fn py_new(quote: QuoteTick, greeks: Option<OptionGreeks>) -> Self {
297 Self { quote, greeks }
298 }
299
300 #[getter]
301 #[pyo3(name = "quote")]
302 fn py_quote(&self) -> QuoteTick {
303 self.quote
304 }
305
306 #[getter]
307 #[pyo3(name = "greeks")]
308 fn py_greeks(&self) -> Option<OptionGreeks> {
309 self.greeks
310 }
311
312 fn __repr__(&self) -> String {
313 format!(
314 "OptionStrikeData(quote={}, greeks={:?})",
315 self.quote, self.greeks
316 )
317 }
318}
319
320#[pymethods]
321#[pyo3_stub_gen::derive::gen_stub_pymethods]
322impl OptionChainSlice {
323 #[new]
325 #[pyo3(signature = (series_id, atm_strike=None, ts_event=0, ts_init=0))]
326 fn py_new(
327 series_id: OptionSeriesId,
328 atm_strike: Option<Price>,
329 ts_event: u64,
330 ts_init: u64,
331 ) -> Self {
332 Self {
333 series_id,
334 atm_strike,
335 calls: BTreeMap::new(),
336 puts: BTreeMap::new(),
337 ts_event: UnixNanos::from(ts_event),
338 ts_init: UnixNanos::from(ts_init),
339 }
340 }
341
342 #[getter]
343 #[pyo3(name = "series_id")]
344 fn py_series_id(&self) -> OptionSeriesId {
345 self.series_id
346 }
347
348 #[getter]
349 #[pyo3(name = "atm_strike")]
350 fn py_atm_strike(&self) -> Option<Price> {
351 self.atm_strike
352 }
353
354 #[getter]
355 #[pyo3(name = "ts_event")]
356 fn py_ts_event(&self) -> u64 {
357 self.ts_event.as_u64()
358 }
359
360 #[getter]
361 #[pyo3(name = "ts_init")]
362 fn py_ts_init(&self) -> u64 {
363 self.ts_init.as_u64()
364 }
365
366 #[pyo3(name = "call_count")]
368 fn py_call_count(&self) -> usize {
369 self.call_count()
370 }
371
372 #[pyo3(name = "put_count")]
374 fn py_put_count(&self) -> usize {
375 self.put_count()
376 }
377
378 #[pyo3(name = "strike_count")]
380 fn py_strike_count(&self) -> usize {
381 self.strike_count()
382 }
383
384 #[pyo3(name = "is_empty")]
386 fn py_is_empty(&self) -> bool {
387 self.is_empty()
388 }
389
390 #[pyo3(name = "strikes")]
392 fn py_strikes(&self) -> Vec<Price> {
393 self.strikes()
394 }
395
396 #[pyo3(name = "get_call")]
398 fn py_get_call(&self, strike: Price) -> Option<OptionStrikeData> {
399 self.get_call(&strike).cloned()
400 }
401
402 #[pyo3(name = "get_put")]
404 fn py_get_put(&self, strike: Price) -> Option<OptionStrikeData> {
405 self.get_put(&strike).cloned()
406 }
407
408 #[pyo3(name = "get_call_quote")]
410 fn py_get_call_quote(&self, strike: Price) -> Option<QuoteTick> {
411 self.get_call_quote(&strike).copied()
412 }
413
414 #[pyo3(name = "get_put_quote")]
416 fn py_get_put_quote(&self, strike: Price) -> Option<QuoteTick> {
417 self.get_put_quote(&strike).copied()
418 }
419
420 #[pyo3(name = "get_call_greeks")]
422 fn py_get_call_greeks(&self, strike: Price) -> Option<OptionGreeks> {
423 self.get_call_greeks(&strike).copied()
424 }
425
426 #[pyo3(name = "get_put_greeks")]
428 fn py_get_put_greeks(&self, strike: Price) -> Option<OptionGreeks> {
429 self.get_put_greeks(&strike).copied()
430 }
431
432 fn __repr__(&self) -> String {
433 format!("{self}")
434 }
435
436 fn __str__(&self) -> String {
437 format!("{self}")
438 }
439}