hypercall_runtime_api/
valuation.rs1use 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
70pub 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}