1use std::{cell::RefCell, collections::HashMap, rc::Rc};
19
20use anyhow;
21use nautilus_core::UnixNanos;
22use nautilus_model::{
23 data::greeks::{GreeksData, PortfolioGreeks, black_scholes_greeks, imply_vol_and_greeks},
24 enums::{InstrumentClass, OptionKind, PositionSide, PriceType},
25 identifiers::{InstrumentId, StrategyId, Venue},
26 instruments::Instrument,
27 position::Position,
28};
29
30use crate::{cache::Cache, clock::Clock, msgbus};
31
32#[allow(dead_code)]
47#[derive(Debug)]
48pub struct GreeksCalculator {
49 cache: Rc<RefCell<Cache>>,
50 clock: Rc<RefCell<dyn Clock>>,
51}
52
53impl GreeksCalculator {
54 pub fn new(cache: Rc<RefCell<Cache>>, clock: Rc<RefCell<dyn Clock>>) -> Self {
56 Self { cache, clock }
57 }
58
59 #[allow(clippy::too_many_arguments)]
74 pub fn instrument_greeks(
75 &self,
76 instrument_id: InstrumentId,
77 flat_interest_rate: Option<f64>,
78 flat_dividend_yield: Option<f64>,
79 spot_shock: Option<f64>,
80 vol_shock: Option<f64>,
81 time_to_expiry_shock: Option<f64>,
82 use_cached_greeks: Option<bool>,
83 cache_greeks: Option<bool>,
84 publish_greeks: Option<bool>,
85 ts_event: Option<UnixNanos>,
86 position: Option<Position>,
87 percent_greeks: Option<bool>,
88 index_instrument_id: Option<InstrumentId>,
89 beta_weights: Option<HashMap<InstrumentId, f64>>,
90 ) -> anyhow::Result<GreeksData> {
91 let flat_interest_rate = flat_interest_rate.unwrap_or(0.0425);
93 let spot_shock = spot_shock.unwrap_or(0.0);
94 let vol_shock = vol_shock.unwrap_or(0.0);
95 let time_to_expiry_shock = time_to_expiry_shock.unwrap_or(0.0);
96 let use_cached_greeks = use_cached_greeks.unwrap_or(false);
97 let cache_greeks = cache_greeks.unwrap_or(false);
98 let publish_greeks = publish_greeks.unwrap_or(false);
99 let ts_event = ts_event.unwrap_or_default();
100 let percent_greeks = percent_greeks.unwrap_or(false);
101
102 let cache = self.cache.borrow();
103 let instrument = cache.instrument(&instrument_id);
104 let instrument = match instrument {
105 Some(instrument) => instrument,
106 None => anyhow::bail!(format!(
107 "Instrument definition for {instrument_id} not found."
108 )),
109 };
110
111 if instrument.instrument_class() != InstrumentClass::Option {
112 let multiplier = instrument.multiplier();
113 let underlying_instrument_id = instrument.id();
114 let underlying_price = cache
115 .price(&underlying_instrument_id, PriceType::Last)
116 .unwrap_or_default()
117 .as_f64();
118 let (delta, _) = self.modify_greeks(
119 multiplier.as_f64(),
120 0.0,
121 underlying_instrument_id,
122 underlying_price + spot_shock,
123 underlying_price,
124 percent_greeks,
125 index_instrument_id,
126 beta_weights.as_ref(),
127 );
128 let mut greeks_data =
129 GreeksData::from_delta(instrument_id, delta, multiplier.as_f64(), ts_event);
130
131 if let Some(pos) = position {
132 greeks_data.pnl = multiplier * ((underlying_price + spot_shock) - pos.avg_px_open);
133 greeks_data.price = greeks_data.pnl;
134 }
135
136 return Ok(greeks_data);
137 }
138
139 let mut greeks_data = None;
140 let underlying = instrument.underlying().unwrap();
141 let underlying_str = format!("{}.{}", underlying, instrument_id.venue);
142 let underlying_instrument_id = InstrumentId::from(underlying_str.as_str());
143
144 if use_cached_greeks {
146 if let Some(cached_greeks) = cache.greeks(&instrument_id) {
147 greeks_data = Some(cached_greeks);
148 }
149 }
150
151 if greeks_data.is_none() {
152 let utc_now_ns = if ts_event != UnixNanos::default() {
153 ts_event
154 } else {
155 self.clock.borrow().timestamp_ns()
156 };
157
158 let utc_now = utc_now_ns.to_datetime_utc();
159 let expiry_utc = instrument
160 .expiration_ns()
161 .map(|ns| ns.to_datetime_utc())
162 .unwrap_or_default();
163 let expiry_int = expiry_utc
164 .format("%Y%m%d")
165 .to_string()
166 .parse::<i32>()
167 .unwrap_or(0);
168 let expiry_in_years = (expiry_utc - utc_now).num_days().min(1) as f64 / 365.25;
169 let currency = instrument.quote_currency().code.to_string();
170 let interest_rate = match cache.yield_curve(¤cy) {
171 Some(yield_curve) => yield_curve(expiry_in_years),
172 None => flat_interest_rate,
173 };
174
175 let mut cost_of_carry = 0.0;
177
178 if let Some(dividend_curve) = cache.yield_curve(&underlying_instrument_id.to_string()) {
179 let dividend_yield = dividend_curve(expiry_in_years);
180 cost_of_carry = interest_rate - dividend_yield;
181 } else if let Some(div_yield) = flat_dividend_yield {
182 cost_of_carry = interest_rate - div_yield;
184 }
185
186 let multiplier = instrument.multiplier();
187 let is_call = instrument.option_kind().unwrap_or(OptionKind::Call) == OptionKind::Call;
188 let strike = instrument.strike_price().unwrap_or_default().as_f64();
189 let option_mid_price = cache
190 .price(&instrument_id, PriceType::Mid)
191 .unwrap_or_default()
192 .as_f64();
193 let underlying_price = cache
194 .price(&underlying_instrument_id, PriceType::Last)
195 .unwrap_or_default()
196 .as_f64();
197
198 let greeks = imply_vol_and_greeks(
199 underlying_price,
200 interest_rate,
201 cost_of_carry,
202 is_call,
203 strike,
204 expiry_in_years,
205 option_mid_price,
206 multiplier.as_f64(),
207 );
208 let (delta, gamma) = self.modify_greeks(
209 greeks.delta,
210 greeks.gamma,
211 underlying_instrument_id,
212 underlying_price,
213 underlying_price,
214 percent_greeks,
215 index_instrument_id,
216 beta_weights.as_ref(),
217 );
218 greeks_data = Some(GreeksData::new(
219 utc_now_ns,
220 utc_now_ns,
221 instrument_id,
222 is_call,
223 strike,
224 expiry_int,
225 expiry_in_years,
226 multiplier.as_f64(),
227 1.0,
228 underlying_price,
229 interest_rate,
230 cost_of_carry,
231 greeks.vol,
232 0.0,
233 greeks.price,
234 delta,
235 gamma,
236 greeks.vega,
237 greeks.theta,
238 (greeks.delta / multiplier.as_f64()).abs(),
239 ));
240
241 if cache_greeks {
243 let mut cache = self.cache.borrow_mut();
244 cache
245 .add_greeks(greeks_data.clone().unwrap())
246 .unwrap_or_default();
247 }
248
249 if publish_greeks {
251 let topic = format!(
252 "data.GreeksData.instrument_id={}",
253 instrument_id.symbol.as_str()
254 )
255 .into();
256 msgbus::publish(topic, &greeks_data.clone().unwrap());
257 }
258 }
259
260 let mut greeks_data = greeks_data.unwrap();
261
262 if spot_shock != 0.0 || vol_shock != 0.0 || time_to_expiry_shock != 0.0 {
263 let underlying_price = greeks_data.underlying_price;
264 let shocked_underlying_price = underlying_price + spot_shock;
265 let shocked_vol = greeks_data.vol + vol_shock;
266 let shocked_time_to_expiry = greeks_data.expiry_in_years - time_to_expiry_shock;
267
268 let greeks = black_scholes_greeks(
269 shocked_underlying_price,
270 greeks_data.interest_rate,
271 greeks_data.cost_of_carry,
272 shocked_vol,
273 greeks_data.is_call,
274 greeks_data.strike,
275 shocked_time_to_expiry,
276 greeks_data.multiplier,
277 );
278 let (delta, gamma) = self.modify_greeks(
279 greeks.delta,
280 greeks.gamma,
281 underlying_instrument_id,
282 shocked_underlying_price,
283 underlying_price,
284 percent_greeks,
285 index_instrument_id,
286 beta_weights.as_ref(),
287 );
288 greeks_data = GreeksData::new(
289 greeks_data.ts_event,
290 greeks_data.ts_event,
291 greeks_data.instrument_id,
292 greeks_data.is_call,
293 greeks_data.strike,
294 greeks_data.expiry,
295 shocked_time_to_expiry,
296 greeks_data.multiplier,
297 greeks_data.quantity,
298 shocked_underlying_price,
299 greeks_data.interest_rate,
300 greeks_data.cost_of_carry,
301 shocked_vol,
302 0.0,
303 greeks.price,
304 delta,
305 gamma,
306 greeks.vega,
307 greeks.theta,
308 (greeks.delta / greeks_data.multiplier).abs(),
309 );
310 }
311
312 if let Some(pos) = position {
313 greeks_data.pnl = greeks_data.price - greeks_data.multiplier * pos.avg_px_open;
314 }
315
316 Ok(greeks_data)
317 }
318
319 #[allow(clippy::too_many_arguments)]
338 pub fn modify_greeks(
339 &self,
340 delta_input: f64,
341 gamma_input: f64,
342 underlying_instrument_id: InstrumentId,
343 underlying_price: f64,
344 unshocked_underlying_price: f64,
345 percent_greeks: bool,
346 index_instrument_id: Option<InstrumentId>,
347 beta_weights: Option<&HashMap<InstrumentId, f64>>,
348 ) -> (f64, f64) {
349 let mut delta = delta_input;
350 let mut gamma = gamma_input;
351
352 let mut index_price = None;
353
354 if let Some(index_id) = index_instrument_id {
355 let cache = self.cache.borrow();
356 index_price = Some(
357 cache
358 .price(&index_id, PriceType::Last)
359 .unwrap_or_default()
360 .as_f64(),
361 );
362
363 let mut beta = 1.0;
364 if let Some(weights) = beta_weights {
365 if let Some(&weight) = weights.get(&underlying_instrument_id) {
366 beta = weight;
367 }
368 }
369
370 if let Some(ref mut idx_price) = index_price {
371 if underlying_price != unshocked_underlying_price {
372 *idx_price += 1.0 / beta
373 * (*idx_price / unshocked_underlying_price)
374 * (underlying_price - unshocked_underlying_price);
375 }
376
377 let delta_multiplier = beta * underlying_price / *idx_price;
378 delta *= delta_multiplier;
379 gamma *= delta_multiplier.powi(2);
380 }
381 }
382
383 if percent_greeks {
384 if let Some(idx_price) = index_price {
385 delta *= idx_price / 100.0;
386 gamma *= (idx_price / 100.0).powi(2);
387 } else {
388 delta *= underlying_price / 100.0;
389 gamma *= (underlying_price / 100.0).powi(2);
390 }
391 }
392
393 (delta, gamma)
394 }
395
396 #[allow(clippy::too_many_arguments)]
409 pub fn portfolio_greeks(
410 &self,
411 underlyings: Option<Vec<String>>,
412 venue: Option<Venue>,
413 instrument_id: Option<InstrumentId>,
414 strategy_id: Option<StrategyId>,
415 side: Option<PositionSide>,
416 flat_interest_rate: Option<f64>,
417 flat_dividend_yield: Option<f64>,
418 spot_shock: Option<f64>,
419 vol_shock: Option<f64>,
420 time_to_expiry_shock: Option<f64>,
421 use_cached_greeks: Option<bool>,
422 cache_greeks: Option<bool>,
423 publish_greeks: Option<bool>,
424 percent_greeks: Option<bool>,
425 index_instrument_id: Option<InstrumentId>,
426 beta_weights: Option<HashMap<InstrumentId, f64>>,
427 ) -> anyhow::Result<PortfolioGreeks> {
428 let ts_event = self.clock.borrow().timestamp_ns();
429 let mut portfolio_greeks =
430 PortfolioGreeks::new(ts_event, ts_event, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
431
432 let flat_interest_rate = flat_interest_rate.unwrap_or(0.0425);
434 let spot_shock = spot_shock.unwrap_or(0.0);
435 let vol_shock = vol_shock.unwrap_or(0.0);
436 let time_to_expiry_shock = time_to_expiry_shock.unwrap_or(0.0);
437 let use_cached_greeks = use_cached_greeks.unwrap_or(false);
438 let cache_greeks = cache_greeks.unwrap_or(false);
439 let publish_greeks = publish_greeks.unwrap_or(false);
440 let percent_greeks = percent_greeks.unwrap_or(false);
441 let side = side.unwrap_or(PositionSide::NoPositionSide);
442
443 let cache = self.cache.borrow();
444 let open_positions = cache.positions(
445 venue.as_ref(),
446 instrument_id.as_ref(),
447 strategy_id.as_ref(),
448 Some(side),
449 );
450 let open_positions: Vec<Position> = open_positions.iter().map(|&p| p.clone()).collect();
451
452 for position in open_positions {
453 let position_instrument_id = position.instrument_id;
454
455 if let Some(ref underlyings_list) = underlyings {
456 let mut skip_position = true;
457
458 for underlying in underlyings_list {
459 if position_instrument_id
460 .symbol
461 .as_str()
462 .starts_with(underlying)
463 {
464 skip_position = false;
465 break;
466 }
467 }
468
469 if skip_position {
470 continue;
471 }
472 }
473
474 let quantity = position.signed_qty;
475 let instrument_greeks = self.instrument_greeks(
476 position_instrument_id,
477 Some(flat_interest_rate),
478 flat_dividend_yield,
479 Some(spot_shock),
480 Some(vol_shock),
481 Some(time_to_expiry_shock),
482 Some(use_cached_greeks),
483 Some(cache_greeks),
484 Some(publish_greeks),
485 Some(ts_event),
486 Some(position),
487 Some(percent_greeks),
488 index_instrument_id,
489 beta_weights.clone(),
490 )?;
491 portfolio_greeks = portfolio_greeks + (quantity * &instrument_greeks).into();
492 }
493
494 Ok(portfolio_greeks)
495 }
496
497 pub fn subscribe_greeks<F>(&self, underlying: &str, handler: Option<F>)
501 where
502 F: Fn(GreeksData) + 'static + Send + Sync,
503 {
504 let pattern = format!("data.GreeksData.instrument_id={}*", underlying).into();
505
506 if let Some(custom_handler) = handler {
507 let handler = msgbus::handler::TypedMessageHandler::with_any(
508 move |greeks: &dyn std::any::Any| {
509 if let Some(greeks_data) = greeks.downcast_ref::<GreeksData>() {
510 custom_handler(greeks_data.clone());
511 }
512 },
513 );
514 msgbus::subscribe(
515 pattern,
516 msgbus::handler::ShareableMessageHandler(Rc::new(handler)),
517 None,
518 );
519 } else {
520 let cache_ref = self.cache.clone();
521 let default_handler = msgbus::handler::TypedMessageHandler::with_any(
522 move |greeks: &dyn std::any::Any| {
523 if let Some(greeks_data) = greeks.downcast_ref::<GreeksData>() {
524 let mut cache = cache_ref.borrow_mut();
525 cache.add_greeks(greeks_data.clone()).unwrap_or_default();
526 }
527 },
528 );
529 msgbus::subscribe(
530 pattern,
531 msgbus::handler::ShareableMessageHandler(Rc::new(default_handler)),
532 None,
533 );
534 }
535 }
536}