Skip to main content

pine_builtins/
lib.rs

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