use pine_builtin_macro::BuiltinFunction;
use pine_core::{PineOutput, Timeframe};
use pine_interpreter::{Interpreter, RuntimeError, Value};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(BuiltinFunction)]
#[builtin(name = "timeframe.in_seconds")]
struct TimeframeInSeconds {
#[arg(default = "")]
timeframe: String,
}
impl TimeframeInSeconds {
fn execute<O: PineOutput>(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let millis = if self.timeframe.is_empty() {
ctx.chart_period
} else {
self.timeframe
.parse::<Timeframe>()
.ok()
.and_then(|tf| tf.to_millis())
};
Ok(millis.map_or(Value::Na, |ms| Value::Int(ms / 1000)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "timeframe.from_seconds")]
struct TimeframeFromSeconds {
seconds: f64,
}
impl TimeframeFromSeconds {
fn execute<O: PineOutput>(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let millis = (self.seconds * 1000.0) as i64;
Ok(Timeframe::from_millis(millis).map_or(Value::Na, |tf| Value::String(tf.period())))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "timeframe.change", stateful)]
struct TimeframeChange {
timeframe: String,
#[state]
prev_bucket: Option<i64>,
}
impl TimeframeChange {
fn execute<O: PineOutput>(
&mut self,
ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
let Some(time) = ctx.current_time else {
return Ok(Value::Bool(false));
};
let bucket = match self
.timeframe
.parse::<Timeframe>()
.ok()
.and_then(|tf| tf.to_millis())
{
Some(ms) if ms > 0 => time.div_euclid(ms),
_ => time,
};
let changed = self.prev_bucket != Some(bucket);
self.prev_bucket = Some(bucket);
Ok(Value::Bool(changed))
}
}
pub fn register<O: PineOutput>(tf: Timeframe) -> Value<O> {
let mut members: HashMap<String, Value<O>> = HashMap::new();
members.insert("period".to_string(), Value::String(tf.period()));
members.insert("main_period".to_string(), Value::String(tf.period()));
members.insert(
"in_seconds".to_string(),
TimeframeInSeconds::builtin_value::<O>(),
);
members.insert(
"from_seconds".to_string(),
TimeframeFromSeconds::builtin_value::<O>(),
);
members.insert("change".to_string(), TimeframeChange::builtin_value::<O>());
members.insert(
"multiplier".to_string(),
Value::Number(tf.multiplier as f64),
);
members.insert("isseconds".to_string(), Value::Bool(tf.is_seconds()));
members.insert("isminutes".to_string(), Value::Bool(tf.is_minutes()));
members.insert("isintraday".to_string(), Value::Bool(tf.is_intraday()));
members.insert("isdaily".to_string(), Value::Bool(tf.is_daily()));
members.insert("isweekly".to_string(), Value::Bool(tf.is_weekly()));
members.insert("ismonthly".to_string(), Value::Bool(tf.is_monthly()));
members.insert("isdwm".to_string(), Value::Bool(tf.is_dwm()));
members.insert("isticks".to_string(), Value::Bool(tf.is_ticks()));
Value::Object {
type_name: "timeframe".to_string(),
fields: Rc::new(RefCell::new(members)),
call: None,
value: None,
}
}