kestrel_chartkit/structure/
mod.rs1pub 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 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 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}