Skip to main content

nautilus_common/python/
greeks.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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}