use crate::common::validate_inputs;
pub use crate::indicator_types::TIndicatorState;
use crate::indicators::hilberttransform::ht_kernel;
use crate::indicators::simd_indicators::hilberttransform_simd::ht_kernel_base as ht_kernel_q_simd;
use crate::ring_buffer::fixed_single_buffer::FixedRingBuffer;
use crate::types::{DisplayGroup, DisplayType, IndicatorError, IndicatorType, Info};
use serde::{Deserialize, Serialize};
use std::simd::Simd;
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::homodynediscriminator_simd::indicator_by_assets;
#[cfg(feature = "simd_assets")]
pub mod by_assets {
pub use crate::indicators::simd_indicators::homodynediscriminator_simd::indicator_by_assets as indicator;
}
pub const INPUTS_WIDTH: usize = 1;
pub const OPTIONS_WIDTH: usize = 0;
pub const INFO: Info = Info {
name: "homodynediscriminator",
indicator_type: IndicatorType::Cycle,
full_name: "Ehlers Homodyne Discriminator",
inputs: &["real"],
options: &[],
outputs: &["dc_period"],
optional_outputs: &[],
display_groups: &[DisplayGroup {
offset: None,
id: "homodynediscriminator",
label: "Ehlers Homodyne Discriminator",
display_type: DisplayType::Indicator,
outputs: &["dc_period"],
}],
};
#[derive(Serialize, Deserialize, Clone)]
pub struct State {
pub price_buf: FixedRingBuffer<f64, 4>,
pub smooth_buf: FixedRingBuffer<f64, 7>,
pub detrender_buf: FixedRingBuffer<f64, 7>,
pub iq1_buf: FixedRingBuffer<Simd<f64, 2>, 7>,
pub i2_prev: f64,
pub q2_prev: f64,
pub re_prev: f64,
pub im_prev: f64,
pub period: f64,
pub smooth_period: f64,
}
impl State {
pub fn new() -> Self {
Self {
price_buf: FixedRingBuffer::new(),
smooth_buf: FixedRingBuffer::new(),
detrender_buf: FixedRingBuffer::new(),
iq1_buf: FixedRingBuffer::new(),
i2_prev: 0.0,
q2_prev: 0.0,
re_prev: 0.0,
im_prev: 0.0,
period: 0.0,
smooth_period: 0.0,
}
}
#[inline(always)]
pub(crate) fn all_buffers_full(&self) -> bool {
self.iq1_buf.is_full()
}
pub fn init_state(real: &[f64]) -> Self {
let mut state = Self::new();
let mut i = 0;
while !state.all_buffers_full() {
state.calc(real[i]);
i += 1;
}
state
}
#[inline(always)]
pub fn calc(&mut self, real: f64) -> f64 {
self.price_buf.push(real);
if self.price_buf.len() < 4 {
return 0.0;
}
let ab = 4.0_f64.mul_add(self.price_buf[0], 3.0 * self.price_buf[1]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * 0.1;
let gain = 0.075_f64.mul_add(self.period, 0.54);
self.smooth_buf.push(smooth);
if self.smooth_buf.len() < 7 {
return 0.0;
}
let (_, detrender) = ht_kernel(&self.smooth_buf, gain);
self.detrender_buf.push(detrender);
if self.detrender_buf.len() < 7 {
return 0.0;
}
let (i1, q1) = ht_kernel(&self.detrender_buf, gain);
self.iq1_buf.push(Simd::<f64, 2>::from_array([i1, q1]));
if self.iq1_buf.len() < 7 {
return 0.0;
}
let [j_i, j_q] = ht_kernel_q_simd(&self.iq1_buf, Simd::<f64, 2>::splat(gain)).to_array();
self.apply_discriminator(i1, q1, j_i, j_q)
}
#[inline(always)]
pub fn calc_with_iq(&mut self, real: f64) -> (f64, f64, f64) {
self.price_buf.push(real);
if self.price_buf.len() < 4 {
return (0.0, 0.0, 0.0);
}
let ab = 4.0_f64.mul_add(self.price_buf[0], 3.0 * self.price_buf[1]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * 0.1;
let gain = 0.075_f64.mul_add(self.period, 0.54);
self.smooth_buf.push(smooth);
if self.smooth_buf.len() < 7 {
return (0.0, 0.0, 0.0);
}
let (_, detrender) = ht_kernel(&self.smooth_buf, gain);
self.detrender_buf.push(detrender);
if self.detrender_buf.len() < 7 {
return (0.0, 0.0, 0.0);
}
let (i1, q1) = ht_kernel(&self.detrender_buf, gain);
self.iq1_buf.push(Simd::<f64, 2>::from_array([i1, q1]));
if self.iq1_buf.len() < 7 {
return (0.0, 0.0, 0.0);
}
let [j_i, j_q] = ht_kernel_q_simd(&self.iq1_buf, Simd::<f64, 2>::splat(gain)).to_array();
let dc_period = self.apply_discriminator(i1, q1, j_i, j_q);
(dc_period, i1, q1)
}
#[inline(always)]
pub unsafe fn calc_unchecked_with_iq(&mut self, real: f64) -> (f64, f64, f64) {
self.price_buf.push_unchecked(real);
let ab = 4.0_f64.mul_add(self.price_buf[0], 3.0 * self.price_buf[1]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * 0.1;
let gain = 0.075_f64.mul_add(self.period, 0.54);
self.smooth_buf.push_unchecked(smooth);
let (_, detrender) = ht_kernel(&self.smooth_buf, gain);
self.detrender_buf.push_unchecked(detrender);
let (i1, q1) = ht_kernel(&self.detrender_buf, gain);
self.iq1_buf
.push_unchecked(Simd::<f64, 2>::from_array([i1, q1]));
let [j_i, j_q] = ht_kernel_q_simd(&self.iq1_buf, Simd::<f64, 2>::splat(gain)).to_array();
let dc_period = self.apply_discriminator(i1, q1, j_i, j_q);
(dc_period, i1, q1)
}
#[inline(always)]
pub unsafe fn calc_unchecked(&mut self, real: f64) -> f64 {
self.price_buf.push_unchecked(real);
let ab = 4.0_f64.mul_add(self.price_buf[0], 3.0 * self.price_buf[1]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * 0.1;
let gain = 0.075_f64.mul_add(self.period, 0.54);
self.smooth_buf.push_unchecked(smooth);
let (_, detrender) = ht_kernel(&self.smooth_buf, gain);
self.detrender_buf.push_unchecked(detrender);
let (i1, q1) = ht_kernel(&self.detrender_buf, gain);
self.iq1_buf
.push_unchecked(Simd::<f64, 2>::from_array([i1, q1]));
let [j_i, j_q] = ht_kernel_q_simd(&self.iq1_buf, Simd::<f64, 2>::splat(gain)).to_array();
self.apply_discriminator(i1, q1, j_i, j_q)
}
#[inline(always)]
fn apply_discriminator(&mut self, i1: f64, q1: f64, j_i: f64, j_q: f64) -> f64 {
let i2_raw = i1 - j_q;
let q2_raw = q1 + j_i;
let i2 = 0.2_f64.mul_add(i2_raw, 0.8 * self.i2_prev);
let q2 = 0.2_f64.mul_add(q2_raw, 0.8 * self.q2_prev);
let re_raw = i2.mul_add(self.i2_prev, q2 * self.q2_prev);
let im_raw = i2.mul_add(self.q2_prev, -(q2 * self.i2_prev));
self.i2_prev = i2;
self.q2_prev = q2;
let re = 0.2_f64.mul_add(re_raw, 0.8 * self.re_prev);
let im = 0.2_f64.mul_add(im_raw, 0.8 * self.im_prev);
self.re_prev = re;
self.im_prev = im;
let mut period = if im != 0.0 && re != 0.0 {
std::f64::consts::TAU / (im / re).atan()
} else {
self.period
};
period = period.min(1.5 * self.period);
period = period.max(0.67 * self.period);
period = period.clamp(6.0, 50.0);
period = 0.2_f64.mul_add(period, 0.8 * self.period);
self.smooth_period = 0.33_f64.mul_add(period, 0.67 * self.smooth_period);
self.period = period;
self.smooth_period
}
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
#[derive(Serialize, Deserialize)]
pub struct IndicatorState {
state: State,
}
impl IndicatorState {
pub fn new(state: State) -> Self {
Self { state }
}
}
impl TIndicatorState<INPUTS_WIDTH> for IndicatorState {
fn batch_indicator(
&mut self,
inputs: &[&[f64]; INPUTS_WIDTH],
_optional_outputs: Option<&[bool]>,
) -> Result<Vec<Vec<f64>>, IndicatorError> {
validate_inputs(inputs, 1)?;
let len = inputs[0].len();
let mut dc_period_line = crate::uninit_vec!(f64, len);
cycle(inputs[0], &mut self.state, &mut dc_period_line);
Ok(vec![dc_period_line])
}
}
pub fn min_data(_options: &[f64]) -> usize {
23
}
pub fn output_length(data_len: usize, options: &[f64]) -> usize {
data_len - min_data(options) + 1
}
pub fn indicator(
inputs: &[&[f64]; INPUTS_WIDTH],
options: &[f64; OPTIONS_WIDTH],
_optional_outputs: Option<&[bool]>,
) -> Result<(Vec<Vec<f64>>, IndicatorState), IndicatorError> {
validate_inputs(inputs, min_data(options))?;
let capacity = output_length(inputs[0].len(), options);
let mut dc_period_line = crate::uninit_vec!(f64, capacity);
let mut state = State::init_state(inputs[0]);
let real = &inputs[0][(min_data(options) - 1)..];
cycle(real, &mut state, &mut dc_period_line);
Ok((vec![dc_period_line], IndicatorState::new(state)))
}
fn cycle(real: &[f64], state: &mut State, dc_period_line: &mut [f64]) {
for i in 0..real.len() {
let dc = unsafe { state.calc_unchecked(*real.get_unchecked(i)) };
unsafe {
*dc_period_line.get_unchecked_mut(i) = dc;
}
}
}