use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use pine_ast::{Expr, Program, Stmt, VarKind};
use pine_builtin_macro::BuiltinFunction;
use pine_core::{Data, PineOutput, Timeframe};
use pine_interpreter::{Interpreter, RuntimeError, Series, Value};
type SecondarySeries<O> = Rc<Vec<(i64, Value<O>)>>;
#[derive(BuiltinFunction)]
#[builtin(name = "request.financial")]
struct RequestFinancial<O: PineOutput> {
symbol: String,
financial_id: String,
period: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestFinancial<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.financial(&self.symbol, &self.financial_id, &self.period))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.dividends")]
struct RequestDividends<O: PineOutput> {
ticker: String,
#[arg(default = "gross")]
field: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestDividends<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.dividends(&self.ticker, &self.field))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.earnings")]
struct RequestEarnings<O: PineOutput> {
ticker: String,
#[arg(default = "actual")]
field: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestEarnings<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.earnings(&self.ticker, &self.field))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.splits")]
struct RequestSplits<O: PineOutput> {
ticker: String,
#[arg(default = "numerator")]
field: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestSplits<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.splits(&self.ticker, &self.field))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.economic")]
struct RequestEconomic<O: PineOutput> {
country_code: String,
field: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestEconomic<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.economic(&self.country_code, &self.field))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.currency_rate")]
struct RequestCurrencyRate<O: PineOutput> {
from: String,
to: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestCurrencyRate<O> {
fn execute(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = &self.options;
if self.from == self.to {
return Ok(Value::Number(1.0));
}
Ok(ctx
.request_provider
.as_ref()
.and_then(|p| p.currency_rate(&self.from, &self.to))
.map_or(Value::Na, Value::Number))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.quandl")]
struct RequestQuandl<O: PineOutput> {
ticker: String,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestQuandl<O> {
fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = (&self.ticker, &self.options);
Ok(Value::Na)
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.seed")]
struct RequestSeed<O: PineOutput> {
source: String,
symbol: String,
#[arg(lazy)]
expression: Value<O>,
#[arg(variadic)]
options: Vec<Value<O>>,
}
impl<O: PineOutput> RequestSeed<O> {
fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = (&self.source, &self.symbol, &self.expression, &self.options);
Ok(Value::Na)
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.footprint")]
struct RequestFootprint {
#[arg(default = 0.0)]
ticks_per_row: f64,
#[arg(default = 70.0)]
va_percent: f64,
#[arg(default = 300.0)]
imbalance_percent: f64,
}
impl RequestFootprint {
fn execute<O: PineOutput>(&self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
Ok(ctx
.request_provider
.as_ref()
.and_then(|provider| {
provider.footprint(self.ticks_per_row, self.va_percent, self.imbalance_percent)
})
.map(|rows| {
crate::footprint::build_footprint(rows, self.va_percent, self.imbalance_percent)
})
.unwrap_or(Value::Na))
}
}
pub fn register<O: PineOutput>() -> Value<O> {
let mut fields: HashMap<String, Value<O>> = HashMap::new();
fields.insert(
"security".to_string(),
RequestSecurity::<O>::builtin_value(),
);
fields.insert(
"security_lower_tf".to_string(),
RequestSecurityLowerTf::<O>::builtin_value(),
);
fields.insert(
"financial".to_string(),
RequestFinancial::<O>::builtin_value(),
);
fields.insert(
"dividends".to_string(),
RequestDividends::<O>::builtin_value(),
);
fields.insert(
"earnings".to_string(),
RequestEarnings::<O>::builtin_value(),
);
fields.insert("splits".to_string(), RequestSplits::<O>::builtin_value());
fields.insert(
"economic".to_string(),
RequestEconomic::<O>::builtin_value(),
);
fields.insert(
"currency_rate".to_string(),
RequestCurrencyRate::<O>::builtin_value(),
);
fields.insert("quandl".to_string(), RequestQuandl::<O>::builtin_value());
fields.insert("seed".to_string(), RequestSeed::<O>::builtin_value());
fields.insert(
"footprint".to_string(),
RequestFootprint::builtin_value::<O>(),
);
Value::Object {
type_name: "request".to_string(),
fields: Rc::new(RefCell::new(fields)),
call: None,
value: None,
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.security", stateful)]
struct RequestSecurity<O: PineOutput> {
symbol: String,
timeframe: String,
#[arg(lazy)]
expression: Value<O>,
#[arg(default = None)]
gaps: Option<Value<O>>,
#[arg(default = None)]
lookahead: Option<Value<O>>,
#[arg(default = None)]
ignore_invalid_symbol: Option<bool>,
#[arg(default = None)]
currency: Option<String>,
#[arg(default = None)]
calc_bars_count: Option<f64>,
#[state]
series: Option<SecondarySeries<O>>,
}
impl<O: PineOutput> RequestSecurity<O> {
fn execute(&mut self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = (
&self.gaps,
&self.lookahead,
&self.ignore_invalid_symbol,
&self.currency,
&self.calc_bars_count,
);
let (Value::Expr(expr), Ok(timeframe)) =
(&self.expression, self.timeframe.parse::<Timeframe>())
else {
return Ok(Value::Na);
};
let expr = Rc::clone(expr);
if self.series.is_none() {
self.series = Some(Rc::new(request_series(ctx, &self.symbol, timeframe, &expr)));
}
let series = self.series.as_ref().expect("series built above");
Ok(aligned(series, current_time(ctx)))
}
}
#[derive(BuiltinFunction)]
#[builtin(name = "request.security_lower_tf", stateful)]
struct RequestSecurityLowerTf<O: PineOutput> {
symbol: String,
timeframe: String,
#[arg(lazy)]
expression: Value<O>,
#[arg(default = None)]
ignore_invalid_symbol: Option<bool>,
#[arg(default = None)]
currency: Option<String>,
#[arg(default = None)]
calc_bars_count: Option<f64>,
#[state]
series: Option<SecondarySeries<O>>,
}
impl<O: PineOutput> RequestSecurityLowerTf<O> {
fn execute(&mut self, ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
let _ = (
&self.ignore_invalid_symbol,
&self.currency,
&self.calc_bars_count,
);
let (Value::Expr(expr), Ok(tf)) = (&self.expression, self.timeframe.parse::<Timeframe>())
else {
return Ok(Value::Na);
};
let expr = Rc::clone(expr);
if let (Some(tf_ms), Some(chart)) = (tf.to_millis(), ctx.chart_period) {
if tf_ms > chart {
return Err(RuntimeError::TypeError(format!(
"request.security_lower_tf: timeframe \"{}\" is not lower than the chart timeframe",
self.timeframe
)));
}
}
if self.series.is_none() {
self.series = Some(Rc::new(request_series(ctx, &self.symbol, tf, &expr)));
}
let series = self.series.as_ref().expect("series built above");
let now = current_time(ctx);
let end = ctx.chart_period.map_or(i64::MAX, |period| now + period);
let values: Vec<Value<O>> = series
.iter()
.filter(|(time, _)| *time >= now && *time < end)
.map(|(_, value)| value.clone())
.collect();
Ok(Value::Array(Rc::new(RefCell::new(values))))
}
}
fn request_series<O: PineOutput>(
ctx: &Interpreter<O>,
symbol: &str,
timeframe: Timeframe,
expr: &Expr,
) -> Vec<(i64, Value<O>)> {
let data = ctx
.request_provider
.clone()
.and_then(|provider| provider.request(symbol, timeframe).ok());
data.map_or_else(Vec::new, |data| {
secondary_series(&ctx.snapshot(), expr, data)
})
}
fn secondary_series<O: PineOutput>(
base_vars: &HashMap<String, Value<O>>,
expr: &Expr,
data: Data,
) -> Vec<(i64, Value<O>)> {
let mut interp = Interpreter::<O>::new();
for (name, value) in base_vars {
interp.set_variable(name, value.clone());
}
let program = Program::new(vec![Stmt::VarDecl {
name: "__req".to_string(),
type_qualifier: None,
type_annotation: None,
initializer: Some(expr.clone()),
var_kind: VarKind::Plain,
loc: Default::default(),
}]);
let mut series = Vec::with_capacity(data.bars.len());
for bar in &data.bars {
bind_bar(&mut interp, bar);
if interp.execute(&program).is_err() {
break;
}
let value = match interp.get_variable("__req") {
Some(Value::Series(series)) => (*series.current).clone(),
Some(value) => value.clone(),
None => Value::Na,
};
series.push((bar.time, value));
}
series
}
fn bind_bar<O: PineOutput>(interp: &mut Interpreter<O>, bar: &pine_core::Bar) {
for (id, value) in [
("open", bar.open),
("high", bar.high),
("low", bar.low),
("close", bar.close),
("volume", bar.volume),
("hl2", (bar.high + bar.low) / 2.0),
("hlc3", (bar.high + bar.low + bar.close) / 3.0),
("hlcc4", (bar.high + bar.low + bar.close * 2.0) / 4.0),
("ohlc4", (bar.open + bar.high + bar.low + bar.close) / 4.0),
] {
interp.advance_series(
id,
Value::Series(Series {
id: id.to_string(),
current: Box::new(Value::Number(value)),
history: None,
}),
);
}
interp.set_variable("bar_index", Value::Number(bar.index as f64));
for (name, value) in crate::register_per_bar(bar) {
interp.set_variable(&name, value);
}
}
fn aligned<O: PineOutput>(series: &[(i64, Value<O>)], now: i64) -> Value<O> {
let confirmed = series.partition_point(|(time, _)| *time <= now);
confirmed
.checked_sub(1)
.and_then(|i| series.get(i))
.map_or(Value::Na, |(_, value)| value.clone())
}
fn current_time<O: PineOutput>(ctx: &Interpreter<O>) -> i64 {
ctx.current_time.unwrap_or(0)
}