use crate::common::validate_inputs;
pub use crate::indicator_types::TIndicatorState;
use crate::ring_buffer::fixed_single_buffer::FixedRingBuffer;
use crate::types::{DisplayGroup, DisplayType, IndicatorError, IndicatorType, Info};
use serde::{Deserialize, Serialize};
pub const INPUTS_WIDTH: usize = 1;
pub const OPTIONS_WIDTH: usize = 1;
#[cfg(feature = "simd_assets")]
pub use crate::indicators::simd_indicators::cybercycle_simd::indicator_by_assets;
#[cfg(feature = "simd_options")]
pub use crate::indicators::simd_indicators::cybercycle_simd::indicator_by_options;
#[cfg(feature = "simd_assets")]
pub mod by_assets {
pub use crate::indicators::simd_indicators::cybercycle_simd::indicator_by_assets as indicator;
}
#[cfg(feature = "simd_options")]
pub mod by_options {
pub use crate::indicators::simd_indicators::cybercycle_simd::indicator_by_options as indicator;
}
pub const INFO: Info = Info {
name: "cybercycle",
indicator_type: IndicatorType::Cycle,
full_name: "Ehlers CyberCycle",
inputs: &["real"],
options: &["alpha"],
outputs: &["cybercycle"],
optional_outputs: &["trigger"],
display_groups: &[DisplayGroup {
offset: None,
id: "cybercycle",
label: "Ehlers Cyber Cycle",
display_type: DisplayType::Indicator,
outputs: &["cybercycle", "trigger"],
}],
};
#[derive(Serialize, Deserialize)]
pub struct IndicatorState {
pub(crate) multipliers: (f64, f64, f64),
pub(crate) state: State,
}
impl IndicatorState {
pub fn new(state: State, multipliers: (f64, f64, f64)) -> Self {
Self { multipliers, 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 real = inputs[0];
let n = real.len();
let mut cycle_line = crate::uninit_vec!(f64, n);
let mut trigger_line = crate::init_optional_outputs_eff!(
optional_outputs, &[false],
trigger_line: n
);
run_cycle(
real,
&mut self.state,
self.multipliers,
&mut cycle_line,
&mut trigger_line,
);
Ok(vec![cycle_line, trigger_line])
}
}
#[derive(Serialize, Deserialize)]
pub struct State {
pub price_buf: FixedRingBuffer<f64, 4>,
pub smooth_buf: FixedRingBuffer<f64, 3>,
pub cycle_prev: f64,
pub cycle_prev2: f64,
}
impl State {
pub fn new() -> Self {
Self {
price_buf: FixedRingBuffer::new(),
smooth_buf: FixedRingBuffer::new(),
cycle_prev: 0.0,
cycle_prev2: 0.0,
}
}
pub fn seed_warmup(real: &[f64]) -> Self {
let mut state = Self::new();
for i in 0..6 {
state.price_buf.push(real[i]);
if state.price_buf.len() >= 4 {
let ab = 2.0_f64.mul_add(state.price_buf[1], state.price_buf[0]);
let cd = 2.0_f64.mul_add(state.price_buf[2], state.price_buf[3]);
state.smooth_buf.push((ab + cd) * (1.0 / 6.0));
}
if state.price_buf.len() >= 3 {
let seed =
(state.price_buf[0] - 2.0 * state.price_buf[1] + state.price_buf[2]) / 4.0;
state.cycle_prev2 = state.cycle_prev;
state.cycle_prev = seed;
}
}
state
}
pub fn init_state(
real: &[f64],
multipliers: (f64, f64, f64),
cycle_line: &mut [f64],
trigger_line: &mut [f64],
) -> Self {
let mut state = Self::new();
for i in 0..6 {
state.price_buf.push(real[i]);
if state.price_buf.len() >= 4 {
let ab = 2.0_f64.mul_add(state.price_buf[1], state.price_buf[0]);
let cd = 2.0_f64.mul_add(state.price_buf[2], state.price_buf[3]);
state.smooth_buf.push((ab + cd) * (1.0 / 6.0));
}
if state.price_buf.len() >= 3 {
let seed =
(state.price_buf[0] - 2.0 * state.price_buf[1] + state.price_buf[2]) / 4.0;
state.cycle_prev2 = state.cycle_prev;
state.cycle_prev = seed;
}
}
let cycle = unsafe { state.calc_unchecked(real[6], multipliers) };
cycle_line[0] = cycle;
if !trigger_line.is_empty() {
trigger_line[0] = state.cycle_prev2;
}
state
}
#[inline(always)]
pub fn calc(&mut self, price: f64, multipliers: (f64, f64, f64)) -> f64 {
self.price_buf.push(price);
if self.price_buf.len() < 4 {
return 0.0;
}
let ab = 2.0_f64.mul_add(self.price_buf[1], self.price_buf[0]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * (1.0 / 6.0);
self.smooth_buf.push(smooth);
if self.smooth_buf.len() < 3 {
return 0.0;
}
let (coeff, d1, d2) = multipliers;
let smooth_diff = (-2.0_f64).mul_add(self.smooth_buf[1], smooth) + self.smooth_buf[2];
let cycle = coeff.mul_add(
smooth_diff,
d1.mul_add(self.cycle_prev, -d2 * self.cycle_prev2),
);
self.cycle_prev2 = self.cycle_prev;
self.cycle_prev = cycle;
cycle
}
#[inline(always)]
pub unsafe fn calc_unchecked(&mut self, price: f64, multipliers: (f64, f64, f64)) -> f64 {
self.price_buf.push_unchecked(price);
let ab = 2.0_f64.mul_add(self.price_buf[1], self.price_buf[0]);
let cd = 2.0_f64.mul_add(self.price_buf[2], self.price_buf[3]);
let smooth = (ab + cd) * (1.0 / 6.0);
self.smooth_buf.push_unchecked(smooth);
let (coeff, d1, d2) = multipliers;
let smooth_diff = (-2.0_f64).mul_add(self.smooth_buf[1], smooth) + self.smooth_buf[2];
let cycle = coeff.mul_add(
smooth_diff,
d1.mul_add(self.cycle_prev, -d2 * self.cycle_prev2),
);
self.cycle_prev2 = self.cycle_prev;
self.cycle_prev = cycle;
cycle
}
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
pub fn min_data(_options: &[f64]) -> usize {
7
}
pub fn output_length(data_len: usize, options: &[f64]) -> usize {
data_len - min_data(options) + 1
}
pub(crate) fn validate_options(options: &[f64; OPTIONS_WIDTH]) -> Result<(), IndicatorError> {
if options[0] <= 0.0 || options[0] >= 1.0 {
return Err(IndicatorError::InvalidOptions);
}
Ok(())
}
pub fn multiplier(alpha: f64) -> (f64, f64, f64) {
let c = 1.0 - 0.5 * alpha;
let b = 1.0 - alpha;
(c * c, 2.0 * b, b * b)
}
#[inline(always)]
pub fn adaptive_alpha(smooth_period: f64) -> f64 {
2.0 / (smooth_period.max(3.0) + 1.0)
}
pub fn indicator(
inputs: &[&[f64]; INPUTS_WIDTH],
options: &[f64; OPTIONS_WIDTH],
optional_outputs: Option<&[bool]>,
) -> Result<(Vec<Vec<f64>>, IndicatorState), IndicatorError> {
validate_options(options)?;
validate_inputs(inputs, min_data(options))?;
let alpha = options[0];
let mults = multiplier(alpha);
let real = inputs[0];
let n = real.len();
let capacity = output_length(n, options);
let mut cycle_line = crate::uninit_vec!(f64, capacity);
let mut trigger_line = crate::init_optional_outputs_eff!(
optional_outputs, &[false],
trigger_line: capacity
);
let mut state = State::init_state(real, mults, &mut cycle_line, &mut trigger_line);
let trigger_start = crate::slice_outputs_start!(capacity - 1, trigger_line);
run_cycle(
&real[min_data(options)..],
&mut state,
mults,
&mut cycle_line[1..],
&mut trigger_line[trigger_start..],
);
Ok((
vec![cycle_line, trigger_line],
IndicatorState::new(state, mults),
))
}
fn run_cycle(
real: &[f64],
state: &mut State,
multipliers: (f64, f64, f64),
cycle_line: &mut [f64],
trigger_line: &mut [f64],
) {
let want_trigger = !trigger_line.is_empty();
for i in 0..real.len() {
unsafe {
*cycle_line.get_unchecked_mut(i) =
state.calc_unchecked(*real.get_unchecked(i), multipliers);
}
crate::store_optional_outputs!(i,
want_trigger, trigger_line => state.cycle_prev2
);
}
}