ta_lib_in_rust/indicators/options/
implied_volatility.rs

1//! # Implied Volatility Indicators
2//!
3//! This module provides indicators based on implied volatility analysis for options trading.
4
5use polars::prelude::*;
6
7/// Implied Volatility Surface for analyzing IV patterns across strikes and expirations
8pub struct IVSurface {
9    /// Minimum number of strikes required to construct a valid IV skew
10    pub min_strikes_for_skew: usize,
11
12    /// Minimum number of expirations required to construct a valid term structure
13    pub min_expirations_for_term: usize,
14
15    /// Historical percentile window for IV rank calculation
16    pub iv_rank_window_days: usize,
17}
18
19impl Default for IVSurface {
20    fn default() -> Self {
21        Self {
22            min_strikes_for_skew: 5,
23            min_expirations_for_term: 3,
24            iv_rank_window_days: 252, // One trading year
25        }
26    }
27}
28
29/// Calculate implied volatility skew
30///
31/// Measures the difference in IV between OTM puts and OTM calls
32/// to identify potential market sentiment and tail risk expectations.
33///
34/// # Arguments
35///
36/// * `df` - DataFrame with price data
37/// * `options_chain` - DataFrame with options data
38/// * `current_price` - Current price of the underlying
39/// * `delta_range` - Range of delta values to include
40///
41/// # Returns
42///
43/// * `Result<Series, PolarsError>` - IV skew value (positive: put skew, negative: call skew)
44pub fn calculate_iv_skew(
45    _df: &DataFrame,
46    _options_chain: &DataFrame,
47    _current_price: f64,
48    _delta_range: (f64, f64),
49) -> Result<Series, PolarsError> {
50    // In a real implementation, we would:
51    // 1. Filter options to the specified delta range
52    // 2. Group by puts vs calls
53    // 3. Calculate average IV for each group
54    // 4. Return put_iv - call_iv
55
56    // Placeholder implementation
57    Ok(Series::new("iv_skew".into(), vec![0.15]))
58}
59
60/// Calculate implied volatility term structure
61///
62/// Analyzes the relationship between IV and time to expiration
63/// to identify potential volatility expectations across different timeframes.
64///
65/// # Arguments
66///
67/// * `df` - DataFrame with price data
68/// * `options_chain` - DataFrame with options data
69/// * `atm_delta` - Delta value for at-the-money options
70///
71/// # Returns
72///
73/// * `Result<Series, PolarsError>` - Series of IV values for different expirations
74pub fn term_structure_analysis(
75    _df: &DataFrame,
76    _options_chain: &DataFrame,
77    _atm_delta: f64,
78) -> Result<Series, PolarsError> {
79    // Placeholder implementation
80    let term_structure = vec![
81        0.25, // 30 DTE: 25% IV
82        0.23, // 60 DTE: 23% IV
83        0.22, // 90 DTE: 22% IV
84    ];
85
86    Ok(Series::new("iv_term_structure".into(), term_structure))
87}
88
89/// Calculate IV rank and percentile
90///
91/// Determines where current IV stands in relation to its historical range.
92///
93/// # Arguments
94///
95/// * `current_iv` - Current implied volatility value
96/// * `historical_iv` - Series of historical IV values
97///
98/// # Returns
99///
100/// * `(f64, f64)` - Tuple of (IV rank, IV percentile)
101pub fn calculate_iv_rank_percentile(current_iv: f64, historical_iv: &Series) -> (f64, f64) {
102    // Collect valid float values into a Vec to simplify processing
103    let mut values = Vec::new();
104    if let Ok(f64_chunked) = historical_iv.f64() {
105        for i in 0..f64_chunked.len() {
106            if let Some(val) = f64_chunked.get(i) {
107                if !val.is_nan() {
108                    values.push(val);
109                }
110            }
111        }
112    }
113
114    // Initialize calculation variables
115    let mut min_iv = f64::MAX;
116    let mut max_iv = f64::MIN;
117    let mut count_below = 0;
118    let total_values = values.len();
119
120    // Process all values to find min, max, and count values below current_iv
121    for &val in &values {
122        min_iv = min_iv.min(val);
123        max_iv = max_iv.max(val);
124
125        if val < current_iv {
126            count_below += 1;
127        }
128    }
129
130    // Calculate IV rank and percentile
131    let iv_rank = if max_iv > min_iv {
132        (current_iv - min_iv) / (max_iv - min_iv)
133    } else {
134        0.5 // Default if there's no range
135    };
136
137    let iv_percentile = if total_values > 0 {
138        count_below as f64 / total_values as f64
139    } else {
140        0.5 // Default if no historical data
141    };
142
143    (iv_rank, iv_percentile)
144}
145
146/// Generate trading signals based on IV behavior
147///
148/// Creates buy/sell signals for volatility-based trading strategies
149/// using implied volatility patterns.
150///
151/// # Arguments
152///
153/// * `df` - DataFrame with price data
154/// * `iv_series` - Series with historical implied volatility
155/// * `iv_percentile_threshold` - Threshold for high and low IV percentile
156///
157/// # Returns
158///
159/// * `Result<Series, PolarsError>` - Series of buy/sell signals
160pub fn implied_volatility_regime(
161    df: &DataFrame,
162    _iv_series: &Series,
163    _iv_percentile_threshold: f64,
164) -> Result<Series, PolarsError> {
165    // Placeholder implementation
166    let rows = df.height();
167    let signals = vec![false; rows];
168
169    Ok(Series::new("iv_signals".into(), signals))
170}