quantwave_core/options_india/
chain_analytics.rs1use serde::{Serialize, Deserialize};
2
3pub fn max_pain(
6 strikes: &[f64],
7 ce_oi: &[u64],
8 pe_oi: &[u64],
9 lot_size: u32,
10) -> f64 {
11 if strikes.is_empty() {
12 return 0.0;
13 }
14
15 let mut min_pain = f64::MAX;
16 let mut max_pain_strike = strikes[0];
17
18 for &expiry_price in strikes {
19 let mut total_pain = 0.0;
20 for i in 0..strikes.len() {
21 let strike = strikes[i];
22 if expiry_price > strike {
24 total_pain += (expiry_price - strike) * ce_oi[i] as f64 * lot_size as f64;
25 }
26 if expiry_price < strike {
28 total_pain += (strike - expiry_price) * pe_oi[i] as f64 * lot_size as f64;
29 }
30 }
31
32 if total_pain < min_pain {
33 min_pain = total_pain;
34 max_pain_strike = expiry_price;
35 }
36 }
37
38 max_pain_strike
39}
40
41pub fn strike_pcr(ce_oi: &[u64], pe_oi: &[u64]) -> Vec<f64> {
44 ce_oi.iter().zip(pe_oi.iter()).map(|(&ce, &pe)| {
45 if ce == 0 {
46 0.0
47 } else {
48 pe as f64 / ce as f64
49 }
50 }).collect()
51}
52
53pub fn chain_pcr(ce_oi: &[u64], pe_oi: &[u64]) -> f64 {
56 let total_ce: u64 = ce_oi.iter().sum();
57 let total_pe: u64 = pe_oi.iter().sum();
58 if total_ce == 0 {
59 0.0
60 } else {
61 total_pe as f64 / total_ce as f64
62 }
63}
64
65#[derive(Debug, Serialize, Deserialize)]
67pub struct OIZones {
68 pub resistance_strikes: Vec<f64>, pub support_strikes: Vec<f64>, }
71
72pub fn oi_zones(strikes: &[f64], ce_oi: &[u64], pe_oi: &[u64], n: usize) -> OIZones {
73 let mut ce_pairs: Vec<(f64, u64)> = strikes.iter().cloned().zip(ce_oi.iter().cloned()).collect();
74 let mut pe_pairs: Vec<(f64, u64)> = strikes.iter().cloned().zip(pe_oi.iter().cloned()).collect();
75
76 ce_pairs.sort_by(|a, b| b.1.cmp(&a.1));
77 pe_pairs.sort_by(|a, b| b.1.cmp(&a.1));
78
79 OIZones {
80 resistance_strikes: ce_pairs.iter().take(n).map(|p| p.0).collect(),
81 support_strikes: pe_pairs.iter().take(n).map(|p| p.0).collect(),
82 }
83}
84
85pub fn gex_per_strike(
90 spot: f64,
91 strikes: &[f64],
92 ce_gamma: &[f64],
93 pe_gamma: &[f64],
94 ce_oi: &[u64],
95 pe_oi: &[u64],
96 lot_size: u32,
97) -> Vec<(f64, f64, f64)> {
98 let mut result = Vec::with_capacity(strikes.len());
99 for i in 0..strikes.len() {
100 let ce_g = ce_oi[i] as f64 * ce_gamma[i] * spot * lot_size as f64 * 0.01;
101 let pe_g = pe_oi[i] as f64 * pe_gamma[i] * spot * lot_size as f64 * -0.01;
102 result.push((ce_g, pe_g, ce_g + pe_g));
103 }
104 result
105}
106
107pub fn gex_flip_strike(strikes: &[f64], net_gex: &[f64]) -> Option<f64> {
110 if strikes.len() < 2 {
111 return None;
112 }
113
114 for i in 0..net_gex.len() - 1 {
115 if (net_gex[i] < 0.0 && net_gex[i+1] > 0.0) || (net_gex[i] > 0.0 && net_gex[i+1] < 0.0) {
116 return Some(strikes[i]);
118 }
119 }
120 None
121}
122
123pub fn atm_straddle(
126 spot: f64,
127 strikes: &[f64],
128 ce_ltp: &[f64],
129 pe_ltp: &[f64],
130) -> (f64, f64, f64) {
131 if strikes.is_empty() {
132 return (0.0, 0.0, 0.0);
133 }
134
135 let mut closest_idx = 0;
136 let mut min_diff = f64::MAX;
137
138 for i in 0..strikes.len() {
139 let diff = (strikes[i] - spot).abs();
140 if diff < min_diff {
141 min_diff = diff;
142 closest_idx = i;
143 }
144 }
145
146 let atm_strike = strikes[closest_idx];
147 let straddle_premium = ce_ltp[closest_idx] + pe_ltp[closest_idx];
148 let implied_move_pct = (straddle_premium / spot) * 100.0;
149
150 (atm_strike, straddle_premium, implied_move_pct)
151}
152
153pub fn synthetic_futures(
156 strikes: &[f64],
157 ce_ltp: &[f64],
158 pe_ltp: &[f64],
159) -> Vec<f64> {
160 strikes.iter().enumerate().map(|(i, &k)| {
161 ce_ltp[i] - pe_ltp[i] + k
162 }).collect()
163}