use super::moving_averages::checked_length;
use pine_builtin_macro::BuiltinFunction;
use pine_interpreter::{Interpreter, PineOutput, RuntimeError, SeriesBuffer, Value};
#[derive(BuiltinFunction)]
#[builtin(name = "ta.change", stateful)]
pub struct TaChange {
source: f64,
#[arg(default = 1.0)]
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaChange {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Ok(Value::Number(0.0));
}
let Some(values) = self.window.observe(self.source, length + 1) else {
return Ok(Value::Na);
};
Ok(Value::Number(values[0] - values[length]))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.highest", stateful)]
pub struct TaHighest {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaHighest {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = checked_length(self.length)?;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(
values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.lowest", stateful)]
pub struct TaLowest {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaLowest {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = checked_length(self.length)?;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(
values.iter().copied().fold(f64::INFINITY, f64::min),
))
}
}
fn extreme_offset(values: &[f64], better: fn(f64, f64) -> bool) -> f64 {
let mut best = 0;
for (i, &value) in values.iter().enumerate() {
if better(value, values[best]) {
best = i;
}
}
if best == 0 {
0.0
} else {
-(best as f64)
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.highestbars", stateful)]
pub struct TaHighestbars {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaHighestbars {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = checked_length(self.length)?;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(extreme_offset(&values, |a, b| a > b)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.lowestbars", stateful)]
pub struct TaLowestbars {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaLowestbars {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = checked_length(self.length)?;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(extreme_offset(&values, |a, b| a < b)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.rising", stateful)]
pub struct TaRising {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaRising {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Ok(Value::Bool(false));
}
let Some(values) = self.window.observe(self.source, length + 1) else {
return Ok(Value::Bool(false));
};
Ok(Value::Bool(values.windows(2).all(|pair| pair[0] > pair[1])))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.falling", stateful)]
pub struct TaFalling {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaFalling {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Ok(Value::Bool(false));
}
let Some(values) = self.window.observe(self.source, length + 1) else {
return Ok(Value::Bool(false));
};
Ok(Value::Bool(values.windows(2).all(|pair| pair[0] < pair[1])))
}
}
struct Crossing {
now_above: bool,
was_above: bool,
}
impl Crossing {
fn observe(
first: &mut SeriesBuffer<f64>,
second: &mut SeriesBuffer<f64>,
source1: f64,
source2: f64,
) -> Option<Self> {
let firsts = first.observe(source1, 2);
let seconds = second.observe(source2, 2);
let (firsts, seconds) = (firsts?, seconds?);
Some(Self {
now_above: firsts[0] > seconds[0],
was_above: firsts[1] > seconds[1],
})
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.cross", stateful)]
pub struct TaCross {
source1: f64,
source2: f64,
#[state]
first: SeriesBuffer<f64>,
#[state]
second: SeriesBuffer<f64>,
}
impl TaCross {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let Some(crossing) = Crossing::observe(
&mut self.first,
&mut self.second,
self.source1,
self.source2,
) else {
return Ok(Value::Bool(false));
};
Ok(Value::Bool(crossing.now_above != crossing.was_above))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.crossover", stateful)]
pub struct TaCrossover {
source1: f64,
source2: f64,
#[state]
first: SeriesBuffer<f64>,
#[state]
second: SeriesBuffer<f64>,
}
impl TaCrossover {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let Some(crossing) = Crossing::observe(
&mut self.first,
&mut self.second,
self.source1,
self.source2,
) else {
return Ok(Value::Bool(false));
};
Ok(Value::Bool(crossing.now_above && !crossing.was_above))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.crossunder", stateful)]
pub struct TaCrossunder {
source1: f64,
source2: f64,
#[state]
first: SeriesBuffer<f64>,
#[state]
second: SeriesBuffer<f64>,
}
impl TaCrossunder {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let Some(crossing) = Crossing::observe(
&mut self.first,
&mut self.second,
self.source1,
self.source2,
) else {
return Ok(Value::Bool(false));
};
Ok(Value::Bool(!crossing.now_above && crossing.was_above))
}
}