use rust_decimal::{prelude::FromPrimitive, Decimal};
use tesser_core::Candle;
use thiserror::Error;
use crate::combinators::PipedIndicator;
pub trait Input {
fn value(&self) -> Decimal;
}
impl Input for Decimal {
fn value(&self) -> Decimal {
*self
}
}
impl Input for f64 {
fn value(&self) -> Decimal {
decimal_from_f64(*self)
}
}
impl Input for Candle {
fn value(&self) -> Decimal {
self.close
}
}
impl<T> Input for &T
where
T: Input + ?Sized,
{
fn value(&self) -> Decimal {
T::value(self)
}
}
#[derive(Debug, Error, PartialEq)]
pub enum IndicatorError {
#[error("{name} requires period > 0 (got {period})")]
InvalidPeriod {
name: &'static str,
period: usize,
},
#[error("{name} parameter '{parameter}' must be non-negative (got {value})")]
InvalidParameter {
name: &'static str,
parameter: &'static str,
value: Decimal,
},
}
impl IndicatorError {
pub fn invalid_period(name: &'static str, period: usize) -> Self {
Self::InvalidPeriod { name, period }
}
pub fn invalid_parameter(name: &'static str, parameter: &'static str, value: Decimal) -> Self {
Self::InvalidParameter {
name,
parameter,
value,
}
}
}
pub trait Indicator {
type Input: Input;
type Output;
fn next(&mut self, input: Self::Input) -> Option<Self::Output>;
fn reset(&mut self);
fn pipe<Next>(self, next: Next) -> PipedIndicator<Self, Next>
where
Self: Sized,
Next: Indicator<Input = Self::Output>,
{
PipedIndicator::new(self, next)
}
}
fn decimal_from_f64(value: f64) -> Decimal {
Decimal::from_f64(value)
.or_else(|| Decimal::from_f64_retain(value))
.expect("failed to convert f64 into Decimal")
}
pub(crate) fn decimal_from_usize(value: usize) -> Decimal {
Decimal::from_usize(value).expect("usize should convert into Decimal")
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use chrono::Utc;
use rust_decimal::Decimal;
use tesser_core::{Candle, Interval, Symbol};
use super::{decimal_from_f64, Input};
#[test]
fn converts_f64_to_decimal() {
let value = 42.1234_f64;
let decimal = decimal_from_f64(value);
assert_eq!(decimal.round_dp(4), Decimal::from_str("42.1234").unwrap());
}
#[test]
fn candle_input_uses_close_price() {
let candle = Candle {
symbol: Symbol::from("BTCUSDT"),
interval: Interval::OneMinute,
open: Decimal::from(10),
high: Decimal::from(15),
low: decimal_from_f64(9.5),
close: decimal_from_f64(12.5),
volume: Decimal::from(100),
timestamp: Utc::now(),
};
let decimal = candle.value();
assert_eq!(decimal.round_dp(1), Decimal::from_str("12.5").unwrap());
}
#[test]
fn reference_input_delegates_to_inner_type() {
let price = Decimal::from(100);
assert_eq!((price).value(), Decimal::from(100));
}
}