use chrono::{DateTime, Utc};
use quant_primitives::Candle;
use rust_decimal::Decimal;
use crate::error::IndicatorError;
use crate::indicator::Indicator;
use crate::series::Series;
struct SeriesAlignment {
output_len: usize,
offset_a: usize,
offset_b: usize,
}
fn align_series_from_end(len_a: usize, len_b: usize) -> SeriesAlignment {
let output_len = len_a.min(len_b);
SeriesAlignment {
output_len,
offset_a: len_a - output_len,
offset_b: len_b - output_len,
}
}
fn apply_binary_op<F>(
values_a: &[(DateTime<Utc>, Decimal)],
values_b: &[(DateTime<Utc>, Decimal)],
op: F,
) -> Vec<(DateTime<Utc>, Decimal)>
where
F: Fn(Decimal, Decimal) -> Decimal,
{
let align = align_series_from_end(values_a.len(), values_b.len());
let mut result = Vec::with_capacity(align.output_len);
for i in 0..align.output_len {
let (ts, val_a) = values_a[align.offset_a + i];
let val_b = values_b[align.offset_b + i].1;
result.push((ts, op(val_a, val_b)));
}
result
}
#[derive(Debug, Clone)]
pub struct Diff<A, B> {
a: A,
b: B,
name: String,
}
impl<A: Indicator, B: Indicator> Diff<A, B> {
pub fn new(a: A, b: B) -> Self {
let name = format!("Diff({},{})", a.name(), b.name());
Self { a, b, name }
}
}
impl<A: Indicator, B: Indicator> Indicator for Diff<A, B> {
fn name(&self) -> &str {
&self.name
}
fn warmup_period(&self) -> usize {
self.a.warmup_period().max(self.b.warmup_period())
}
fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
let series_a = self.a.compute(candles)?;
let series_b = self.b.compute(candles)?;
let values = apply_binary_op(series_a.values(), series_b.values(), |a, b| a - b);
Ok(Series::new(values))
}
}
#[derive(Debug, Clone)]
pub struct Ratio<A, B> {
a: A,
b: B,
name: String,
}
impl<A: Indicator, B: Indicator> Ratio<A, B> {
pub fn new(a: A, b: B) -> Self {
let name = format!("Ratio({},{})", a.name(), b.name());
Self { a, b, name }
}
}
impl<A: Indicator, B: Indicator> Indicator for Ratio<A, B> {
fn name(&self) -> &str {
&self.name
}
fn warmup_period(&self) -> usize {
self.a.warmup_period().max(self.b.warmup_period())
}
fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
let series_a = self.a.compute(candles)?;
let series_b = self.b.compute(candles)?;
let values = apply_binary_op(series_a.values(), series_b.values(), |a, b| {
if b.is_zero() {
Decimal::ZERO
} else {
a / b
}
});
Ok(Series::new(values))
}
}
#[derive(Debug, Clone)]
pub struct Lag<I> {
inner: I,
periods: usize,
name: String,
}
impl<I: Indicator> Lag<I> {
pub fn new(inner: I, periods: usize) -> Result<Self, IndicatorError> {
if periods == 0 {
return Err(IndicatorError::InvalidParameter {
message: "Lag periods must be > 0".to_string(),
});
}
let name = format!("Lag({},{})", inner.name(), periods);
Ok(Self {
inner,
periods,
name,
})
}
}
impl<I: Indicator> Indicator for Lag<I> {
fn name(&self) -> &str {
&self.name
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period() + self.periods
}
fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
let inner_series = self.inner.compute(candles)?;
let values = inner_series.values();
if values.len() <= self.periods {
return Err(IndicatorError::InsufficientData {
required: self.inner.warmup_period() + self.periods,
actual: candles.len(),
});
}
let output_len = values.len() - self.periods;
let mut lagged_values = Vec::with_capacity(output_len);
for i in 0..output_len {
let value = values[i].1;
let ts = values[i + self.periods].0;
lagged_values.push((ts, value));
}
Ok(Series::new(lagged_values))
}
}
#[derive(Debug, Clone)]
pub struct Scale<I> {
inner: I,
factor: Decimal,
name: String,
}
impl<I: Indicator> Scale<I> {
pub fn new(inner: I, factor: Decimal) -> Self {
let name = format!("Scale({},{})", inner.name(), factor);
Self {
inner,
factor,
name,
}
}
}
impl<I: Indicator> Indicator for Scale<I> {
fn name(&self) -> &str {
&self.name
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
let inner_series = self.inner.compute(candles)?;
let values: Vec<_> = inner_series
.values()
.iter()
.map(|(ts, v)| (*ts, *v * self.factor))
.collect();
Ok(Series::new(values))
}
}
#[cfg(test)]
#[path = "combinators_tests.rs"]
mod tests;