Skip to main content

egui_charts/data/
options_volume.rs

1//! Host-supplied options turnover for an underlying instrument.
2
3use chrono::NaiveDate;
4use std::ops::RangeInclusive;
5use std::sync::{Arc, Mutex, PoisonError, Weak};
6
7/// Options whose traded contract volumes the host sums at each strike.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct OptionsVolumeQuery {
10    /// Underlying symbol as the feed knows it.
11    pub symbol: String,
12    /// Inclusive expiry dates. Use the same date at both ends for one expiry.
13    pub expiries: RangeInclusive<NaiveDate>,
14}
15
16/// Traded contract volume at one strike, summed over the requested expiries.
17/// This is turnover, not open interest, premium or the underlying's bar volume.
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct OptionsVolume {
20    /// Strike in the underlying's price units.
21    pub strike: f64,
22    /// Nonnegative, finite call contract volume.
23    pub calls: f64,
24    /// Nonnegative, finite put contract volume.
25    pub puts: f64,
26}
27
28pub(super) struct Snapshot<T> {
29    values: Mutex<T>,
30    wake: Box<dyn Fn() + Send + Sync>,
31}
32
33impl<T> Snapshot<T> {
34    pub(super) fn new(values: T, wake: impl Fn() + Send + Sync + 'static) -> Arc<Self> {
35        Arc::new(Self {
36            values: Mutex::new(values),
37            wake: Box::new(wake),
38        })
39    }
40
41    pub(super) fn read<R>(&self, read: impl FnOnce(&T) -> R) -> R {
42        read(&self.values.lock().unwrap_or_else(PoisonError::into_inner))
43    }
44
45    pub(super) fn update<R>(&self, update: impl FnOnce(&mut T) -> Option<R>) -> Option<R> {
46        let result = update(&mut self.values.lock().unwrap_or_else(PoisonError::into_inner));
47        if result.is_some() {
48            (self.wake)();
49        }
50        result
51    }
52}
53
54/// A feed's options snapshots for one underlying and expiry query. Cheap to clone.
55/// Only the latest snapshot is retained, so updates cannot accumulate between frames.
56#[derive(Clone)]
57pub struct OptionsVolumeSink(Weak<Snapshot<Arc<[OptionsVolume]>>>);
58
59impl OptionsVolumeSink {
60    /// Replace all figures, including clearing them with an empty snapshot, and wake the host.
61    /// Pushes after the last consumer is dropped are ignored.
62    pub fn push(&self, values: Vec<OptionsVolume>) {
63        if let Some(snapshot) = self.0.upgrade() {
64            snapshot.update(|latest| {
65                *latest = values.into();
66                Some(())
67            });
68        }
69    }
70
71    /// Whether a profile still consumes this query. Stop producing when false.
72    pub fn is_active(&self) -> bool {
73        self.0.strong_count() > 0
74    }
75}
76
77#[cfg(feature = "studies")]
78#[derive(Clone)]
79pub(crate) struct OptionsVolumeSource(Arc<Snapshot<Arc<[OptionsVolume]>>>);
80
81#[cfg(feature = "studies")]
82impl OptionsVolumeSource {
83    pub(crate) fn new(wake: impl Fn() + Send + Sync + 'static) -> (Self, OptionsVolumeSink) {
84        let snapshot = Snapshot::new(Arc::from([]), wake);
85        let sink = OptionsVolumeSink(Arc::downgrade(&snapshot));
86        (Self(snapshot), sink)
87    }
88
89    pub(crate) fn values(&self) -> Arc<[OptionsVolume]> {
90        self.0.read(Arc::clone)
91    }
92}