use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone, Copy)]
pub struct DeMarkPivotLevels {
pub pivot: f64, pub resistance_1: f64, pub support_1: f64, pub x_value: f64, }
impl DeMarkPivotLevels {
pub fn empty() -> Self {
Self {
pivot: 0.0,
resistance_1: 0.0,
support_1: 0.0,
x_value: 0.0,
}
}
pub fn all_levels(&self) -> [f64; 3] {
[self.support_1, self.pivot, self.resistance_1]
}
pub fn trading_range(&self) -> f64 {
self.resistance_1 - self.support_1
}
pub fn is_in_range(&self, price: f64) -> bool {
price >= self.support_1 && price <= self.resistance_1
}
}
#[derive(Clone)]
pub struct DeMarkPivots {
current_levels: DeMarkPivotLevels,
levels_history: Vec<DeMarkPivotLevels>,
opens: Vec<f64>,
highs: Vec<f64>,
lows: Vec<f64>,
closes: Vec<f64>,
calculation_period: usize, bars_since_update: usize,
is_ready: bool,
update_count: usize,
}
impl DeMarkPivots {
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: DeMarkPivotLevels::empty(),
levels_history: Vec::with_capacity(100),
opens: Vec::with_capacity(32),
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) -> DeMarkPivotLevels {
self.opens.push(open);
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.opens.is_empty() || self.highs.is_empty() ||
self.lows.is_empty() || self.closes.is_empty() {
return;
}
let period_open = self.opens[0]; 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 x_value = if period_close < period_open {
period_high + (2.0 * period_low) + period_close
} else if period_close > period_open {
(2.0 * period_high) + period_low + period_close
} else {
period_high + period_low + (2.0 * period_close)
};
let pivot = x_value / 4.0;
let support_1 = (x_value / 2.0) - period_high;
let resistance_1 = (x_value / 2.0) - period_low;
let new_levels = DeMarkPivotLevels {
pivot,
resistance_1,
support_1,
x_value,
};
if self.levels_history.len() >= 100 {
self.levels_history.remove(0);
}
self.levels_history.push(self.current_levels);
self.current_levels = new_levels;
self.opens.clear();
self.highs.clear();
self.lows.clear();
self.closes.clear();
self.update_count += 1;
self.is_ready = true;
}
pub fn levels(&self) -> DeMarkPivotLevels {
self.current_levels
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn reset(&mut self) {
self.current_levels = DeMarkPivotLevels::empty();
self.levels_history.clear();
self.opens.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 {
if current_price > self.current_levels.support_1 {
self.current_levels.support_1
} else {
self.current_levels.support_1
}
}
pub fn nearest_resistance(&self, current_price: f64) -> f64 {
if current_price < self.current_levels.resistance_1 {
self.current_levels.resistance_1
} else {
self.current_levels.resistance_1
}
}
pub fn price_position(&self, current_price: f64) -> i8 {
if current_price < self.current_levels.support_1 {
-1 } else if current_price > self.current_levels.resistance_1 {
1 } else {
0 }
}
pub fn trading_signal(&self, current_price: f64, prev_price: f64) -> i8 {
let current_pos = self.price_position(current_price);
let prev_pos = self.price_position(prev_price);
if prev_pos <= 0 && current_pos == 1 {
return 1; }
if prev_pos >= 0 && current_pos == -1 {
return -1; }
0 }
pub fn distance_to_nearest_level(&self, current_price: f64) -> f64 {
let dist_to_support = (current_price - self.current_levels.support_1).abs();
let dist_to_resistance = (current_price - self.current_levels.resistance_1).abs();
let dist_to_pivot = (current_price - self.current_levels.pivot).abs();
dist_to_support.min(dist_to_resistance).min(dist_to_pivot)
}
pub fn level_strength(&self, level: f64, price_history: &[f64], tolerance_pct: f64) -> u32 {
let tolerance = level * tolerance_pct / 100.0;
price_history.iter()
.filter(|&&price| (price - level).abs() <= tolerance)
.count() as u32
}
pub fn levels_history(&self) -> Vec<DeMarkPivotLevels> {
self.levels_history.iter().cloned().collect()
}
pub fn info(&self, current_price: f64) -> String {
let levels = self.current_levels;
let position = match self.price_position(current_price) {
-1 => "Ниже поддержки",
0 => "В диапазоне",
1 => "Выше сопротивления",
_ => "Неизвестно",
};
format!(
"DeMark Pivots: PP={:.4}, R1={:.4}, S1={:.4}, X={:.4}, Позиция: {}, Диапазон: {:.4}",
levels.pivot,
levels.resistance_1,
levels.support_1,
levels.x_value,
position,
levels.trading_range()
)
}
pub fn period(&self) -> usize {
self.calculation_period
}
pub fn update_count(&self) -> usize {
self.update_count
}
}
impl DeMarkPivots {
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.current_levels.pivot)
}
pub fn additional_values(&self) -> std::collections::HashMap<String, f64> {
let mut values = std::collections::HashMap::new();
values.insert("pivot".to_string(), self.current_levels.pivot);
values.insert("resistance_1".to_string(), self.current_levels.resistance_1);
values.insert("support_1".to_string(), self.current_levels.support_1);
values.insert("x_value".to_string(), self.current_levels.x_value);
values.insert("trading_range".to_string(), self.current_levels.trading_range());
values
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_demark_pivots_basic() {
let mut demark = DeMarkPivots::new();
let levels = demark.update_bar(100.0, 105.0, 98.0, 103.0, 1000.0);
assert!((levels.x_value - 411.0).abs() < 0.001);
assert!((levels.pivot - 102.75).abs() < 0.001);
assert!((levels.support_1 - 100.5).abs() < 0.001);
assert!((levels.resistance_1 - 107.5).abs() < 0.001);
}
#[test]
fn test_demark_pivots_bearish() {
let mut demark = DeMarkPivots::new();
let levels = demark.update_bar(103.0, 105.0, 98.0, 100.0, 1000.0);
assert!((levels.x_value - 401.0).abs() < 0.001);
assert!((levels.pivot - 100.25).abs() < 0.001);
assert!((levels.support_1 - 95.5).abs() < 0.001);
assert!((levels.resistance_1 - 102.5).abs() < 0.001);
}
#[test]
fn test_demark_pivots_neutral() {
let mut demark = DeMarkPivots::new();
let levels = demark.update_bar(101.0, 105.0, 98.0, 101.0, 1000.0);
assert!((levels.x_value - 405.0).abs() < 0.001);
assert!((levels.pivot - 101.25).abs() < 0.001);
assert!((levels.support_1 - 97.5).abs() < 0.001);
assert!((levels.resistance_1 - 104.5).abs() < 0.001);
}
#[test]
fn test_price_position() {
let mut demark = DeMarkPivots::new();
let _levels = demark.update_bar(100.0, 105.0, 98.0, 103.0, 1000.0);
assert_eq!(demark.price_position(95.0), -1); assert_eq!(demark.price_position(104.0), 0); assert_eq!(demark.price_position(110.0), 1); }
#[test]
fn test_trading_signals() {
let mut demark = DeMarkPivots::new();
let _levels = demark.update_bar(100.0, 105.0, 98.0, 103.0, 1000.0);
assert_eq!(demark.trading_signal(108.0, 104.0), 1); assert_eq!(demark.trading_signal(99.0, 104.0), -1); assert_eq!(demark.trading_signal(104.0, 103.0), 0); }
}