use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone, Copy)]
pub struct ClassicPivotLevels {
pub pivot: f64, pub resistance_1: f64, pub resistance_2: f64, pub resistance_3: f64, pub support_1: f64, pub support_2: f64, pub support_3: f64, }
impl ClassicPivotLevels {
pub fn empty() -> Self {
Self {
pivot: 0.0,
resistance_1: 0.0,
resistance_2: 0.0,
resistance_3: 0.0,
support_1: 0.0,
support_2: 0.0,
support_3: 0.0,
}
}
pub fn resistance_levels(&self) -> [f64; 3] {
[self.resistance_1, self.resistance_2, self.resistance_3]
}
pub fn support_levels(&self) -> [f64; 3] {
[self.support_1, self.support_2, self.support_3]
}
pub fn all_levels(&self) -> [f64; 7] {
[
self.support_3,
self.support_2,
self.support_1,
self.pivot,
self.resistance_1,
self.resistance_2,
self.resistance_3,
]
}
}
#[derive(Clone)]
pub struct PivotPoints {
current_levels: ClassicPivotLevels,
levels_history: Vec<ClassicPivotLevels>,
highs: Vec<f64>,
lows: Vec<f64>,
closes: Vec<f64>,
calculation_period: usize, bars_since_update: usize,
is_ready: bool,
update_count: usize,
}
impl PivotPoints {
pub fn new() -> Self {
Self::with_period(1)
}
pub fn with_period(calculation_period: usize) -> Self {
assert!(calculation_period > 0, "Period must be greater than 0");
Self {
current_levels: ClassicPivotLevels::empty(),
levels_history: Vec::with_capacity(100),
highs: Vec::with_capacity(32),
lows: Vec::with_capacity(32),
closes: Vec::with_capacity(32),
calculation_period,
bars_since_update: 0,
is_ready: false,
update_count: 0,
}
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, close: f64, _volume: f64) -> ClassicPivotLevels {
self.highs.push(high);
self.lows.push(low);
self.closes.push(close);
self.bars_since_update += 1;
if self.bars_since_update >= self.calculation_period {
self.calculate_levels();
self.bars_since_update = 0;
}
self.current_levels
}
pub fn calculate_levels(&mut self) {
if self.highs.is_empty() {
return;
}
let period_high = self.highs.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let period_low = self.lows.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let period_close = self.closes[self.closes.len() - 1];
let pivot = (period_high + period_low + period_close) / 3.0;
let resistance_1 = (2.0 * pivot) - period_low;
let resistance_2 = pivot + (period_high - period_low);
let resistance_3 = period_high + 2.0 * (pivot - period_low);
let support_1 = (2.0 * pivot) - period_high;
let support_2 = pivot - (period_high - period_low);
let support_3 = period_low - 2.0 * (period_high - pivot);
let new_levels = ClassicPivotLevels {
pivot,
resistance_1,
resistance_2,
resistance_3,
support_1,
support_2,
support_3,
};
if self.levels_history.len() >= 100 {
self.levels_history.remove(0);
}
self.levels_history.push(self.current_levels);
self.current_levels = new_levels;
self.highs.clear();
self.lows.clear();
self.closes.clear();
self.update_count += 1;
self.is_ready = true;
}
pub fn levels(&self) -> ClassicPivotLevels {
self.current_levels
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn reset(&mut self) {
self.current_levels = ClassicPivotLevels::empty();
self.levels_history.clear();
self.highs.clear();
self.lows.clear();
self.closes.clear();
self.bars_since_update = 0;
self.is_ready = false;
self.update_count = 0;
}
pub fn nearest_support(&self, current_price: f64) -> f64 {
let supports = [
self.current_levels.support_1,
self.current_levels.support_2,
self.current_levels.support_3,
];
supports
.iter()
.filter(|&&level| level < current_price)
.fold(f64::NEG_INFINITY, |acc, &level| acc.max(level))
}
pub fn nearest_resistance(&self, current_price: f64) -> f64 {
let resistances = [
self.current_levels.resistance_1,
self.current_levels.resistance_2,
self.current_levels.resistance_3,
];
resistances
.iter()
.filter(|&&level| level > current_price)
.fold(f64::INFINITY, |acc, &level| acc.min(level))
}
pub fn price_position(&self, current_price: f64) -> i8 {
let pivot = self.current_levels.pivot;
let threshold = pivot * 0.001;
if current_price > pivot + threshold {
1 } else if current_price < pivot - threshold {
-1 } else {
0 }
}
pub fn trading_signal(&self, current_price: f64, prev_price: f64) -> i8 {
if !self.is_ready() {
return 0;
}
let levels = self.current_levels.all_levels();
for &level in &levels {
if prev_price <= level && current_price > level {
if level >= self.current_levels.pivot {
return 1; }
}
}
for &level in &levels {
if prev_price >= level && current_price < level {
if level <= self.current_levels.pivot {
return -1; }
}
}
0
}
pub fn distance_to_nearest_level(&self, current_price: f64) -> f64 {
if current_price.abs() < 1e-12 {
return 0.0;
}
let levels = self.current_levels.all_levels();
let nearest_distance = levels
.iter()
.map(|&level| (level - current_price).abs())
.fold(f64::INFINITY, |acc, dist| acc.min(dist));
(nearest_distance / current_price) * 100.0
}
pub fn level_strength(&self, level: f64, price_history: &[f64], tolerance_pct: f64) -> u32 {
if price_history.len() < 2 {
return 0;
}
let tolerance = level * (tolerance_pct / 100.0);
let mut touches = 0;
for i in 1..price_history.len() {
let prev_price = price_history[i - 1];
let current_price = price_history[i];
let near_level = (current_price - level).abs() <= tolerance;
let moving_away = (current_price - level).abs() > (prev_price - level).abs();
if near_level && moving_away {
touches += 1;
}
}
touches
}
pub fn levels_history(&self) -> Vec<ClassicPivotLevels> {
self.levels_history.iter().cloned().collect()
}
pub fn info(&self, current_price: f64) -> String {
format!(
"PP: {:.4}, Position: {}, Nearest S: {:.4}, Nearest R: {:.4}, Distance: {:.2}%",
self.current_levels.pivot,
match self.price_position(current_price) {
1 => "Above",
-1 => "Below",
_ => "At Pivot"
},
self.nearest_support(current_price),
self.nearest_resistance(current_price),
self.distance_to_nearest_level(current_price)
)
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Triple(
self.current_levels.resistance_1,
self.current_levels.support_1,
self.current_levels.pivot,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pivot_points_creation() {
let pp = PivotPoints::new();
assert!(!pp.is_ready());
}
#[test]
fn test_classic_pivot_levels() {
let levels = ClassicPivotLevels::empty();
assert_eq!(levels.pivot, 0.0);
assert_eq!(levels.resistance_1, 0.0);
assert_eq!(levels.support_1, 0.0);
}
#[test]
fn test_pivot_points_update() {
let mut pp = PivotPoints::new();
let levels = pp.update_bar(100.0, 105.0, 95.0, 102.0, 1000.0);
assert!(pp.is_ready());
assert!((levels.pivot - 100.67).abs() < 0.1);
}
#[test]
fn test_pivot_points_levels_order() {
let mut pp = PivotPoints::new();
let levels = pp.update_bar(100.0, 110.0, 90.0, 100.0, 1000.0);
assert!(levels.support_3 < levels.support_2);
assert!(levels.support_2 < levels.support_1);
assert!(levels.support_1 < levels.pivot);
assert!(levels.pivot < levels.resistance_1);
assert!(levels.resistance_1 < levels.resistance_2);
assert!(levels.resistance_2 < levels.resistance_3);
}
#[test]
fn test_pivot_points_reset() {
let mut pp = PivotPoints::new();
pp.update_bar(100.0, 105.0, 95.0, 102.0, 1000.0);
pp.reset();
assert!(!pp.is_ready());
}
}