Skip to main content

pine_builtins/
lib.rs

1use pine_builtin_macro::BuiltinFunction;
2use pine_core::{
3    AlertConditionOutput, BoxOutput, DrawingOutput, FillOutput, GlobalOutput, InputOutput,
4    LabelOutput, LineOutput, LogOutput, MetadataOutput, PineOutput, PlotOutput, TableOutput,
5};
6use pine_core::{PineVersion, SymInfo, Timeframe};
7use pine_interpreter::{Builtin, Interpreter, RuntimeError, Value};
8use std::collections::HashMap;
9use std::rc::Rc;
10
11// Re-export for convenience
12pub use pine_core::Bar;
13pub use pine_core::DefaultPineOutput;
14pub use pine_core::LogLevel;
15pub use pine_interpreter::BuiltinFn;
16pub use pine_interpreter::EvaluatedArg;
17
18// Namespace modules
19mod alertcondition;
20mod array;
21mod barstate;
22mod r#box;
23mod chart;
24mod color;
25mod constants;
26mod currency;
27mod dividends;
28mod earnings;
29mod fill;
30mod footprint;
31mod globals;
32mod indicator;
33mod input;
34mod label;
35mod library;
36mod line;
37mod linefill;
38mod log;
39mod map;
40mod math;
41mod matrix;
42mod plot;
43mod polyline;
44mod request;
45mod runtime;
46mod session;
47mod str;
48mod strategy;
49mod syminfo;
50mod ta;
51mod table;
52mod ticker;
53mod time;
54mod timeframe;
55
56// Global utility functions - defined first so they can be referenced in register function
57
58/// na(value) - Returns true if the value is na, false otherwise
59#[derive(BuiltinFunction)]
60#[builtin(name = "na")]
61struct Na<O: PineOutput> {
62    value: Value<O>,
63}
64
65impl<O: PineOutput> Na<O> {
66    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
67        Ok(Value::Bool(matches!(self.value, Value::Na)))
68    }
69}
70
71/// bool(x) - Converts value to bool
72#[derive(BuiltinFunction)]
73#[builtin(name = "bool")]
74struct Bool<O: PineOutput> {
75    x: Value<O>,
76}
77
78impl<O: PineOutput> Bool<O> {
79    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
80        match &self.x {
81            Value::Bool(b) => Ok(Value::Bool(*b)),
82            Value::Int(n) => Ok(Value::Bool(*n != 0)),
83            Value::Number(n) => Ok(Value::Bool(*n != 0.0)),
84            Value::Na => Ok(Value::Bool(false)),
85            _ => Ok(Value::Bool(true)),
86        }
87    }
88}
89
90/// int(x) - Converts value to int (truncates float)
91#[derive(BuiltinFunction)]
92#[builtin(name = "int")]
93struct Int<O: PineOutput> {
94    x: Value<O>,
95}
96
97impl<O: PineOutput> Int<O> {
98    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
99        // `to_number` unwraps a series to its current value, so `int(close)`
100        // works exactly like `int(literal)`; na stays na.
101        match self.x.to_number()? {
102            Some(n) => Ok(Value::Int(n.trunc() as i64)),
103            None => Ok(Value::Na),
104        }
105    }
106}
107
108/// float(x) - Converts value to float
109#[derive(BuiltinFunction)]
110#[builtin(name = "float")]
111struct Float<O: PineOutput> {
112    x: Value<O>,
113}
114
115impl<O: PineOutput> Float<O> {
116    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
117        // `to_number` unwraps a series to its current value, so `float(close)`
118        // works exactly like `float(literal)`; na stays na.
119        match self.x.to_number()? {
120            Some(n) => Ok(Value::Number(n)),
121            None => Ok(Value::Na),
122        }
123    }
124}
125
126/// nz(source, replacement) - Replaces na values with default or replacement value
127#[derive(BuiltinFunction)]
128#[builtin(name = "nz")]
129struct Nz<O: PineOutput> {
130    source: Value<O>,
131    #[arg(default = Value::Number(0.0))]
132    replacement: Value<O>,
133}
134
135impl<O: PineOutput> Nz<O> {
136    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
137        // na source -> the replacement (any type; defaults to 0 when omitted).
138        // Pine `na` reaches here as `Value::Na` or a NaN number.
139        match &self.source {
140            Value::Na => Ok(self.replacement.clone()),
141            Value::Number(n) if n.is_nan() => Ok(self.replacement.clone()),
142            _ => Ok(self.source.clone()),
143        }
144    }
145}
146
147/// fixnan(source) - Replaces NaN values with previous nearest non-NaN value
148#[derive(BuiltinFunction)]
149#[builtin(name = "fixnan")]
150struct Fixnan<O: PineOutput> {
151    source: Value<O>,
152}
153
154impl<O: PineOutput> Fixnan<O> {
155    fn execute(&self, _ctx: &mut Interpreter<O>) -> Result<Value<O>, RuntimeError> {
156        // This is a simplified implementation
157        // A full implementation would need to track previous values across bar evaluations
158        match &self.source {
159            Value::Na => {
160                // Try to get the last non-na value from context
161                // For now, just return 0.0 as a placeholder
162                Ok(Value::Number(0.0))
163            }
164            Value::Number(n) if n.is_nan() => Ok(Value::Number(0.0)),
165            _ => Ok(self.source.clone()),
166        }
167    }
168}
169
170/// The na-cast functions (`box(x)`, `color(x)`, `string(x)`, …) — they cast `na`
171/// to a type, which in our dynamic typing is the identity on the argument.
172fn na_cast<O: PineOutput>() -> BuiltinFn<O> {
173    Rc::new(|_ctx, call_args| {
174        Ok(match call_args.args.into_iter().next() {
175            Some(EvaluatedArg::Positional(v)) => v,
176            Some(EvaluatedArg::Named { value, .. }) => value,
177            None => Value::Na,
178        })
179    })
180}
181
182/// Makes a namespace object also callable as its type's na-cast (`box.new` and
183/// `box(x)` on the same name).
184fn callable_namespace<O: PineOutput>(namespace: Value<O>) -> Value<O> {
185    match namespace {
186        Value::Object {
187            type_name,
188            fields,
189            value,
190            ..
191        } => Value::Object {
192            type_name,
193            fields,
194            value,
195            call: Some(Builtin::untyped(na_cast::<O>())),
196        },
197        other => other,
198    }
199}
200
201/// Register all builtin namespaces as objects and global functions
202/// Returns namespace objects to be loaded as variables (e.g., "array", "str", "ta")
203/// and global builtin functions (e.g., "na")
204/// Each member stores the builtin function pointer as Value::BuiltinFunction
205///
206/// This uses DefaultPineOutput for now. Full generic support will be added when the
207/// BuiltinFunction macro is updated to support generic output types.
208pub fn register_namespace_objects<
209    O: PineOutput
210        + LogOutput
211        + PlotOutput
212        + LabelOutput
213        + BoxOutput
214        + InputOutput
215        + LineOutput
216        + TableOutput
217        + MetadataOutput
218        + GlobalOutput
219        + AlertConditionOutput
220        + FillOutput
221        + DrawingOutput,
222>(
223    version: PineVersion,
224    syminfo: Option<SymInfo>,
225    timeframe: Option<Timeframe>,
226) -> (
227    HashMap<String, Value<O>>,
228    Vec<pine_interpreter::PerBarAdvance<O>>,
229) {
230    let mut namespaces = HashMap::new();
231    let mut advances = Vec::new();
232
233    // `syminfo` and `timeframe` are always present in Pine, so an absent one
234    // falls back to defaults.
235    namespaces.insert(
236        "syminfo".to_string(),
237        syminfo::create_syminfo(syminfo.unwrap_or_default()),
238    );
239    namespaces.insert(
240        "timeframe".to_string(),
241        timeframe::register(timeframe.unwrap_or_default()),
242    );
243
244    // Register namespace objects
245    namespaces.insert("array".to_string(), array::register());
246    namespaces.insert("box".to_string(), callable_namespace(r#box::register()));
247    namespaces.insert("chart".to_string(), chart::register());
248    namespaces.insert("color".to_string(), callable_namespace(color::register()));
249    namespaces.insert("map".to_string(), map::register());
250    namespaces.insert("session".to_string(), session::register());
251    namespaces.insert("runtime".to_string(), runtime::register());
252    namespaces.insert("alert".to_string(), alertcondition::register_alert());
253    namespaces.insert("ticker".to_string(), ticker::register());
254    namespaces.insert("earnings".to_string(), earnings::register());
255    namespaces.insert("footprint".to_string(), footprint::register_footprint());
256    namespaces.insert("volume_row".to_string(), footprint::register_volume_row());
257    namespaces.insert("dividends".to_string(), dividends::register());
258    namespaces.insert("currency".to_string(), currency::register());
259    for (name, value) in input::register(version) {
260        namespaces.insert(name, value);
261    }
262    namespaces.insert("label".to_string(), callable_namespace(label::register()));
263    for (name, value) in line::register(version) {
264        // `line` is also the `line(x)` na-cast; `hline` stays as-is.
265        let value = if name == "line" {
266            callable_namespace(value)
267        } else {
268            value
269        };
270        namespaces.insert(name, value);
271    }
272    namespaces.insert(
273        "string".to_string(),
274        Value::BuiltinFunction(Builtin::untyped(na_cast::<O>())),
275    );
276    namespaces.insert(
277        "linefill".to_string(),
278        callable_namespace(linefill::register()),
279    );
280    namespaces.insert("polyline".to_string(), polyline::register());
281    namespaces.insert("table".to_string(), callable_namespace(table::register()));
282    for (name, value) in indicator::register(version) {
283        namespaces.insert(name, value);
284    }
285    for (name, value) in library::register(version) {
286        namespaces.insert(name, value);
287    }
288    namespaces.insert("request".to_string(), request::register());
289    namespaces.insert("strategy".to_string(), strategy::register(version));
290    namespaces.insert("alertcondition".to_string(), alertcondition::register());
291    namespaces.insert("fill".to_string(), fill::register());
292    for (name, value) in globals::register() {
293        namespaces.insert(name, value);
294    }
295
296    // Constant-only namespaces (string tags used as arguments elsewhere).
297    namespaces.insert("size".to_string(), constants::size::register());
298    namespaces.insert("shape".to_string(), constants::shape::register());
299    namespaces.insert("location".to_string(), constants::location::register());
300    namespaces.insert("position".to_string(), constants::position::register());
301    namespaces.insert("display".to_string(), constants::display::register());
302    namespaces.insert("format".to_string(), constants::format::register());
303    namespaces.insert("order".to_string(), constants::order::register());
304    namespaces.insert("text".to_string(), constants::text::register());
305    namespaces.insert("xloc".to_string(), constants::xloc::register());
306    namespaces.insert("extend".to_string(), constants::extend::register());
307    namespaces.insert("barmerge".to_string(), constants::barmerge::register());
308    namespaces.insert("yloc".to_string(), constants::yloc::register());
309    namespaces.insert("scale".to_string(), constants::scale::register());
310    namespaces.insert("font".to_string(), constants::font::register());
311    namespaces.insert("splits".to_string(), constants::splits::register());
312    namespaces.insert("adjustment".to_string(), constants::adjustment::register());
313    namespaces.insert(
314        "backadjustment".to_string(),
315        constants::backadjustment::register(),
316    );
317    namespaces.insert(
318        "settlement_as_close".to_string(),
319        constants::settlement_as_close::register(),
320    );
321    namespaces.insert("log".to_string(), log::register());
322    for (name, func) in math::register(version) {
323        namespaces.insert(name, func);
324    }
325    namespaces.insert("matrix".to_string(), matrix::register());
326    for (name, func) in str::register(version) {
327        namespaces.insert(name, func);
328    }
329    let (ta_ns, ta_advance) = ta::register(version);
330    for (name, func) in ta_ns {
331        namespaces.insert(name, func);
332    }
333    advances.push(ta_advance);
334
335    // Register global builtin functions
336    namespaces.insert("na".to_string(), Na::<O>::builtin_value());
337    namespaces.insert("bool".to_string(), Bool::<O>::builtin_value());
338    namespaces.insert("int".to_string(), Int::<O>::builtin_value());
339    namespaces.insert("float".to_string(), Float::<O>::builtin_value());
340    namespaces.insert("nz".to_string(), Nz::<O>::builtin_value());
341    namespaces.insert("fixnan".to_string(), Fixnan::<O>::builtin_value());
342
343    // Register time/date functions
344    for (name, func) in time::register_time_functions() {
345        namespaces.insert(name, func);
346    }
347    // `dayofweek` is value + function + namespace at once; its scalar is
348    // refreshed each bar via `per_bar_object_values`.
349    namespaces.insert("dayofweek".to_string(), time::register_dayofweek());
350    namespaces.insert("time_close".to_string(), time::register_time_close());
351    namespaces.insert(
352        "time_tradingday".to_string(),
353        time::register_time_tradingday(),
354    );
355
356    // Register plot functions
357    for (name, func) in plot::register_plot_functions() {
358        namespaces.insert(name, func);
359    }
360
361    (namespaces, advances)
362}
363
364/// Per-bar variables, rebuilt for each [`Bar`] and registered before it executes.
365///
366/// The compile-time counterpart is [`register_namespace_objects`]; this holds the
367/// values that change every bar.
368pub fn register_per_bar<O: PineOutput>(bar: &Bar) -> Vec<(String, Value<O>)> {
369    vec![
370        ("barstate".to_string(), barstate::register(bar)),
371        ("timenow".to_string(), time::register_timenow()),
372    ]
373}
374
375/// Every built-in variable a [`Bar`] sets: the price series (OHLCV and its
376/// standard derivations) as history-carrying [`Value::Series`], `bar_index` as a
377/// plain number, then the per-bar namespaces from [`register_per_bar`].
378///
379/// The single source of truth for these names and formulas, shared by execution
380/// and the sema symbol table. The value's kind says how to store it: a
381/// `Value::Series` is one the interpreter should advance to accumulate lookback
382/// (`close[1]`); everything else is a plain assignment. Sema, needing only the
383/// names to resolve, registers them as-is.
384pub fn per_bar_variables<O: PineOutput>(
385    bar: &Bar,
386    last_bar: Option<&Bar>,
387) -> Vec<(String, Value<O>)> {
388    let series = |id: &str, value: f64| {
389        (
390            id.to_string(),
391            Value::Series(pine_interpreter::Series {
392                id: id.to_string(),
393                current: Box::new(Value::Number(value)),
394                history: None,
395            }),
396        )
397    };
398    let mut vars = vec![
399        series("open", bar.open),
400        series("high", bar.high),
401        series("low", bar.low),
402        series("close", bar.close),
403        series("volume", bar.volume),
404        series("hl2", (bar.high + bar.low) / 2.0),
405        series("hlc3", (bar.high + bar.low + bar.close) / 3.0),
406        series("hlcc4", (bar.high + bar.low + bar.close * 2.0) / 4.0),
407        series("ohlc4", (bar.open + bar.high + bar.low + bar.close) / 4.0),
408        ("bar_index".to_string(), Value::Number(bar.index as f64)),
409        (
410            "last_bar_index".to_string(),
411            last_bar.map_or(Value::Na, |b| Value::Number(b.index as f64)),
412        ),
413        (
414            "last_bar_time".to_string(),
415            last_bar.map_or(Value::Na, |b| Value::Number(b.time as f64)),
416        ),
417    ];
418    vars.extend(register_per_bar(bar));
419    vars
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425    use pine_interpreter::{EvaluatedArg, FunctionCallArgs};
426
427    #[test]
428    fn test_na() {
429        let mut ctx = Interpreter::<DefaultPineOutput>::new();
430
431        // Test with na value
432        let args = vec![EvaluatedArg::Positional(Value::Na)];
433        let result = Na::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
434        assert_eq!(result, Value::Bool(true));
435
436        // Test with number
437        let args = vec![EvaluatedArg::Positional(Value::Number(42.0))];
438        let result = Na::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
439        assert_eq!(result, Value::Bool(false));
440
441        // Test with string
442        let args = vec![EvaluatedArg::Positional(Value::String("hello".to_string()))];
443        let result = Na::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
444        assert_eq!(result, Value::Bool(false));
445
446        // Test with bool
447        let args = vec![EvaluatedArg::Positional(Value::Bool(true))];
448        let result = Na::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
449        assert_eq!(result, Value::Bool(false));
450    }
451
452    #[test]
453    fn test_bool() {
454        let mut ctx = Interpreter::<DefaultPineOutput>::new();
455
456        // Test number to bool
457        let args = vec![EvaluatedArg::Positional(Value::Number(5.0))];
458        let result = Bool::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
459        assert_eq!(result, Value::Bool(true));
460
461        let args = vec![EvaluatedArg::Positional(Value::Number(0.0))];
462        let result = Bool::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
463        assert_eq!(result, Value::Bool(false));
464
465        // Test na to bool
466        let args = vec![EvaluatedArg::Positional(Value::Na)];
467        let result = Bool::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
468        assert_eq!(result, Value::Bool(false));
469    }
470
471    #[test]
472    fn test_int() {
473        let mut ctx = Interpreter::<DefaultPineOutput>::new();
474
475        // Test float to int (truncate)
476        let args = vec![EvaluatedArg::Positional(Value::Number(5.7))];
477        let result = Int::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
478        assert_eq!(result, Value::Number(5.0));
479
480        let args = vec![EvaluatedArg::Positional(Value::Number(-5.7))];
481        let result = Int::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
482        assert_eq!(result, Value::Number(-5.0));
483
484        // Test bool to int
485        let args = vec![EvaluatedArg::Positional(Value::Bool(true))];
486        let result = Int::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
487        assert_eq!(result, Value::Number(1.0));
488
489        // Test na to int
490        let args = vec![EvaluatedArg::Positional(Value::Na)];
491        let result = Int::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
492        assert_eq!(result, Value::Na);
493    }
494
495    #[test]
496    fn test_float() {
497        let mut ctx = Interpreter::<DefaultPineOutput>::new();
498
499        // Test number to float
500        let args = vec![EvaluatedArg::Positional(Value::Number(5.0))];
501        let result = Float::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
502        assert_eq!(result, Value::Number(5.0));
503
504        // Test bool to float
505        let args = vec![EvaluatedArg::Positional(Value::Bool(true))];
506        let result = Float::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
507        assert_eq!(result, Value::Number(1.0));
508
509        // Test na to float
510        let args = vec![EvaluatedArg::Positional(Value::Na)];
511        let result = Float::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
512        assert_eq!(result, Value::Na);
513    }
514
515    #[test]
516    fn test_nz() {
517        let mut ctx = Interpreter::<DefaultPineOutput>::new();
518
519        // Test na value without replacement (should return 0.0)
520        let args = vec![EvaluatedArg::Positional(Value::Na)];
521        let result = Nz::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
522        assert_eq!(result, Value::Number(0.0));
523
524        // Test na value with replacement
525        let args = vec![
526            EvaluatedArg::Positional(Value::Na),
527            EvaluatedArg::Positional(Value::Number(42.0)),
528        ];
529        let result = Nz::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
530        assert_eq!(result, Value::Number(42.0));
531
532        // Test non-na value (should return source)
533        let args = vec![EvaluatedArg::Positional(Value::Number(5.0))];
534        let result = Nz::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
535        assert_eq!(result, Value::Number(5.0));
536    }
537
538    #[test]
539    fn test_fixnan() {
540        let mut ctx = Interpreter::<DefaultPineOutput>::new();
541
542        // Test na value
543        let args = vec![EvaluatedArg::Positional(Value::Na)];
544        let result = Fixnan::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
545        assert_eq!(result, Value::Number(0.0));
546
547        // Test normal value
548        let args = vec![EvaluatedArg::Positional(Value::Number(5.0))];
549        let result = Fixnan::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
550        assert_eq!(result, Value::Number(5.0));
551
552        // Test NaN value
553        let args = vec![EvaluatedArg::Positional(Value::Number(f64::NAN))];
554        let result = Fixnan::builtin_fn(&mut ctx, FunctionCallArgs::without_types(args)).unwrap();
555        assert_eq!(result, Value::Number(0.0));
556    }
557}