Skip to main content

pine_builtins/
lib.rs

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