use pine_builtin_macro::BuiltinFunction;
use pine_core::{PineOutput, SeriesBuffer};
use pine_interpreter::{Interpreter, RuntimeError, Value};
#[derive(BuiltinFunction)]
#[builtin(name = "ta.stdev", stateful)]
pub struct TaStdev {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaStdev {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Err(RuntimeError::TypeError(
"length must be greater than 0".to_string(),
));
}
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
if values.len() == 1 {
return Ok(Value::Number(0.0));
}
let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
let variance: f64 = values
.iter()
.map(|&val| {
let diff = val - mean;
diff * diff
})
.sum::<f64>()
/ values.len() as f64;
Ok(Value::Number(variance.sqrt()))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.variance", stateful)]
pub struct TaVariance {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaVariance {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Err(RuntimeError::TypeError(
"length must be greater than 0".to_string(),
));
}
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
if values.len() == 1 {
return Ok(Value::Number(0.0));
}
let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
let variance: f64 = values
.iter()
.map(|&val| {
let diff = val - mean;
diff * diff
})
.sum::<f64>()
/ values.len() as f64;
Ok(Value::Number(variance))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.median", stateful)]
pub struct TaMedian {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaMedian {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Err(RuntimeError::TypeError(
"length must be greater than 0".to_string(),
));
}
let Some(mut values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = values.len() / 2;
let median = if values.len() % 2 == 1 {
values[mid]
} else {
(values[mid - 1] + values[mid]) / 2.0
};
Ok(Value::Number(median))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.percentile_nearest_rank", stateful)]
pub struct TaPercentileNearestRank {
source: f64,
length: f64,
percentage: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaPercentileNearestRank {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Err(RuntimeError::TypeError(
"length must be greater than 0".to_string(),
));
}
let Some(mut values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let rank = (self.percentage / 100.0 * values.len() as f64).ceil() as usize;
let index = rank.clamp(1, values.len()) - 1;
Ok(Value::Number(values[index]))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.cum", stateful)]
pub struct TaCum {
source: f64,
#[state]
total: f64,
}
impl TaCum {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
if self.source.is_finite() {
self.total += self.source;
}
Ok(Value::Number(self.total))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.dev", stateful)]
pub struct TaDev {
source: f64,
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaDev {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if length == 0 {
return Err(RuntimeError::TypeError(
"length must be greater than 0".to_string(),
));
}
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
let mad: f64 =
values.iter().map(|&val| (val - mean).abs()).sum::<f64>() / values.len() as f64;
Ok(Value::Number(mad))
}
}