Skip to main content

kestrel_chartkit/structure/
mod.rs

1pub mod zone_registry;
2
3pub use zone_registry::*;
4
5use crate::model::{Bar, SupportResistanceZone, ZoneKind};
6
7pub fn find_sr_zones(bars: &[Bar], pivot_len: usize) -> Vec<SupportResistanceZone> {
8    if bars.len() < pivot_len * 2 + 1 {
9        return Vec::new();
10    }
11
12    let mut supports = Vec::new();
13    let mut resistances = Vec::new();
14    let current_price = bars.last().map(|b| b.close).unwrap_or(1.0);
15
16    for i in pivot_len..(bars.len() - pivot_len) {
17        let candidate_high = bars[i].high;
18        let is_pivot_high = bars[i - pivot_len..=i + pivot_len]
19            .iter()
20            .all(|b| b.high <= candidate_high);
21
22        if is_pivot_high {
23            resistances.push(candidate_high);
24        }
25
26        let candidate_low = bars[i].low;
27        let is_pivot_low = bars[i - pivot_len..=i + pivot_len]
28            .iter()
29            .all(|b| b.low >= candidate_low);
30
31        if is_pivot_low {
32            supports.push(candidate_low);
33        }
34    }
35
36    let mut zones = Vec::new();
37
38    // Sort supports descending (closest to price first)
39    supports.sort_by(|a, b| b.partial_cmp(a).unwrap());
40    supports.dedup_by(|a, b| (*a - *b).abs() < current_price * 0.005);
41
42    for sup in supports.into_iter().filter(|s| *s < current_price).take(2) {
43        let dist = (sup - current_price) / current_price * 100.0;
44        zones.push(SupportResistanceZone {
45            kind: ZoneKind::Support,
46            price: sup,
47            price_top: sup + current_price * 0.0025,
48            price_bottom: sup - current_price * 0.0025,
49            strength: 0.8,
50            distance_pct: dist,
51            touches: 1,
52        });
53    }
54
55    // Sort resistances ascending (closest to price first)
56    resistances.sort_by(|a, b| a.partial_cmp(b).unwrap());
57    resistances.dedup_by(|a, b| (*a - *b).abs() < current_price * 0.005);
58
59    for res in resistances
60        .into_iter()
61        .filter(|r| *r > current_price)
62        .take(2)
63    {
64        let dist = (res - current_price) / current_price * 100.0;
65        zones.push(SupportResistanceZone {
66            kind: ZoneKind::Resistance,
67            price: res,
68            price_top: res + current_price * 0.0025,
69            price_bottom: res - current_price * 0.0025,
70            strength: 0.8,
71            distance_pct: dist,
72            touches: 1,
73        });
74    }
75
76    zones
77}