#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::zigzag::ZigZagError;
const BASIS_POINTS_SCALE: i128 = 100_000;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ZigZagConfig {
pub reversal_depth: f64,
pub epsilon_multiplier: f64,
pub bar_threshold_dbps: u32,
pub threshold_mode: ThresholdMode,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ThresholdMode {
BarCount,
}
impl ZigZagConfig {
pub fn new(
reversal_depth: f64,
epsilon_multiplier: f64,
bar_threshold_dbps: u32,
) -> Result<Self, ZigZagError> {
Self::validate(reversal_depth, epsilon_multiplier, bar_threshold_dbps)?;
Ok(Self {
reversal_depth,
epsilon_multiplier,
bar_threshold_dbps,
threshold_mode: ThresholdMode::BarCount,
})
}
fn validate(
reversal_depth: f64,
epsilon_multiplier: f64,
bar_threshold_dbps: u32,
) -> Result<(), ZigZagError> {
if reversal_depth <= 0.0 {
return Err(ZigZagError::InvalidConfig(
"reversal_depth must be positive",
));
}
if epsilon_multiplier < 0.0 {
return Err(ZigZagError::InvalidConfig(
"epsilon_multiplier must be non-negative",
));
}
if epsilon_multiplier >= reversal_depth {
return Err(ZigZagError::InvalidConfig(
"epsilon_multiplier must be less than reversal_depth",
));
}
if bar_threshold_dbps == 0 {
return Err(ZigZagError::InvalidConfig(
"bar_threshold_dbps must be non-zero",
));
}
Ok(())
}
}
#[inline]
pub fn compute_delta(reference_price: i64, bar_threshold_dbps: u32) -> i64 {
let delta = (reference_price as i128 * bar_threshold_dbps as i128) / BASIS_POINTS_SCALE;
delta as i64
}
#[inline]
pub fn compute_tau(config: &ZigZagConfig, reference_price: i64) -> i64 {
let delta = compute_delta(reference_price, config.bar_threshold_dbps);
(config.reversal_depth * delta as f64).round() as i64
}
#[inline]
pub fn compute_epsilon(config: &ZigZagConfig, reference_price: i64) -> i64 {
let delta = compute_delta(reference_price, config.bar_threshold_dbps);
(config.epsilon_multiplier * delta as f64).round() as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_validation() {
assert!(ZigZagConfig::new(3.0, 1.0, 250).is_ok());
assert!(ZigZagConfig::new(3.0, 3.0, 250).is_err());
assert!(ZigZagConfig::new(3.0, 4.0, 250).is_err());
assert!(ZigZagConfig::new(0.0, 0.0, 250).is_err());
assert!(ZigZagConfig::new(-1.0, 0.0, 250).is_err());
assert!(ZigZagConfig::new(3.0, 1.0, 0).is_err());
}
#[test]
fn test_compute_delta_btcusdt() {
let price = 5_000_000_000_000_i64; let delta = compute_delta(price, 250);
assert_eq!(delta, 12_500_000_000); }
#[test]
fn test_compute_tau() {
let config = ZigZagConfig::new(3.0, 1.0, 250).unwrap();
let price = 5_000_000_000_000_i64;
assert_eq!(compute_tau(&config, price), 37_500_000_000); }
#[test]
fn test_compute_epsilon() {
let config = ZigZagConfig::new(3.0, 1.0, 250).unwrap();
let price = 5_000_000_000_000_i64;
assert_eq!(compute_epsilon(&config, price), 12_500_000_000); }
}