use pine_builtin_macro::BuiltinFunction;
use pine_core::{PineOutput, SeriesBuffer};
use pine_interpreter::{Interpreter, RuntimeError, Value};
pub(crate) fn smooth_step(previous: Option<f64>, source: f64, alpha: f64, seed: &[f64]) -> f64 {
match previous {
Some(previous) => alpha * source + (1.0 - alpha) * previous,
None => seed.iter().sum::<f64>() / seed.len() as f64,
}
}
pub(crate) fn ema_step(
window: &mut SeriesBuffer<f64>,
previous: &mut Option<f64>,
source: f64,
length: usize,
) -> Option<f64> {
if source.is_nan() {
return *previous;
}
let seed = window.observe(source, length)?;
let alpha = 2.0 / (length as f64 + 1.0);
let ema = smooth_step(*previous, source, alpha, &seed);
*previous = Some(ema);
Some(ema)
}
pub(crate) fn wilder_step(
window: &mut SeriesBuffer<f64>,
previous: &mut Option<f64>,
source: f64,
length: usize,
) -> Option<f64> {
if source.is_nan() {
return *previous;
}
let seed = window.observe(source, length)?;
let alpha = 1.0 / length as f64;
let value = smooth_step(*previous, source, alpha, &seed);
*previous = Some(value);
Some(value)
}
pub(crate) fn weighted_average(values: &[f64]) -> f64 {
let len = values.len();
let weighted: f64 = values
.iter()
.enumerate()
.map(|(i, &value)| value * (len - i) as f64)
.sum();
let total_weight = (len * (len + 1)) as f64 / 2.0;
weighted / total_weight
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.sma", stateful)]
pub struct TaSma {
source: f64,
#[length_check]
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaSma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(values.iter().sum::<f64>() / length as f64))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.ema", stateful)]
pub struct TaEma {
source: f64,
#[length_check]
length: f64,
#[state]
window: SeriesBuffer<f64>,
#[state]
previous: Option<f64>,
}
impl TaEma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let Some(seed) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
let alpha = 2.0 / (length as f64 + 1.0);
let ema = smooth_step(self.previous, self.source, alpha, &seed);
self.previous = Some(ema);
Ok(Value::Number(ema))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.rma", stateful)]
pub struct TaRma {
source: f64,
#[length_check]
length: f64,
#[state]
window: SeriesBuffer<f64>,
#[state]
previous: Option<f64>,
}
impl TaRma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
if self.source.is_nan() {
return Ok(Value::Na);
}
let Some(seed) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
let rma = smooth_step(self.previous, self.source, 1.0 / length as f64, &seed);
self.previous = Some(rma);
Ok(Value::Number(rma))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.wma", stateful)]
pub struct TaWma {
source: f64,
#[length_check]
length: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaWma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
Ok(Value::Number(weighted_average(&values)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.vwma", stateful)]
pub struct TaVwma {
source: f64,
#[length_check]
length: f64,
#[state]
prices: SeriesBuffer<f64>,
#[state]
volumes: SeriesBuffer<f64>,
}
impl TaVwma {
fn execute<O: PineOutput>(
&mut self,
ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let volume = ctx
.get_variable("volume")
.ok_or_else(|| RuntimeError::UndefinedVariable("volume".to_string()))?
.as_number()?;
let prices = self.prices.observe(self.source, length);
let volumes = self.volumes.observe(volume, length);
let (Some(prices), Some(volumes)) = (prices, volumes) else {
return Ok(Value::Na);
};
let volume_sum: f64 = volumes.iter().sum();
if volume_sum == 0.0 {
return Ok(Value::Na);
}
let weighted: f64 = prices
.iter()
.zip(&volumes)
.map(|(price, volume)| price * volume)
.sum();
Ok(Value::Number(weighted / volume_sum))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.hma", stateful)]
pub struct TaHma {
source: f64,
#[length_check]
length: f64,
#[state]
window: SeriesBuffer<f64>,
#[state]
raw: SeriesBuffer<f64>,
}
impl TaHma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let half = (length / 2).max(1);
let root = ((length as f64).sqrt().floor() as usize).max(1);
let Some(values) = self.window.observe(self.source, length) else {
return Ok(Value::Na);
};
let raw = 2.0 * weighted_average(&values[..half]) - weighted_average(&values);
let Some(smoothed) = self.raw.observe(raw, root) else {
return Ok(Value::Na);
};
Ok(Value::Number(weighted_average(&smoothed)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.swma", stateful)]
pub struct TaSwma {
source: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaSwma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let Some(values) = self.window.observe(self.source, 4) else {
return Ok(Value::Na);
};
let swma = (values[0] + 2.0 * values[1] + 2.0 * values[2] + values[3]) / 6.0;
Ok(Value::Number(swma))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "ta.alma", stateful)]
pub struct TaAlma {
series: f64,
#[length_check]
length: f64,
offset: f64,
sigma: f64,
#[state]
window: SeriesBuffer<f64>,
}
impl TaAlma {
fn execute<O: PineOutput>(
&mut self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let length = self.length as usize;
let Some(values) = self.window.observe(self.series, length) else {
return Ok(Value::Na);
};
let m = self.offset * (length as f64 - 1.0);
let s = length as f64 / self.sigma;
let weights: Vec<f64> = (0..length)
.map(|w| (-((w as f64 - m).powi(2)) / (2.0 * s * s)).exp())
.collect();
let norm: f64 = weights.iter().sum();
let sum: f64 = weights
.iter()
.enumerate()
.map(|(w, weight)| weight * values[length - 1 - w])
.sum();
Ok(Value::Number(sum / norm))
}
}