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(name = "StrikeRange", module = "nautilus_trader.model", from_py_object)]
34#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
35#[derive(Clone, Debug)]
36pub struct PyStrikeRange {
37 pub inner: StrikeRange,
38}
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl PyStrikeRange {
43 #[staticmethod]
45 #[pyo3(name = "fixed")]
46 fn py_fixed(strikes: Vec<Price>) -> Self {
47 Self {
48 inner: StrikeRange::Fixed(strikes),
49 }
50 }
51
52 #[staticmethod]
54 #[pyo3(name = "atm_relative")]
55 fn py_atm_relative(strikes_above: usize, strikes_below: usize) -> Self {
56 Self {
57 inner: StrikeRange::AtmRelative {
58 strikes_above,
59 strikes_below,
60 },
61 }
62 }
63
64 #[staticmethod]
66 #[pyo3(name = "atm_percent")]
67 fn py_atm_percent(pct: f64) -> Self {
68 Self {
69 inner: StrikeRange::AtmPercent { pct },
70 }
71 }
72
73 #[staticmethod]
75 #[pyo3(name = "delta")]
76 fn py_delta(target: f64, tolerance: f64) -> Self {
77 Self {
78 inner: StrikeRange::Delta { target, tolerance },
79 }
80 }
81
82 #[getter]
84 #[pyo3(name = "kind")]
85 fn py_kind(&self) -> &'static str {
86 match self.inner {
87 StrikeRange::Fixed(_) => "Fixed",
88 StrikeRange::AtmRelative { .. } => "AtmRelative",
89 StrikeRange::AtmPercent { .. } => "AtmPercent",
90 StrikeRange::Delta { .. } => "Delta",
91 }
92 }
93
94 fn __repr__(&self) -> String {
95 format!("{:?}", self.inner)
96 }
97
98 fn __str__(&self) -> String {
99 format!("{:?}", self.inner)
100 }
101}
102
103#[pymethods]
104#[pyo3_stub_gen::derive::gen_stub_pymethods]
105impl OptionGreeks {
106 #[new]
108 #[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))]
109 #[expect(clippy::too_many_arguments)]
110 fn py_new(
111 instrument_id: InstrumentId,
112 delta: f64,
113 gamma: f64,
114 vega: f64,
115 theta: f64,
116 rho: f64,
117 mark_iv: Option<f64>,
118 bid_iv: Option<f64>,
119 ask_iv: Option<f64>,
120 underlying_price: Option<f64>,
121 open_interest: Option<f64>,
122 ts_event: u64,
123 ts_init: u64,
124 convention: Option<GreeksConvention>,
125 ) -> Self {
126 Self {
127 instrument_id,
128 convention: convention.unwrap_or_default(),
129 greeks: OptionGreekValues {
130 delta,
131 gamma,
132 vega,
133 theta,
134 rho,
135 },
136 mark_iv,
137 bid_iv,
138 ask_iv,
139 underlying_price,
140 open_interest,
141 ts_event: UnixNanos::from(ts_event),
142 ts_init: UnixNanos::from(ts_init),
143 }
144 }
145
146 #[getter]
147 #[pyo3(name = "convention")]
148 fn py_convention(&self) -> GreeksConvention {
149 self.convention
150 }
151
152 #[getter]
153 #[pyo3(name = "instrument_id")]
154 fn py_instrument_id(&self) -> InstrumentId {
155 self.instrument_id
156 }
157
158 #[getter]
159 #[pyo3(name = "delta")]
160 fn py_delta(&self) -> f64 {
161 self.greeks.delta
162 }
163
164 #[getter]
165 #[pyo3(name = "gamma")]
166 fn py_gamma(&self) -> f64 {
167 self.greeks.gamma
168 }
169
170 #[getter]
171 #[pyo3(name = "vega")]
172 fn py_vega(&self) -> f64 {
173 self.greeks.vega
174 }
175
176 #[getter]
177 #[pyo3(name = "theta")]
178 fn py_theta(&self) -> f64 {
179 self.greeks.theta
180 }
181
182 #[getter]
183 #[pyo3(name = "rho")]
184 fn py_rho(&self) -> f64 {
185 self.greeks.rho
186 }
187
188 #[getter]
189 #[pyo3(name = "mark_iv")]
190 fn py_mark_iv(&self) -> Option<f64> {
191 self.mark_iv
192 }
193
194 #[getter]
195 #[pyo3(name = "bid_iv")]
196 fn py_bid_iv(&self) -> Option<f64> {
197 self.bid_iv
198 }
199
200 #[getter]
201 #[pyo3(name = "ask_iv")]
202 fn py_ask_iv(&self) -> Option<f64> {
203 self.ask_iv
204 }
205
206 #[getter]
207 #[pyo3(name = "underlying_price")]
208 fn py_underlying_price(&self) -> Option<f64> {
209 self.underlying_price
210 }
211
212 #[getter]
213 #[pyo3(name = "open_interest")]
214 fn py_open_interest(&self) -> Option<f64> {
215 self.open_interest
216 }
217
218 #[getter]
219 #[pyo3(name = "ts_event")]
220 fn py_ts_event(&self) -> u64 {
221 self.ts_event.as_u64()
222 }
223
224 #[getter]
225 #[pyo3(name = "ts_init")]
226 fn py_ts_init(&self) -> u64 {
227 self.ts_init.as_u64()
228 }
229
230 fn __repr__(&self) -> String {
231 format!("{self}")
232 }
233
234 fn __str__(&self) -> String {
235 format!("{self}")
236 }
237}
238
239#[pymethods]
240#[pyo3_stub_gen::derive::gen_stub_pymethods]
241impl OptionStrikeData {
242 #[new]
244 #[pyo3(signature = (quote, greeks=None))]
245 fn py_new(quote: QuoteTick, greeks: Option<OptionGreeks>) -> Self {
246 Self { quote, greeks }
247 }
248
249 #[getter]
250 #[pyo3(name = "quote")]
251 fn py_quote(&self) -> QuoteTick {
252 self.quote
253 }
254
255 #[getter]
256 #[pyo3(name = "greeks")]
257 fn py_greeks(&self) -> Option<OptionGreeks> {
258 self.greeks
259 }
260
261 fn __repr__(&self) -> String {
262 format!(
263 "OptionStrikeData(quote={}, greeks={:?})",
264 self.quote, self.greeks
265 )
266 }
267}
268
269#[pymethods]
270#[pyo3_stub_gen::derive::gen_stub_pymethods]
271impl OptionChainSlice {
272 #[new]
274 #[pyo3(signature = (series_id, atm_strike=None, ts_event=0, ts_init=0))]
275 fn py_new(
276 series_id: OptionSeriesId,
277 atm_strike: Option<Price>,
278 ts_event: u64,
279 ts_init: u64,
280 ) -> Self {
281 Self {
282 series_id,
283 atm_strike,
284 calls: BTreeMap::new(),
285 puts: BTreeMap::new(),
286 ts_event: UnixNanos::from(ts_event),
287 ts_init: UnixNanos::from(ts_init),
288 }
289 }
290
291 #[getter]
292 #[pyo3(name = "series_id")]
293 fn py_series_id(&self) -> OptionSeriesId {
294 self.series_id
295 }
296
297 #[getter]
298 #[pyo3(name = "atm_strike")]
299 fn py_atm_strike(&self) -> Option<Price> {
300 self.atm_strike
301 }
302
303 #[getter]
304 #[pyo3(name = "ts_event")]
305 fn py_ts_event(&self) -> u64 {
306 self.ts_event.as_u64()
307 }
308
309 #[getter]
310 #[pyo3(name = "ts_init")]
311 fn py_ts_init(&self) -> u64 {
312 self.ts_init.as_u64()
313 }
314
315 #[pyo3(name = "call_count")]
317 fn py_call_count(&self) -> usize {
318 self.call_count()
319 }
320
321 #[pyo3(name = "put_count")]
323 fn py_put_count(&self) -> usize {
324 self.put_count()
325 }
326
327 #[pyo3(name = "strike_count")]
329 fn py_strike_count(&self) -> usize {
330 self.strike_count()
331 }
332
333 #[pyo3(name = "is_empty")]
335 fn py_is_empty(&self) -> bool {
336 self.is_empty()
337 }
338
339 #[pyo3(name = "strikes")]
341 fn py_strikes(&self) -> Vec<Price> {
342 self.strikes()
343 }
344
345 #[pyo3(name = "get_call")]
347 fn py_get_call(&self, strike: Price) -> Option<OptionStrikeData> {
348 self.get_call(&strike).cloned()
349 }
350
351 #[pyo3(name = "get_put")]
353 fn py_get_put(&self, strike: Price) -> Option<OptionStrikeData> {
354 self.get_put(&strike).cloned()
355 }
356
357 #[pyo3(name = "get_call_quote")]
359 fn py_get_call_quote(&self, strike: Price) -> Option<QuoteTick> {
360 self.get_call_quote(&strike).copied()
361 }
362
363 #[pyo3(name = "get_put_quote")]
365 fn py_get_put_quote(&self, strike: Price) -> Option<QuoteTick> {
366 self.get_put_quote(&strike).copied()
367 }
368
369 #[pyo3(name = "get_call_greeks")]
371 fn py_get_call_greeks(&self, strike: Price) -> Option<OptionGreeks> {
372 self.get_call_greeks(&strike).copied()
373 }
374
375 #[pyo3(name = "get_put_greeks")]
377 fn py_get_put_greeks(&self, strike: Price) -> Option<OptionGreeks> {
378 self.get_put_greeks(&strike).copied()
379 }
380
381 fn __repr__(&self) -> String {
382 format!("{self}")
383 }
384
385 fn __str__(&self) -> String {
386 format!("{self}")
387 }
388}