1use std::collections::HashMap;
17
18use nautilus_core::{
19 UnixNanos,
20 python::{IntoPyObjectNautilusExt, to_pyvalue_err},
21};
22use nautilus_model::{
23 data::greeks::{GreeksData, PortfolioGreeks},
24 enums::PositionSide,
25 identifiers::{InstrumentId, StrategyId, Venue},
26 position::Position,
27 types::Price,
28};
29use pyo3::prelude::*;
30
31use crate::{
32 greeks::{GreeksCalculator, GreeksFilter},
33 python::{cache::PyCache, clock::PyClock},
34};
35
36#[allow(non_camel_case_types)]
37#[pyo3::pyclass(
38 module = "nautilus_trader.core.nautilus_pyo3.common",
39 name = "GreeksCalculator",
40 unsendable
41)]
42#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
43#[derive(Debug)]
44pub struct PyGreeksCalculator(GreeksCalculator);
45
46#[pymethods]
47#[pyo3_stub_gen::derive::gen_stub_pymethods]
48impl PyGreeksCalculator {
49 #[new]
50 #[expect(clippy::needless_pass_by_value)]
51 fn py_new(cache: PyCache, clock: PyClock) -> Self {
52 Self(GreeksCalculator::new(cache.cache_rc(), clock.clock_rc()))
53 }
54
55 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
56 #[pyo3(
57 name = "instrument_greeks",
58 signature = (
59 instrument_id,
60 flat_interest_rate=0.0425,
61 flat_dividend_yield=None,
62 spot_shock=0.0,
63 vol_shock=0.0,
64 time_to_expiry_shock=0.0,
65 use_cached_greeks=false,
66 update_vol=false,
67 cache_greeks=false,
68 ts_event=0,
69 position=None,
70 percent_greeks=false,
71 index_instrument_id=None,
72 beta_weights=None,
73 vega_time_weight_base=None
74 )
75 )]
76 fn py_instrument_greeks(
77 &self,
78 instrument_id: InstrumentId,
79 flat_interest_rate: f64,
80 flat_dividend_yield: Option<f64>,
81 spot_shock: f64,
82 vol_shock: f64,
83 time_to_expiry_shock: f64,
84 use_cached_greeks: bool,
85 update_vol: bool,
86 cache_greeks: bool,
87 ts_event: u64,
88 position: Option<Position>,
89 percent_greeks: bool,
90 index_instrument_id: Option<InstrumentId>,
91 beta_weights: Option<HashMap<InstrumentId, f64>>,
92 vega_time_weight_base: Option<i32>,
93 ) -> PyResult<GreeksData> {
94 self.0
95 .instrument_greeks(
96 instrument_id,
97 Some(flat_interest_rate),
98 flat_dividend_yield,
99 Some(spot_shock),
100 Some(vol_shock),
101 Some(time_to_expiry_shock),
102 Some(use_cached_greeks),
103 Some(update_vol),
104 Some(cache_greeks),
105 Some(false),
106 (ts_event != 0).then(|| UnixNanos::from(ts_event)),
107 position,
108 Some(percent_greeks),
109 index_instrument_id,
110 beta_weights.as_ref(),
111 vega_time_weight_base,
112 )
113 .map_err(to_pyvalue_err)
114 }
115
116 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
117 #[pyo3(
118 name = "modify_greeks",
119 signature = (
120 delta_input,
121 gamma_input,
122 underlying_instrument_id,
123 underlying_price,
124 unshocked_underlying_price,
125 percent_greeks,
126 index_instrument_id=None,
127 beta_weights=None,
128 vega_input=0.0,
129 vol=0.0,
130 expiry_in_days=0,
131 vega_time_weight_base=None
132 )
133 )]
134 fn py_modify_greeks(
135 &self,
136 delta_input: f64,
137 gamma_input: f64,
138 underlying_instrument_id: InstrumentId,
139 underlying_price: f64,
140 unshocked_underlying_price: f64,
141 percent_greeks: bool,
142 index_instrument_id: Option<InstrumentId>,
143 beta_weights: Option<HashMap<InstrumentId, f64>>,
144 vega_input: f64,
145 vol: f64,
146 expiry_in_days: i32,
147 vega_time_weight_base: Option<i32>,
148 ) -> (f64, f64, f64) {
149 self.0.modify_greeks(
150 delta_input,
151 gamma_input,
152 underlying_instrument_id,
153 underlying_price,
154 unshocked_underlying_price,
155 percent_greeks,
156 index_instrument_id,
157 beta_weights.as_ref(),
158 vega_input,
159 vol,
160 expiry_in_days,
161 vega_time_weight_base,
162 )
163 }
164
165 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
166 #[pyo3(
167 name = "portfolio_greeks",
168 signature = (
169 underlyings=None,
170 venue=None,
171 instrument_id=None,
172 strategy_id=None,
173 side=None,
174 flat_interest_rate=0.0425,
175 flat_dividend_yield=None,
176 spot_shock=0.0,
177 vol_shock=0.0,
178 time_to_expiry_shock=0.0,
179 use_cached_greeks=false,
180 update_vol=false,
181 cache_greeks=false,
182 percent_greeks=false,
183 index_instrument_id=None,
184 beta_weights=None,
185 greeks_filter=None,
186 vega_time_weight_base=None
187 )
188 )]
189 fn py_portfolio_greeks(
190 &self,
191 underlyings: Option<Vec<String>>,
192 venue: Option<Venue>,
193 instrument_id: Option<InstrumentId>,
194 strategy_id: Option<StrategyId>,
195 side: Option<PositionSide>,
196 flat_interest_rate: f64,
197 flat_dividend_yield: Option<f64>,
198 spot_shock: f64,
199 vol_shock: f64,
200 time_to_expiry_shock: f64,
201 use_cached_greeks: bool,
202 update_vol: bool,
203 cache_greeks: bool,
204 percent_greeks: bool,
205 index_instrument_id: Option<InstrumentId>,
206 beta_weights: Option<HashMap<InstrumentId, f64>>,
207 greeks_filter: Option<Py<PyAny>>,
208 vega_time_weight_base: Option<i32>,
209 ) -> PyResult<PortfolioGreeks> {
210 let greeks_filter: Option<GreeksFilter> = greeks_filter.map(|callback| {
211 Box::new(move |data: &GreeksData| {
212 Python::attach(|py| {
213 callback
214 .bind(py)
215 .call1((data.clone().into_py_any_unwrap(py),))
216 .and_then(|result| result.extract::<bool>())
217 .unwrap_or(false)
218 })
219 }) as GreeksFilter
220 });
221
222 self.0
223 .portfolio_greeks(
224 underlyings.as_deref(),
225 venue,
226 instrument_id,
227 strategy_id,
228 Some(side.unwrap_or(PositionSide::NoPositionSide)),
229 Some(flat_interest_rate),
230 flat_dividend_yield,
231 Some(spot_shock),
232 Some(vol_shock),
233 Some(time_to_expiry_shock),
234 Some(use_cached_greeks),
235 Some(update_vol),
236 Some(cache_greeks),
237 Some(false),
238 Some(percent_greeks),
239 index_instrument_id,
240 beta_weights.as_ref(),
241 greeks_filter.as_ref(),
242 vega_time_weight_base,
243 )
244 .map_err(to_pyvalue_err)
245 }
246
247 #[pyo3(name = "cache_futures_spread")]
248 fn py_cache_futures_spread(
249 &self,
250 call_instrument_id: InstrumentId,
251 put_instrument_id: InstrumentId,
252 futures_instrument_id: InstrumentId,
253 ) -> PyResult<Price> {
254 self.0
255 .cache_futures_spread(call_instrument_id, put_instrument_id, futures_instrument_id)
256 .map_err(to_pyvalue_err)
257 }
258
259 #[pyo3(name = "get_cached_futures_spread_price")]
260 fn py_get_cached_futures_spread_price(
261 &self,
262 underlying_instrument_id: InstrumentId,
263 ) -> Option<Price> {
264 self.0
265 .get_cached_futures_spread_price(underlying_instrument_id)
266 }
267}