use crate::bar_indicators::average::{MovingAverageProvider, MovingAverageType};
use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone)]
pub struct Pvo {
fast_period: usize,
slow_period: usize,
signal_period: usize,
fast_ma_type: MovingAverageType,
slow_ma_type: MovingAverageType,
signal_ma_type: MovingAverageType,
fast_ma: MovingAverageProvider,
slow_ma: MovingAverageProvider,
signal_ma: MovingAverageProvider,
value: f64,
signal: f64,
ready: bool,
}
impl Pvo {
pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self {
Self::new_with_ma_type(fast_period, slow_period, signal_period, MovingAverageType::EMA)
}
pub fn new_with_ma_type(fast_period: usize, slow_period: usize, signal_period: usize, ma_type: MovingAverageType) -> Self {
let fast = fast_period.max(1);
let slow = slow_period.max(1);
let signal = signal_period.max(1);
Self {
fast_period: fast,
slow_period: slow,
signal_period: signal,
fast_ma_type: ma_type,
slow_ma_type: ma_type,
signal_ma_type: ma_type,
fast_ma: MovingAverageProvider::new(ma_type, fast),
slow_ma: MovingAverageProvider::new(ma_type, slow),
signal_ma: MovingAverageProvider::new(ma_type, signal),
value: 0.0,
signal: 0.0,
ready: false,
}
}
pub fn with_ma_types(
fast_period: usize,
slow_period: usize,
signal_period: usize,
fast_ma_type: MovingAverageType,
slow_ma_type: MovingAverageType,
signal_ma_type: MovingAverageType,
) -> Self {
let fast = fast_period.max(1);
let slow = slow_period.max(1);
let signal = signal_period.max(1);
Self {
fast_period: fast,
slow_period: slow,
signal_period: signal,
fast_ma_type,
slow_ma_type,
signal_ma_type,
fast_ma: MovingAverageProvider::new(fast_ma_type, fast),
slow_ma: MovingAverageProvider::new(slow_ma_type, slow),
signal_ma: MovingAverageProvider::new(signal_ma_type, signal),
value: 0.0,
signal: 0.0,
ready: false,
}
}
pub fn update_bar(&mut self, _o: f64, _h: f64, _l: f64, _c: f64, v: f64) -> f64 {
let _ = self.fast_ma.update_bar(0.0, 0.0, 0.0, v, 0.0);
let _ = self.slow_ma.update_bar(0.0, 0.0, 0.0, v, 0.0);
let fast = self.fast_ma.value().main();
let slow = self.slow_ma.value().main();
let denom = if slow.abs() < 1e-12 { 1e-12 } else { slow };
self.value = 100.0 * (fast - slow) / denom;
if self.fast_ma.is_ready() && self.slow_ma.is_ready() {
let _ = self.signal_ma.update_bar(0.0, 0.0, 0.0, self.value, 0.0);
self.signal = self.signal_ma.value().main();
}
self.ready =
self.fast_ma.is_ready() && self.slow_ma.is_ready() && self.signal_ma.is_ready();
self.value
}
pub fn set_ma_type(&mut self, ma_type: MovingAverageType) {
self.fast_ma_type = ma_type;
self.slow_ma_type = ma_type;
self.signal_ma_type = ma_type;
self.reset();
}
pub fn set_fast_ma_type(&mut self, ma_type: MovingAverageType) {
if self.fast_ma_type != ma_type {
self.fast_ma = MovingAverageProvider::new(ma_type, self.fast_period);
self.fast_ma_type = ma_type;
self.ready = false;
}
}
pub fn set_slow_ma_type(&mut self, ma_type: MovingAverageType) {
if self.slow_ma_type != ma_type {
self.slow_ma = MovingAverageProvider::new(ma_type, self.slow_period);
self.slow_ma_type = ma_type;
self.ready = false;
}
}
pub fn set_signal_ma_type(&mut self, ma_type: MovingAverageType) {
if self.signal_ma_type != ma_type {
self.signal_ma = MovingAverageProvider::new(ma_type, self.signal_period);
self.signal_ma_type = ma_type;
self.ready = false;
}
}
#[inline]
pub fn get_fast_ma_type(&self) -> MovingAverageType {
self.fast_ma_type
}
#[inline]
pub fn get_slow_ma_type(&self) -> MovingAverageType {
self.slow_ma_type
}
#[inline]
pub fn get_signal_ma_type(&self) -> MovingAverageType {
self.signal_ma_type
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Macd {
line: self.value,
signal: self.signal,
histogram: self.value - self.signal,
}
}
pub fn value_pvo(&self) -> f64 {
self.value
}
pub fn value_signal(&self) -> f64 {
self.signal
}
pub fn value_histogram(&self) -> f64 {
self.value - self.signal
}
pub fn is_ready(&self) -> bool {
self.ready
}
pub fn reset(&mut self) {
self.fast_ma = MovingAverageProvider::new(self.fast_ma_type, self.fast_period);
self.slow_ma = MovingAverageProvider::new(self.slow_ma_type, self.slow_period);
self.signal_ma = MovingAverageProvider::new(self.signal_ma_type, self.signal_period);
self.value = 0.0;
self.signal = 0.0;
self.ready = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pvo_creation() {
let pvo = Pvo::new(12, 26, 9);
assert!(!pvo.is_ready());
assert_eq!(pvo.value_pvo(), 0.0);
assert_eq!(pvo.value_signal(), 0.0);
}
#[test]
fn test_pvo_warmup() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
pvo.update_bar(price, price + 1.0, price - 1.0, price, 1000.0 + i as f64 * 10.0);
}
assert!(pvo.is_ready());
}
#[test]
fn test_pvo_values_finite() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
let price = 100.0 + i as f64;
let volume = 1000.0 + (i as f64 * 0.2).sin() * 500.0;
let value = pvo.update_bar(price, price + 1.0, price - 1.0, price, volume);
assert!(value.is_finite());
}
}
#[test]
fn test_pvo_histogram() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
let histogram = pvo.value_histogram();
assert!(histogram.is_finite());
assert_eq!(histogram, pvo.value_pvo() - pvo.value_signal());
}
#[test]
fn test_pvo_reset() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 10.0);
}
pvo.reset();
assert!(!pvo.is_ready());
assert_eq!(pvo.value_pvo(), 0.0);
assert_eq!(pvo.value_signal(), 0.0);
}
#[test]
fn test_pvo_with_sma() {
let mut pvo = Pvo::new_with_ma_type(12, 26, 9, MovingAverageType::SMA);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
assert!(pvo.is_ready());
assert_eq!(pvo.get_fast_ma_type(), MovingAverageType::SMA);
assert_eq!(pvo.get_slow_ma_type(), MovingAverageType::SMA);
assert_eq!(pvo.get_signal_ma_type(), MovingAverageType::SMA);
}
#[test]
fn test_pvo_with_different_ma_types() {
let mut pvo = Pvo::with_ma_types(
12, 26, 9,
MovingAverageType::EMA,
MovingAverageType::SMA,
MovingAverageType::WMA
);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
assert!(pvo.is_ready());
assert_eq!(pvo.get_fast_ma_type(), MovingAverageType::EMA);
assert_eq!(pvo.get_slow_ma_type(), MovingAverageType::SMA);
assert_eq!(pvo.get_signal_ma_type(), MovingAverageType::WMA);
}
#[test]
fn test_pvo_set_individual_ma_types() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
assert!(pvo.is_ready());
pvo.set_fast_ma_type(MovingAverageType::SMA);
assert!(!pvo.is_ready()); assert_eq!(pvo.get_fast_ma_type(), MovingAverageType::SMA);
pvo.set_slow_ma_type(MovingAverageType::WMA);
assert_eq!(pvo.get_slow_ma_type(), MovingAverageType::WMA);
pvo.set_signal_ma_type(MovingAverageType::SMA);
assert_eq!(pvo.get_signal_ma_type(), MovingAverageType::SMA);
}
#[test]
fn test_pvo_set_ma_type_all() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
assert!(pvo.is_ready());
pvo.set_ma_type(MovingAverageType::WMA);
assert!(!pvo.is_ready()); assert_eq!(pvo.get_fast_ma_type(), MovingAverageType::WMA);
assert_eq!(pvo.get_slow_ma_type(), MovingAverageType::WMA);
assert_eq!(pvo.get_signal_ma_type(), MovingAverageType::WMA);
}
#[test]
fn test_pvo_value_returns_macd_type() {
let mut pvo = Pvo::new(12, 26, 9);
for i in 0..50 {
pvo.update_bar(100.0, 101.0, 99.0, 100.0, 1000.0 + i as f64 * 100.0);
}
match pvo.value() {
IndicatorValue::Macd { line, signal, histogram } => {
assert!((line - pvo.value_pvo()).abs() < 1e-10);
assert!((signal - pvo.value_signal()).abs() < 1e-10);
assert!((histogram - pvo.value_histogram()).abs() < 1e-10);
}
_ => panic!("Expected Macd variant"),
}
}
}