use std::collections::HashMap;
use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone)]
pub struct CamarillaPivotLevels {
pub pivot_point: f64,
pub resistance_1: f64,
pub resistance_2: f64,
pub resistance_3: f64,
pub resistance_4: f64,
pub support_1: f64,
pub support_2: f64,
pub support_3: f64,
pub support_4: f64,
}
impl CamarillaPivotLevels {
pub fn new(high: f64, low: f64, close: f64) -> Self {
let pivot_point = (high + low + close) / 3.0;
let range = high - low;
let multiplier = 1.1;
Self {
pivot_point,
resistance_1: close + range * multiplier / 12.0,
resistance_2: close + range * multiplier / 6.0,
resistance_3: close + range * multiplier / 4.0,
resistance_4: close + range * multiplier / 2.0,
support_1: close - range * multiplier / 12.0,
support_2: close - range * multiplier / 6.0,
support_3: close - range * multiplier / 4.0,
support_4: close - range * multiplier / 2.0,
}
}
pub fn all_levels(&self) -> Vec<f64> {
vec![
self.support_4,
self.support_3,
self.support_2,
self.support_1,
self.pivot_point,
self.resistance_1,
self.resistance_2,
self.resistance_3,
self.resistance_4,
]
}
pub fn nearest_level(&self, price: f64) -> f64 {
self.all_levels()
.into_iter()
.min_by(|a, b| (a - price).abs().partial_cmp(&(b - price).abs()).unwrap())
.unwrap_or(self.pivot_point)
}
pub fn distance_to_nearest(&self, price: f64) -> f64 {
let nearest = self.nearest_level(price);
(price - nearest).abs()
}
pub fn price_zone(&self, price: f64) -> &'static str {
match price {
p if p >= self.resistance_4 => "Above R4",
p if p >= self.resistance_3 => "R3-R4",
p if p >= self.resistance_2 => "R2-R3",
p if p >= self.resistance_1 => "R1-R2",
p if p >= self.pivot_point => "PP-R1",
p if p >= self.support_1 => "S1-PP",
p if p >= self.support_2 => "S2-S1",
p if p >= self.support_3 => "S3-S2",
p if p >= self.support_4 => "S4-S3",
_ => "Below S4",
}
}
pub fn level_by_name(&self, name: &str) -> Option<f64> {
match name {
"PP" => Some(self.pivot_point),
"R1" => Some(self.resistance_1),
"R2" => Some(self.resistance_2),
"R3" => Some(self.resistance_3),
"R4" => Some(self.resistance_4),
"S1" => Some(self.support_1),
"S2" => Some(self.support_2),
"S3" => Some(self.support_3),
"S4" => Some(self.support_4),
_ => None,
}
}
pub fn level_strength(&self, level_name: &str, price: f64) -> f64 {
if let Some(level) = self.level_by_name(level_name) {
let distance = (price - level).abs();
if distance < 1e-6 {
f64::MAX
} else {
1.0 / distance
}
} else {
0.0
}
}
}
#[derive(Clone)]
pub struct CamarillaPivots {
current_levels: Option<CamarillaPivotLevels>,
update_period: usize,
period_high: f64,
period_low: f64,
period_close: f64,
bars_in_period: usize,
levels_history: Vec<CamarillaPivotLevels>,
level_touches: HashMap<String, u32>,
level_breaks: HashMap<String, u32>,
is_ready: bool,
}
impl CamarillaPivots {
pub fn new() -> Self {
Self::with_period(24) }
pub fn with_period(period: usize) -> Self {
assert!(period > 0, "Period must be greater than 0");
Self {
current_levels: None,
update_period: period,
period_high: f64::NEG_INFINITY,
period_low: f64::INFINITY,
period_close: 0.0,
bars_in_period: 0,
levels_history: Vec::with_capacity(100),
level_touches: HashMap::new(),
level_breaks: HashMap::new(),
is_ready: false,
}
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, close: f64, _volume: f64) -> Option<CamarillaPivotLevels> {
self.period_high = self.period_high.max(high);
self.period_low = self.period_low.min(low);
self.period_close = close;
self.bars_in_period += 1;
if let Some(levels) = self.current_levels.clone() {
self.check_level_interactions(high, low, close, &levels);
}
if self.bars_in_period >= self.update_period {
let new_levels = CamarillaPivotLevels::new(self.period_high, self.period_low, self.period_close);
if self.levels_history.len() >= 100 {
self.levels_history.remove(0);
}
self.levels_history.push(new_levels.clone());
self.current_levels = Some(new_levels);
self.period_high = f64::NEG_INFINITY;
self.period_low = f64::INFINITY;
self.period_close = 0.0;
self.bars_in_period = 0;
self.is_ready = true;
}
self.current_levels.clone()
}
fn check_level_interactions(&mut self, high: f64, low: f64, close: f64, levels: &CamarillaPivotLevels) {
let tolerance = 0.0005;
let level_names = vec![
("S4", levels.support_4),
("S3", levels.support_3),
("S2", levels.support_2),
("S1", levels.support_1),
("PP", levels.pivot_point),
("R1", levels.resistance_1),
("R2", levels.resistance_2),
("R3", levels.resistance_3),
("R4", levels.resistance_4),
];
for (name, level) in level_names {
if low <= level * (1.0 + tolerance) && high >= level * (1.0 - tolerance) {
*self.level_touches.entry(name.to_string()).or_insert(0) += 1;
}
if close > level * (1.0 + tolerance) || close < level * (1.0 - tolerance) {
*self.level_breaks.entry(name.to_string()).or_insert(0) += 1;
}
}
}
pub fn current_levels(&self) -> Option<&CamarillaPivotLevels> {
self.current_levels.as_ref()
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn period(&self) -> usize {
self.update_period
}
pub fn reset(&mut self) {
self.current_levels = None;
self.period_high = f64::NEG_INFINITY;
self.period_low = f64::INFINITY;
self.period_close = 0.0;
self.bars_in_period = 0;
self.levels_history.clear();
self.level_touches.clear();
self.level_breaks.clear();
self.is_ready = false;
}
pub fn trading_signal(&self, current_price: f64) -> i8 {
if let Some(levels) = &self.current_levels {
let tolerance = 0.001;
if (current_price - levels.support_3).abs() <= levels.support_3 * tolerance ||
(current_price - levels.support_4).abs() <= levels.support_4 * tolerance {
return 1;
}
if (current_price - levels.resistance_3).abs() <= levels.resistance_3 * tolerance ||
(current_price - levels.resistance_4).abs() <= levels.resistance_4 * tolerance {
return -1;
}
if current_price > levels.resistance_3 * (1.0 + tolerance) {
return 1;
}
if current_price < levels.support_3 * (1.0 - tolerance) {
return -1;
}
}
0
}
pub fn advanced_signal(&self, current_price: f64) -> i8 {
if let Some(levels) = &self.current_levels {
let zone = levels.price_zone(current_price);
match zone {
"S4-S3" | "Below S4" => {
if self.level_strength("S4") > 2 || self.level_strength("S3") > 2 {
return 1;
}
}
"R3-R4" | "Above R4" => {
if self.level_strength("R4") > 2 || self.level_strength("R3") > 2 {
return -1;
}
}
"S2-S1" => {
if self.level_strength("S2") > 1 {
return 1;
}
}
"R1-R2" => {
if self.level_strength("R2") > 1 {
return -1;
}
}
_ => {}
}
}
0
}
pub fn level_strength(&self, level_name: &str) -> u32 {
self.level_touches.get(level_name).copied().unwrap_or(0)
}
pub fn level_breaks(&self, level_name: &str) -> u32 {
self.level_breaks.get(level_name).copied().unwrap_or(0)
}
pub fn all_level_touches(&self) -> &HashMap<String, u32> {
&self.level_touches
}
pub fn all_level_breaks(&self) -> &HashMap<String, u32> {
&self.level_breaks
}
pub fn strongest_level(&self) -> Option<String> {
self.level_touches
.iter()
.max_by_key(|&(_, &count)| count)
.map(|(name, _)| name.clone())
}
pub fn levels_history(&self) -> &[CamarillaPivotLevels] {
&self.levels_history
}
pub fn market_volatility(&self) -> f64 {
if let Some(levels) = &self.current_levels {
levels.resistance_4 - levels.support_4
} else {
0.0
}
}
pub fn info(&self, current_price: f64) -> String {
if let Some(levels) = &self.current_levels {
format!(
"Camarilla - PP: {:.2}, Zone: {}, Nearest: {:.2}, Distance: {:.4}, Volatility: {:.2}",
levels.pivot_point,
levels.price_zone(current_price),
levels.nearest_level(current_price),
levels.distance_to_nearest(current_price),
self.market_volatility()
)
} else {
"Camarilla - Not Ready".to_string()
}
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(
self.current_levels
.as_ref()
.map(|l| l.pivot_point)
.unwrap_or(0.0)
)
}
}