Skip to main content

hypercall_runtime_api/
valuation.rs

1use anyhow::{anyhow, Result};
2use hypercall_types::{expiry_date_to_timestamp, perp_underlying, ParsedOptionSymbol};
3use rust_decimal::prelude::ToPrimitive;
4
5use crate::boundary::market_inputs::GreeksCacheReader;
6
7async fn get_expired_option_mark(
8    greeks_cache: &dyn GreeksCacheReader,
9    symbol: &str,
10    parsed_symbol: &ParsedOptionSymbol,
11) -> Result<Option<f64>> {
12    let expiry_timestamp =
13        expiry_date_to_timestamp(&parsed_symbol.underlying, parsed_symbol.expiry);
14    if expiry_timestamp <= 0 {
15        return Err(anyhow!(
16            "Theoretical pricing unavailable for {}: invalid expiry {}",
17            symbol,
18            parsed_symbol.expiry
19        ));
20    }
21
22    if expiry_timestamp > now_unix_timestamp_secs()? {
23        return Ok(None);
24    }
25
26    let settlement_price = greeks_cache
27        .get_settlement_price(&parsed_symbol.underlying, expiry_timestamp)
28        .await
29        .ok_or_else(|| {
30            anyhow!(
31                "Theoretical pricing unavailable for {}: missing settlement price for {} at expiry {}",
32                symbol,
33                parsed_symbol.underlying,
34                expiry_timestamp
35            )
36        })?;
37    let strike = parsed_symbol.strike.to_f64().ok_or_else(|| {
38        anyhow!(
39            "Theoretical pricing unavailable for {}: invalid strike {}",
40            symbol,
41            parsed_symbol.strike
42        )
43    })?;
44
45    Ok(Some(intrinsic_option_price(
46        settlement_price,
47        strike,
48        parsed_symbol.option_type,
49    )))
50}
51
52fn intrinsic_option_price(
53    settlement_price: f64,
54    strike: f64,
55    option_type: hypercall_types::OptionType,
56) -> f64 {
57    match option_type {
58        hypercall_types::OptionType::Call => (settlement_price - strike).max(0.0),
59        hypercall_types::OptionType::Put => (strike - settlement_price).max(0.0),
60    }
61}
62
63fn now_unix_timestamp_secs() -> Result<i64> {
64    Ok(std::time::SystemTime::now()
65        .duration_since(std::time::UNIX_EPOCH)
66        .map_err(|error| anyhow!("failed to read system clock: {}", error))?
67        .as_secs() as i64)
68}
69
70/// Return the canonical valuation for a symbol used by risk-style displays.
71///
72/// Options use theoretical values from the vol-oracle-backed Greeks cache.
73/// Perps and raw underlyings use spot prices from the same cache.
74pub async fn get_symbol_theoretical_price(
75    greeks_cache: &dyn GreeksCacheReader,
76    symbol: &str,
77) -> Result<f64> {
78    if let Ok(parsed_symbol) = ParsedOptionSymbol::from_symbol(symbol) {
79        if let Some(expired_mark) =
80            get_expired_option_mark(greeks_cache, symbol, &parsed_symbol).await?
81        {
82            return Ok(expired_mark);
83        }
84    }
85
86    if greeks_cache.has_symbol(symbol).await {
87        return greeks_cache
88            .get_theoretical_mark(symbol)
89            .await
90            .map_err(|error| anyhow!("Theoretical pricing unavailable for {}: {}", symbol, error));
91    }
92
93    if let Some(underlying) = perp_underlying(symbol) {
94        return greeks_cache
95            .get_spot_price(underlying)
96            .await
97            .ok_or_else(|| {
98                anyhow!(
99                    "Theoretical pricing unavailable for {}: missing spot price for {}",
100                    symbol,
101                    underlying
102                )
103            });
104    }
105
106    greeks_cache.get_spot_price(symbol).await.ok_or_else(|| {
107        anyhow!(
108            "Theoretical pricing unavailable for {}: missing spot price",
109            symbol
110        )
111    })
112}
113
114#[cfg(test)]
115mod tests {
116    use super::intrinsic_option_price;
117    use hypercall_types::OptionType;
118
119    #[test]
120    fn intrinsic_option_price_uses_call_payoff() {
121        assert_eq!(
122            intrinsic_option_price(125_000.0, 100_000.0, OptionType::Call),
123            25_000.0
124        );
125        assert_eq!(
126            intrinsic_option_price(95_000.0, 100_000.0, OptionType::Call),
127            0.0
128        );
129    }
130
131    #[test]
132    fn intrinsic_option_price_uses_put_payoff() {
133        assert_eq!(
134            intrinsic_option_price(75_000.0, 100_000.0, OptionType::Put),
135            25_000.0
136        );
137        assert_eq!(
138            intrinsic_option_price(105_000.0, 100_000.0, OptionType::Put),
139            0.0
140        );
141    }
142}