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