use std::f64::consts::PI;
use crate::bar_indicators::ohlcv_field::OhlcvField;
#[derive(Debug, Clone)]
pub struct AnalyticSignal {
pub real_part: f64, pub imaginary_part: f64, pub instantaneous_amplitude: f64, pub instantaneous_phase: f64, pub instantaneous_frequency: f64, }
impl Default for AnalyticSignal {
fn default() -> Self {
Self::new()
}
}
impl AnalyticSignal {
pub fn new() -> Self {
Self {
real_part: 0.0,
imaginary_part: 0.0,
instantaneous_amplitude: 0.0,
instantaneous_phase: 0.0,
instantaneous_frequency: 0.0,
}
}
}
#[derive(Clone)]
pub struct HilbertTransform {
source: OhlcvField,
time_series: Vec<f64>,
hilbert_transform: Vec<f64>, analytic_signal: AnalyticSignal,
amplitude_history: Vec<f64>, phase_history: Vec<f64>, frequency_history: Vec<f64>,
frequency_filter_length: usize,
window_size: usize, sampling_rate: f64,
avg_amplitude: f64, amplitude_variance: f64, phase_coherence: f64, frequency_stability: f64,
is_ready: bool,
min_samples: usize,
}
impl HilbertTransform {
pub fn new(window_size: usize, sampling_rate: f64) -> Self {
let window_size = window_size.clamp(16, 256);
Self {
source: OhlcvField::Close,
time_series: Vec::with_capacity(512),
hilbert_transform: Vec::with_capacity(512),
analytic_signal: AnalyticSignal::new(),
amplitude_history: Vec::with_capacity(512),
phase_history: Vec::with_capacity(512),
frequency_history: Vec::with_capacity(512),
frequency_filter_length: 5,
window_size,
sampling_rate,
avg_amplitude: 0.0,
amplitude_variance: 0.0,
phase_coherence: 0.0,
frequency_stability: 0.0,
is_ready: false,
min_samples: window_size * 2,
}
}
pub fn with_source(window_size: usize, sampling_rate: f64, source: OhlcvField) -> Self {
let mut s = Self::new(window_size, sampling_rate);
s.source = source;
s
}
pub fn update(&mut self, value: f64) -> &AnalyticSignal {
if self.time_series.len() >= 512 {
self.time_series.remove(0);
}
self.time_series.push(value);
if self.time_series.len() >= self.min_samples {
self.compute_hilbert_transform();
self.compute_analytic_signal();
self.update_statistics();
self.is_ready = true;
}
&self.analytic_signal
}
fn compute_hilbert_transform(&mut self) {
self.hilbert_transform.clear();
let n = self.time_series.len();
for i in 0..n {
let mut hilbert_value = 0.0;
let mut weight_sum = 0.0;
let window_start = i.saturating_sub(self.window_size / 2);
let window_end = (i + self.window_size / 2).min(n);
for j in window_start..window_end {
if i != j {
let tau_diff = i as f64 - j as f64;
let weight = 1.0 / (PI * tau_diff);
let window_coeff = self.hamming_window(j, window_start, window_end);
hilbert_value += weight * self.time_series[j] * window_coeff;
weight_sum += weight.abs();
}
}
if weight_sum > 0.0 {
hilbert_value /= weight_sum;
}
self.hilbert_transform.push(hilbert_value);
}
}
fn hamming_window(&self, index: usize, start: usize, end: usize) -> f64 {
let n = end - start;
if n <= 1 {
return 1.0;
}
let i = index - start;
0.54 - 0.46 * (2.0 * PI * i as f64 / (n - 1) as f64).cos()
}
fn compute_analytic_signal(&mut self) {
if self.time_series.is_empty() || self.hilbert_transform.is_empty() {
return;
}
let last_idx = self.time_series.len() - 1;
self.analytic_signal.real_part = self.time_series[last_idx];
self.analytic_signal.imaginary_part = if last_idx < self.hilbert_transform.len() {
self.hilbert_transform[last_idx]
} else {
0.0
};
self.analytic_signal.instantaneous_amplitude = (
self.analytic_signal.real_part.powi(2) +
self.analytic_signal.imaginary_part.powi(2)
).sqrt();
self.analytic_signal.instantaneous_phase = self.analytic_signal.imaginary_part
.atan2(self.analytic_signal.real_part);
self.compute_instantaneous_frequency();
self.save_to_history();
}
fn compute_instantaneous_frequency(&mut self) {
if self.phase_history.len() < 2 {
self.analytic_signal.instantaneous_frequency = 0.0;
return;
}
let current_phase = self.analytic_signal.instantaneous_phase;
let prev_phase = self.phase_history[self.phase_history.len() - 1];
let mut phase_diff = current_phase - prev_phase;
while phase_diff > PI {
phase_diff -= 2.0 * PI;
}
while phase_diff < -PI {
phase_diff += 2.0 * PI;
}
let raw_frequency = phase_diff * self.sampling_rate / (2.0 * PI);
self.analytic_signal.instantaneous_frequency = self.smooth_frequency(raw_frequency);
}
fn smooth_frequency(&self, new_frequency: f64) -> f64 {
if self.frequency_history.len() < self.frequency_filter_length {
return new_frequency;
}
let start_idx = self.frequency_history.len().saturating_sub(self.frequency_filter_length);
let sum: f64 = self.frequency_history[start_idx..].iter().sum();
let avg = sum / (self.frequency_history.len() - start_idx) as f64;
0.7 * avg + 0.3 * new_frequency
}
fn save_to_history(&mut self) {
if self.amplitude_history.len() >= 512 {
self.amplitude_history.remove(0);
}
self.amplitude_history.push(self.analytic_signal.instantaneous_amplitude);
if self.phase_history.len() >= 512 {
self.phase_history.remove(0);
}
self.phase_history.push(self.analytic_signal.instantaneous_phase);
if self.frequency_history.len() >= 512 {
self.frequency_history.remove(0);
}
self.frequency_history.push(self.analytic_signal.instantaneous_frequency);
}
fn update_statistics(&mut self) {
if !self.amplitude_history.is_empty() {
self.avg_amplitude = self.amplitude_history.iter().sum::<f64>() / self.amplitude_history.len() as f64;
let variance_sum: f64 = self.amplitude_history.iter()
.map(|&a| (a - self.avg_amplitude).powi(2))
.sum();
self.amplitude_variance = variance_sum / self.amplitude_history.len() as f64;
}
self.phase_coherence = self.calculate_phase_coherence();
self.frequency_stability = self.calculate_frequency_stability();
}
fn calculate_phase_coherence(&self) -> f64 {
if self.phase_history.len() < 3 {
return 0.0;
}
let mut phase_diff_variance = 0.0;
let mut valid_diffs = 0;
for i in 1..self.phase_history.len() {
let phase_diff = self.phase_history[i] - self.phase_history[i - 1];
let normalized_diff = ((phase_diff + PI) % (2.0 * PI)) - PI;
phase_diff_variance += normalized_diff.powi(2);
valid_diffs += 1;
}
if valid_diffs > 0 {
phase_diff_variance /= valid_diffs as f64;
1.0 / (1.0 + phase_diff_variance)
} else {
0.0
}
}
fn calculate_frequency_stability(&self) -> f64 {
if self.frequency_history.len() < 2 {
return 0.0;
}
let mean_freq: f64 = self.frequency_history.iter().sum::<f64>() / self.frequency_history.len() as f64;
let freq_variance: f64 = self.frequency_history.iter()
.map(|&f| (f - mean_freq).powi(2))
.sum::<f64>() / self.frequency_history.len() as f64;
if mean_freq.abs() > 1e-10 {
let cv = freq_variance.sqrt() / mean_freq.abs();
1.0 / (1.0 + cv)
} else {
0.0
}
}
pub fn analytic_signal(&self) -> &AnalyticSignal {
&self.analytic_signal
}
pub fn instantaneous_amplitude(&self) -> f64 {
self.analytic_signal.instantaneous_amplitude
}
pub fn instantaneous_phase(&self) -> f64 {
self.analytic_signal.instantaneous_phase
}
pub fn instantaneous_frequency(&self) -> f64 {
self.analytic_signal.instantaneous_frequency
}
pub fn average_amplitude(&self) -> f64 {
self.avg_amplitude
}
pub fn amplitude_variance(&self) -> f64 {
self.amplitude_variance
}
pub fn phase_coherence(&self) -> f64 {
self.phase_coherence
}
pub fn frequency_stability(&self) -> f64 {
self.frequency_stability
}
pub fn amplitude_history(&self) -> &[f64] {
&self.amplitude_history
}
pub fn phase_history(&self) -> &[f64] {
&self.phase_history
}
pub fn frequency_history(&self) -> &[f64] {
&self.frequency_history
}
pub fn set_frequency_filter_length(&mut self, length: usize) {
self.frequency_filter_length = length.clamp(1, 20);
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn window_size(&self) -> usize {
self.window_size
}
pub fn sampling_rate(&self) -> f64 {
self.sampling_rate
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) {
let value = self.source.extract(open, high, low, close, volume);
self.update(value);
}
pub fn value(&self) -> crate::bar_indicators::indicator_value::IndicatorValue {
crate::bar_indicators::indicator_value::IndicatorValue::Hilbert {
amplitude: self.analytic_signal.instantaneous_amplitude,
phase: self.analytic_signal.instantaneous_phase,
frequency: self.analytic_signal.instantaneous_frequency,
}
}
pub fn reset(&mut self) {
self.time_series.clear();
self.hilbert_transform.clear();
self.analytic_signal = AnalyticSignal::new();
self.amplitude_history.clear();
self.phase_history.clear();
self.frequency_history.clear();
self.avg_amplitude = 0.0;
self.amplitude_variance = 0.0;
self.phase_coherence = 0.0;
self.frequency_stability = 0.0;
self.is_ready = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hilbert_creation() {
let ht = HilbertTransform::new(32, 100.0);
assert!(!ht.is_ready());
assert_eq!(ht.window_size(), 32);
assert_eq!(ht.sampling_rate(), 100.0);
}
#[test]
fn test_hilbert_update() {
let mut ht = HilbertTransform::new(32, 100.0);
for i in 0..150 {
let value = (i as f64 * 0.1).sin() * 10.0 + 100.0;
ht.update(value);
}
assert!(ht.is_ready());
assert!(ht.instantaneous_amplitude().is_finite());
assert!(ht.instantaneous_phase().is_finite());
}
#[test]
fn test_hilbert_analytic_signal() {
let mut ht = HilbertTransform::new(32, 100.0);
for i in 0..150 {
ht.update(100.0 + (i as f64 * 0.2).sin() * 5.0);
}
let sig = ht.analytic_signal();
assert!(sig.real_part.is_finite());
assert!(sig.imaginary_part.is_finite());
}
#[test]
fn test_hilbert_reset() {
let mut ht = HilbertTransform::new(32, 100.0);
for i in 0..150 {
ht.update(100.0 + i as f64);
}
assert!(ht.is_ready());
ht.reset();
assert!(!ht.is_ready());
}
}