pineforge-codegen 0.6.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. pineforge_codegen/__init__.py +53 -0
  2. pineforge_codegen/analyzer/__init__.py +60 -0
  3. pineforge_codegen/analyzer/base.py +1563 -0
  4. pineforge_codegen/analyzer/call_handlers.py +895 -0
  5. pineforge_codegen/analyzer/contracts.py +163 -0
  6. pineforge_codegen/analyzer/diagnostics.py +118 -0
  7. pineforge_codegen/analyzer/tables.py +204 -0
  8. pineforge_codegen/analyzer/types.py +250 -0
  9. pineforge_codegen/ast_nodes.py +293 -0
  10. pineforge_codegen/codegen/__init__.py +78 -0
  11. pineforge_codegen/codegen/base.py +1381 -0
  12. pineforge_codegen/codegen/emit_top.py +875 -0
  13. pineforge_codegen/codegen/helpers.py +163 -0
  14. pineforge_codegen/codegen/helpers_syminfo.py +134 -0
  15. pineforge_codegen/codegen/input.py +189 -0
  16. pineforge_codegen/codegen/security.py +1564 -0
  17. pineforge_codegen/codegen/ta.py +298 -0
  18. pineforge_codegen/codegen/tables.py +613 -0
  19. pineforge_codegen/codegen/types.py +573 -0
  20. pineforge_codegen/codegen/visit_call.py +1305 -0
  21. pineforge_codegen/codegen/visit_expr.py +701 -0
  22. pineforge_codegen/codegen/visit_stmt.py +729 -0
  23. pineforge_codegen/errors.py +98 -0
  24. pineforge_codegen/lexer.py +531 -0
  25. pineforge_codegen/parser.py +1198 -0
  26. pineforge_codegen/pragmas.py +117 -0
  27. pineforge_codegen/signatures.py +808 -0
  28. pineforge_codegen/support_checker.py +1111 -0
  29. pineforge_codegen/symbols.py +118 -0
  30. pineforge_codegen/tokens.py +406 -0
  31. pineforge_codegen/tv_input_choices.py +86 -0
  32. pineforge_codegen-0.6.5.dist-info/METADATA +462 -0
  33. pineforge_codegen-0.6.5.dist-info/RECORD +35 -0
  34. pineforge_codegen-0.6.5.dist-info/WHEEL +4 -0
  35. pineforge_codegen-0.6.5.dist-info/licenses/LICENSE +197 -0
@@ -0,0 +1,808 @@
1
+ """Complete PineScript v6 intrinsic function signatures.
2
+
3
+ Single source of truth for all TradingView built-in function signatures.
4
+ Used by both analyzer (type inference, kwargs resolution) and codegen
5
+ (correct C++ emission).
6
+
7
+ Each signature specifies:
8
+ - params: ordered list of (name, type, default_value_or_None)
9
+ - return_type: PineType
10
+ - overloads: list of alternative param lists (if function is overloaded)
11
+ """
12
+
13
+ from __future__ import annotations
14
+ from dataclasses import dataclass, field
15
+ from typing import Any
16
+
17
+ from .symbols import PineType
18
+
19
+
20
+ @dataclass
21
+ class Param:
22
+ """One function parameter."""
23
+ name: str
24
+ pine_type: PineType = PineType.FLOAT
25
+ default: Any = None # None = required, else default value
26
+ is_series: bool = True # series vs simple/const
27
+
28
+
29
+ @dataclass
30
+ class FuncSig:
31
+ """One function signature (one overload)."""
32
+ params: list[Param] = field(default_factory=list)
33
+ return_type: PineType = PineType.FLOAT
34
+ returns_tuple: bool = False
35
+ tuple_count: int = 0 # number of tuple elements
36
+
37
+
38
+ @dataclass
39
+ class IntrinsicFunc:
40
+ """Complete intrinsic function definition with all overloads."""
41
+ name: str # e.g. "ta.sma" or "math.abs"
42
+ signatures: list[FuncSig] = field(default_factory=list)
43
+
44
+ @property
45
+ def primary(self) -> FuncSig:
46
+ return self.signatures[0]
47
+
48
+ @property
49
+ def param_names(self) -> list[str]:
50
+ """Param names from the widest overload (for kwargs resolution).
51
+
52
+ Uses the overload with the most params so all possible kwargs
53
+ can be resolved to positional positions.
54
+ """
55
+ widest = max(self.signatures, key=lambda s: len(s.params))
56
+ return [p.name for p in widest.params]
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Helper constructors
61
+ # ---------------------------------------------------------------------------
62
+
63
+ def _sig(params: list[tuple], ret: PineType = PineType.FLOAT,
64
+ returns_tuple: bool = False, tuple_count: int = 0) -> FuncSig:
65
+ """Shorthand to build a FuncSig from tuples of (name, type, default)."""
66
+ plist = []
67
+ for p in params:
68
+ name = p[0]
69
+ ptype = p[1] if len(p) > 1 else PineType.FLOAT
70
+ default = p[2] if len(p) > 2 else None
71
+ plist.append(Param(name=name, pine_type=ptype, default=default))
72
+ return FuncSig(params=plist, return_type=ret,
73
+ returns_tuple=returns_tuple, tuple_count=tuple_count)
74
+
75
+
76
+ def _func(name: str, *sigs: FuncSig) -> IntrinsicFunc:
77
+ return IntrinsicFunc(name=name, signatures=list(sigs))
78
+
79
+
80
+ # Shortcuts for common param types
81
+ F = PineType.FLOAT
82
+ I = PineType.INT
83
+ B = PineType.BOOL
84
+ S = PineType.STRING
85
+ NA = PineType.NA
86
+ VOID = PineType.VOID
87
+
88
+
89
+ # ============================================================================
90
+ # ta.* functions
91
+ # ============================================================================
92
+
93
+ TA_FUNCTIONS: dict[str, IntrinsicFunc] = {}
94
+
95
+ def _ta(short_name: str, *sigs: FuncSig) -> None:
96
+ TA_FUNCTIONS[short_name] = _func(f"ta.{short_name}", *sigs)
97
+
98
+ # --- Moving Averages ---
99
+ _ta("sma", _sig([("source", F), ("length", I)]))
100
+ _ta("ema", _sig([("source", F), ("length", I)]))
101
+ _ta("rma", _sig([("source", F), ("length", I)]))
102
+ _ta("wma", _sig([("source", F), ("length", I)]))
103
+ _ta("hma", _sig([("source", F), ("length", I)]))
104
+ _ta("vwma", _sig([("source", F), ("length", I)]))
105
+ _ta("alma", _sig([("source", F), ("length", I), ("offset", F, 0.85), ("sigma", F, 6.0)]))
106
+ _ta("swma", _sig([("source", F)]))
107
+ _ta("linreg", _sig([("source", F), ("length", I), ("offset", I, 0)]))
108
+
109
+ # --- Oscillators ---
110
+ _ta("rsi", _sig([("source", F), ("length", I)]))
111
+ _ta("stoch", _sig([("source", F), ("high", F), ("low", F), ("length", I)]))
112
+ _ta("cci", _sig([("source", F), ("length", I)]))
113
+ _ta("mfi", _sig([("series", F), ("length", I)]))
114
+ _ta("mom", _sig([("source", F), ("length", I)]))
115
+ _ta("roc", _sig([("source", F), ("length", I)]))
116
+ _ta("cmo", _sig([("source", F), ("length", I)]))
117
+ _ta("tsi", _sig([("source", F), ("short_length", I), ("long_length", I)]))
118
+ _ta("wpr", _sig([("length", I)]))
119
+ _ta("cog", _sig([("source", F), ("length", I)]))
120
+
121
+ # --- MACD ---
122
+ _ta("macd", _sig([("source", F), ("fastlen", I), ("slowlen", I), ("siglen", I)],
123
+ returns_tuple=True, tuple_count=3))
124
+
125
+ # --- Bollinger Bands / Keltner ---
126
+ _ta("bb", _sig([("source", F), ("length", I), ("mult", F, 2.0)],
127
+ returns_tuple=True, tuple_count=3))
128
+ _ta("kc", _sig([("source", F), ("length", I), ("mult", F, 1.5)],
129
+ returns_tuple=True, tuple_count=3))
130
+ _ta("bbw", _sig([("source", F), ("length", I), ("mult", F, 2.0)]))
131
+ _ta("kcw", _sig([("source", F), ("length", I), ("mult", F, 1.5)]))
132
+
133
+ # --- Volatility & Range ---
134
+ _ta("atr", _sig([("length", I)]))
135
+ _ta("tr", _sig([("handle_na", B, False)]))
136
+ _ta("stdev", _sig([("source", F), ("length", I)]))
137
+ _ta("variance",_sig([("source", F), ("length", I)]))
138
+
139
+ # --- Trend ---
140
+ _ta("supertrend", _sig([("factor", F), ("atrPeriod", I)],
141
+ returns_tuple=True, tuple_count=2))
142
+ _ta("dmi", _sig([("diLength", I), ("adxSmoothing", I)],
143
+ returns_tuple=True, tuple_count=3))
144
+ _ta("sar", _sig([("start", F, 0.02), ("inc", F, 0.02), ("max", F, 0.2)]))
145
+
146
+ # --- Crossover / State ---
147
+ _ta("crossover", _sig([("source1", F), ("source2", F)], ret=B))
148
+ _ta("crossunder", _sig([("source1", F), ("source2", F)], ret=B))
149
+ _ta("cross", _sig([("source1", F), ("source2", F)], ret=B))
150
+ _ta("rising", _sig([("source", F), ("length", I)], ret=B))
151
+ _ta("falling", _sig([("source", F), ("length", I)], ret=B))
152
+ _ta("change", _sig([("source", F), ("length", I, 1)]))
153
+
154
+ # --- Extremes & Lookback ---
155
+ # ta.highest / ta.lowest have TWO overloads:
156
+ # ta.highest(source, length) — full form
157
+ # ta.highest(length) — source defaults to high/low
158
+ _ta("highest",
159
+ _sig([("source", F), ("length", I)]),
160
+ _sig([("length", I)]))
161
+ _ta("lowest",
162
+ _sig([("source", F), ("length", I)]),
163
+ _sig([("length", I)]))
164
+ _ta("highestbars", _sig([("source", F), ("length", I)], ret=I))
165
+ _ta("lowestbars", _sig([("source", F), ("length", I)], ret=I))
166
+ _ta("median", _sig([("source", F), ("length", I)]))
167
+ _ta("percentrank", _sig([("source", F), ("length", I)]))
168
+ _ta("percentile_nearest_rank", _sig([("source", F), ("length", I), ("percentage", F)]))
169
+ _ta("percentile_linear_interpolation", _sig([("source", F), ("length", I), ("percentage", F)]))
170
+
171
+ # --- Pivots ---
172
+ # ta.pivothigh/pivotlow have TWO overloads:
173
+ # ta.pivothigh(source, leftbars, rightbars)
174
+ # ta.pivothigh(leftbars, rightbars) — source defaults to high/low
175
+ _ta("pivothigh",
176
+ _sig([("source", F), ("leftbars", I), ("rightbars", I)]),
177
+ _sig([("leftbars", I), ("rightbars", I)]))
178
+ _ta("pivotlow",
179
+ _sig([("source", F), ("leftbars", I), ("rightbars", I)]),
180
+ _sig([("leftbars", I), ("rightbars", I)]))
181
+
182
+ # --- Volume indicators ---
183
+ # ta.vwap has TWO overloads:
184
+ # ta.vwap(source, anchor, stdev_mult) — 3-arg bands form (primary), returns [vwap, upper, lower]
185
+ # ta.vwap(source) — scalar 1-arg form (daily anchor)
186
+ # Note: primary is the 3-arg form so param_names covers all kwargs; the
187
+ # analyzer remaps 3-arg calls to the internal "vwap_bands" dispatch key.
188
+ _ta("vwap", _sig([("source", F), ("anchor", B), ("stdev_mult", F)],
189
+ returns_tuple=True, tuple_count=3),
190
+ _sig([("source", F)]))
191
+
192
+ # --- Statistical (additional) ---
193
+ _ta("mode", _sig([("source", F), ("length", I)]))
194
+ _ta("range", _sig([("source", F), ("length", I)]))
195
+ _ta("dev", _sig([("source", F), ("length", I)]))
196
+
197
+ # --- Cumulative & Historical ---
198
+ _ta("cum", _sig([("source", F)]))
199
+ _ta("barssince", _sig([("condition", B)], ret=I))
200
+ _ta("valuewhen", _sig([("condition", B), ("source", F), ("occurrence", I)]))
201
+ _ta("correlation", _sig([("source1", F), ("source2", F), ("length", I)]))
202
+
203
+ # --- Chart all-time extremes (single series) ---
204
+ _ta("max", _sig([("source", F)]))
205
+ _ta("min", _sig([("source", F)]))
206
+
207
+ # --- Rank Correlation Index ---
208
+ _ta("rci", _sig([("source", F), ("length", I)]))
209
+
210
+ # --- Pivot levels ---
211
+ _ta("pivot_point_levels",
212
+ _sig([("type", S), ("anchor", B), ("developing", B, False)]))
213
+
214
+
215
+ # ============================================================================
216
+ # math.* functions
217
+ # ============================================================================
218
+
219
+ MATH_FUNCTIONS: dict[str, IntrinsicFunc] = {}
220
+
221
+ def _math(short_name: str, *sigs: FuncSig) -> None:
222
+ MATH_FUNCTIONS[short_name] = _func(f"math.{short_name}", *sigs)
223
+
224
+ # Arithmetic
225
+ _math("abs", _sig([("x", F)]))
226
+ _math("ceil", _sig([("x", F)], ret=I))
227
+ _math("floor", _sig([("x", F)], ret=I))
228
+ _math("round",
229
+ _sig([("x", F)], ret=I), # round(x) → int
230
+ _sig([("x", F), ("precision", I)])) # round(x, n) → float
231
+ _math("round_to_mintick", _sig([("x", F)]))
232
+ _math("sign", _sig([("x", F)], ret=I))
233
+ _math("max",
234
+ _sig([("x", F), ("y", F)]), # 2-arg
235
+ _sig([("x", F), ("y", F), ("z", F)]), # 3-arg
236
+ _sig([("x", F), ("y", F), ("z", F), ("w", F)])) # 4-arg
237
+ _math("min",
238
+ _sig([("x", F), ("y", F)]),
239
+ _sig([("x", F), ("y", F), ("z", F)]),
240
+ _sig([("x", F), ("y", F), ("z", F), ("w", F)]))
241
+ _math("avg",
242
+ _sig([("x", F), ("y", F)]),
243
+ _sig([("x", F), ("y", F), ("z", F)]),
244
+ _sig([("x", F), ("y", F), ("z", F), ("w", F)]),
245
+ _sig([("x", F), ("y", F), ("z", F), ("w", F), ("u", F)]),
246
+ _sig([("x", F), ("y", F), ("z", F), ("w", F), ("u", F), ("v", F)]))
247
+ _math("sum", _sig([("source", F), ("length", I)])) # rolling sum → math::Sum in runtime
248
+ _math("pow", _sig([("base", F), ("exp", F)]))
249
+ _math("sqrt", _sig([("x", F)]))
250
+ _math("exp", _sig([("x", F)]))
251
+ _math("log", _sig([("x", F)]))
252
+ _math("log10", _sig([("x", F)]))
253
+ _math("random", _sig([("min", F, 0.0), ("max", F, 1.0), ("seed", I, None)]))
254
+
255
+ # Trigonometry
256
+ _math("sin", _sig([("x", F)]))
257
+ _math("cos", _sig([("x", F)]))
258
+ _math("tan", _sig([("x", F)]))
259
+ _math("asin", _sig([("x", F)]))
260
+ _math("acos", _sig([("x", F)]))
261
+ _math("atan", _sig([("x", F)]))
262
+ _math("atan2", _sig([("y", F), ("x", F)]))
263
+ _math("todegrees", _sig([("x", F)]))
264
+ _math("toradians", _sig([("x", F)]))
265
+
266
+
267
+ # ============================================================================
268
+ # strategy.* functions
269
+ # ============================================================================
270
+
271
+ STRATEGY_FUNCTIONS: dict[str, IntrinsicFunc] = {}
272
+
273
+ def _strat(short_name: str, *sigs: FuncSig) -> None:
274
+ STRATEGY_FUNCTIONS[short_name] = _func(f"strategy.{short_name}", *sigs)
275
+
276
+ _strat("entry", _sig([
277
+ ("id", S), ("direction", B), ("qty", F, None),
278
+ ("limit", F, None), ("stop", F, None),
279
+ ("oca_name", S, None), ("oca_type", I, None),
280
+ ("comment", S, None), ("alert_message", S, None),
281
+ ("disable_alert", B, None), ("qty_type", I, None),
282
+ ], ret=VOID))
283
+
284
+ _strat("order", _sig([
285
+ ("id", S), ("direction", B), ("qty", F, None),
286
+ ("limit", F, None), ("stop", F, None),
287
+ ("oca_name", S, None), ("oca_type", I, None),
288
+ ("comment", S, None), ("alert_message", S, None),
289
+ ("disable_alert", B, None), ("qty_type", I, None),
290
+ ], ret=VOID))
291
+
292
+ _strat("exit", _sig([
293
+ ("id", S), ("from_entry", S, ""),
294
+ ("qty", F, None), ("qty_percent", F, None),
295
+ ("profit", F, None), ("loss", F, None),
296
+ ("limit", F, None), ("stop", F, None),
297
+ ("trail_price", F, None), ("trail_points", F, None),
298
+ ("trail_offset", F, None),
299
+ ("oca_name", S, None),
300
+ ("comment", S, None), ("comment_profit", S, None),
301
+ ("comment_loss", S, None), ("comment_trailing", S, None),
302
+ ("alert_message", S, None),
303
+ ("alert_profit", S, None), ("alert_loss", S, None), ("alert_trailing", S, None),
304
+ ("disable_alert", B, None),
305
+ ], ret=VOID))
306
+
307
+ _strat("close", _sig([
308
+ ("id", S), ("comment", S, None),
309
+ ("qty", F, None), ("qty_percent", F, None),
310
+ ("alert_message", S, None), ("disable_alert", B, None),
311
+ ("immediately", B, False),
312
+ ], ret=VOID))
313
+
314
+ _strat("close_all", _sig([
315
+ ("comment", S, None), ("alert_message", S, None),
316
+ ("disable_alert", B, None), ("immediately", B, False),
317
+ ], ret=VOID))
318
+
319
+ _strat("cancel", _sig([("id", S)], ret=VOID))
320
+ _strat("cancel_all", _sig([], ret=VOID))
321
+
322
+ _strat("convert_to_account", _sig([("value", F)], ret=F))
323
+ _strat("convert_to_symbol", _sig([("value", F)], ret=F))
324
+ _strat("default_entry_qty", _sig([("fill_price", F)], ret=F))
325
+
326
+
327
+ # ============================================================================
328
+ # str.* functions
329
+ # ============================================================================
330
+
331
+ STR_FUNCTIONS: dict[str, IntrinsicFunc] = {}
332
+
333
+ def _str(short_name: str, *sigs: FuncSig) -> None:
334
+ STR_FUNCTIONS[short_name] = _func(f"str.{short_name}", *sigs)
335
+
336
+ _str("tostring",
337
+ _sig([("value", F)], ret=S),
338
+ _sig([("value", F), ("format", S)], ret=S))
339
+ _str("tonumber", _sig([("string", S)], ret=F))
340
+ _str("format", _sig([("formatStr", S)], ret=S)) # variadic
341
+ _str("format_time", _sig([("time", I), ("format", S), ("timezone", S, "")], ret=S))
342
+ _str("length", _sig([("string", S)], ret=I))
343
+ _str("contains", _sig([("source", S), ("str", S)], ret=B))
344
+ _str("startswith", _sig([("source", S), ("str", S)], ret=B))
345
+ _str("endswith", _sig([("source", S), ("str", S)], ret=B))
346
+ _str("pos", _sig([("source", S), ("str", S)], ret=I))
347
+ _str("substring",
348
+ _sig([("source", S), ("begin_pos", I)], ret=S),
349
+ _sig([("source", S), ("begin_pos", I), ("end_pos", I)], ret=S))
350
+ _str("replace", _sig([("source", S), ("target", S), ("replacement", S), ("occurrence", I, 0)], ret=S))
351
+ _str("replace_all", _sig([("source", S), ("target", S), ("replacement", S)], ret=S))
352
+ _str("lower", _sig([("source", S)], ret=S))
353
+ _str("upper", _sig([("source", S)], ret=S))
354
+ _str("trim", _sig([("source", S)], ret=S))
355
+ _str("repeat", _sig([("source", S), ("count", I)], ret=S))
356
+ _str("match", _sig([("source", S), ("regex", S)], ret=S))
357
+ _str("split", _sig([("string", S), ("separator", S)], ret=S)) # returns array<string>
358
+
359
+
360
+ # ============================================================================
361
+ # map.* functions
362
+ # ============================================================================
363
+
364
+ MAP_FUNCTIONS: dict[str, IntrinsicFunc] = {}
365
+
366
+ def _map(short_name: str, *sigs: FuncSig) -> None:
367
+ MAP_FUNCTIONS[short_name] = _func(f"map.{short_name}", *sigs)
368
+
369
+ _map("new", _sig([], ret=F))
370
+ _map("put", _sig([("id", F), ("key", S), ("value", F)], ret=F))
371
+ _map("get", _sig([("id", F), ("key", S)], ret=F))
372
+ _map("remove", _sig([("id", F), ("key", S)], ret=F))
373
+ _map("contains", _sig([("id", F), ("key", S)], ret=B))
374
+ _map("size", _sig([("id", F)], ret=I))
375
+ _map("clear", _sig([("id", F)], ret=VOID))
376
+ _map("keys", _sig([("id", F)], ret=F)) # returns array
377
+ _map("values", _sig([("id", F)], ret=F)) # returns array
378
+ _map("copy", _sig([("id", F)], ret=F))
379
+ _map("put_all", _sig([("id", F), ("id2", F)], ret=VOID))
380
+
381
+
382
+ # ============================================================================
383
+ # syminfo.* functions
384
+ # ============================================================================
385
+
386
+ SYMINFO_FUNCTIONS: dict[str, IntrinsicFunc] = {}
387
+
388
+ def _syminfo_fn(short_name: str, *sigs: FuncSig) -> None:
389
+ SYMINFO_FUNCTIONS[short_name] = _func(f"syminfo.{short_name}", *sigs)
390
+
391
+ _syminfo_fn("prefix", _sig([], ret=S))
392
+ _syminfo_fn("ticker", _sig([], ret=S))
393
+
394
+
395
+ # ============================================================================
396
+ # Standalone built-in functions (no namespace)
397
+ # ============================================================================
398
+
399
+ BUILTIN_FUNCTIONS: dict[str, IntrinsicFunc] = {}
400
+
401
+ def _builtin(name: str, *sigs: FuncSig) -> None:
402
+ BUILTIN_FUNCTIONS[name] = _func(name, *sigs)
403
+
404
+ _builtin("na", _sig([("x", F)], ret=B))
405
+ _builtin("nz",
406
+ _sig([("x", F)], ret=F),
407
+ _sig([("x", F), ("y", F)], ret=F))
408
+ _builtin("fixnan", _sig([("x", F)], ret=F))
409
+ _builtin("timestamp",
410
+ _sig([("year", I), ("month", I), ("day", I),
411
+ ("hour", I, 0), ("minute", I, 0), ("second", I, 0)], ret=I))
412
+ _builtin("time",
413
+ _sig([("timeframe", S), ("session", S), ("timezone", S, "")], ret=I),
414
+ _sig([("timeframe", S)], ret=I))
415
+ _builtin("time_close",
416
+ _sig([("timeframe", S), ("session", S), ("timezone", S, "")], ret=I),
417
+ _sig([("timeframe", S)], ret=I))
418
+ _builtin("year", _sig([("time", I, None), ("timezone", S, "")], ret=I))
419
+ _builtin("month", _sig([("time", I, None), ("timezone", S, "")], ret=I))
420
+ _builtin("dayofmonth", _sig([("time", I, None), ("timezone", S, "")], ret=I))
421
+ _builtin("dayofweek", _sig([("time", I, None), ("timezone", S, "")], ret=I))
422
+ _builtin("hour", _sig([("time", I, None), ("timezone", S, "")], ret=I))
423
+ _builtin("minute", _sig([("time", I, None), ("timezone", S, "")], ret=I))
424
+ _builtin("second", _sig([("time", I, None), ("timezone", S, "")], ret=I))
425
+ _builtin("weekofyear", _sig([("time", I, None), ("timezone", S, "")], ret=I))
426
+ _builtin("max_bars_back", _sig([("var", F), ("num", I)], ret=VOID))
427
+
428
+ # Type casts
429
+ _builtin("int", _sig([("x", F)], ret=I))
430
+ _builtin("float", _sig([("x", I)], ret=F))
431
+ _builtin("bool", _sig([("x", F)], ret=B))
432
+ _builtin("string", _sig([("x", F)], ret=S))
433
+
434
+ # Timeframe
435
+ _builtin("timeframe.change", _sig([("timeframe", S)], ret=B))
436
+ _builtin("timeframe.in_seconds", _sig([("timeframe", S)], ret=F))
437
+
438
+ # Plain input() — overloaded on type of defval (PineScript v6)
439
+ _builtin("input",
440
+ _sig([("defval", F), ("title", S, "")], ret=F),
441
+ _sig([("defval", I), ("title", S, "")], ret=I),
442
+ _sig([("defval", B), ("title", S, "")], ret=B),
443
+ _sig([("defval", S), ("title", S, "")], ret=S))
444
+
445
+
446
+ # ============================================================================
447
+ # input.* functions
448
+ # ============================================================================
449
+
450
+ INPUT_FUNCTIONS: dict[str, IntrinsicFunc] = {}
451
+
452
+ def _input(short_name: str, *sigs: FuncSig) -> None:
453
+ INPUT_FUNCTIONS[short_name] = _func(f"input.{short_name}", *sigs)
454
+
455
+ # Common trailing: tooltip, inline, group, display (plot_display), confirm
456
+ _C = [("tooltip", S, None), ("inline", S, None), ("group", S, None),
457
+ ("display", I, None), ("confirm", B, None)]
458
+
459
+ _input("int", _sig([("defval", I), ("title", S, ""), ("minval", I, None),
460
+ ("maxval", I, None), ("step", I, 1)] + _C, ret=I))
461
+ _input("float", _sig([("defval", F), ("title", S, ""), ("minval", F, None),
462
+ ("maxval", F, None), ("step", F, None)] + _C, ret=F))
463
+ _input("bool", _sig([("defval", B), ("title", S, "")] + _C, ret=B))
464
+ _input("string", _sig([("defval", S), ("title", S, ""), ("options", S, None)] + _C, ret=S))
465
+ _input("source", _sig([("defval", F), ("title", S, "")] + _C, ret=F))
466
+ _input("color", _sig([("defval", I), ("title", S, "")] + _C, ret=I))
467
+ _input("timeframe", _sig([("defval", S, ""), ("title", S, "")] + _C, ret=S))
468
+ _input("session", _sig([("defval", S, ""), ("title", S, "")] + _C, ret=S))
469
+ _input("symbol", _sig([("defval", S, ""), ("title", S, "")] + _C, ret=S))
470
+ _input("price", _sig([("defval", F), ("title", S, "")] + _C, ret=F))
471
+ _input("time", _sig([("defval", I), ("title", S, "")] + _C, ret=I))
472
+ _input("text_area", _sig([("defval", S), ("title", S, "")] + _C, ret=S))
473
+ # defval is an enum member (typed as float in Pine's overloads; value is int index)
474
+ _input("enum", _sig([("defval", F), ("title", S, "")] + _C, ret=I))
475
+
476
+
477
+ # ============================================================================
478
+ # Math constants
479
+ # ============================================================================
480
+
481
+ MATH_CONSTANTS = {
482
+ "pi": 3.141592653589793,
483
+ "e": 2.718281828459045,
484
+ "phi": 1.618033988749895,
485
+ "rphi": 0.6180339887498949,
486
+ }
487
+
488
+
489
+ # ============================================================================
490
+ # Built-in variables (not functions)
491
+ # ============================================================================
492
+
493
+ BUILTIN_VARIABLES: dict[str, PineType] = {
494
+ # Price series
495
+ "open": F, "high": F, "low": F, "close": F, "volume": F,
496
+ # Derived price
497
+ "hl2": F, "hlc3": F, "hlcc4": F, "ohlc4": F,
498
+ # Bar info
499
+ "bar_index": I, "time": I, "time_close": I,
500
+ "last_bar_index": I, "last_bar_time": I, "timenow": I, "time_tradingday": I,
501
+ # Time/date (when used as variables, NOT function calls)
502
+ "hour": I, "minute": I, "second": I,
503
+ "dayofmonth": I, "dayofweek": I,
504
+ "month": I, "year": I, "weekofyear": I,
505
+ # Special
506
+ "na": NA,
507
+ # ta.tr as a variable
508
+ "ta.tr": F,
509
+ # Official no-call ta.* series variables.
510
+ "ta.obv": F, "ta.accdist": F, "ta.nvi": F, "ta.pvi": F,
511
+ "ta.pvt": F, "ta.wad": F, "ta.wvad": F, "ta.iii": F,
512
+ # chart.is_* chart-type predicates (batch engine is always standard chart)
513
+ "chart.is_standard": B,
514
+ "chart.is_heikinashi": B,
515
+ "chart.is_kagi": B,
516
+ "chart.is_linebreak": B,
517
+ "chart.is_pnf": B,
518
+ "chart.is_range": B,
519
+ "chart.is_renko": B,
520
+ # Session predicates (Pine v6 HIGH — backed by pine_session_is* engine fns)
521
+ "session.ismarket": B, "session.ispremarket": B, "session.ispostmarket": B,
522
+ "session.isfirstbar": B, "session.isfirstbar_regular": B,
523
+ "session.islastbar": B, "session.islastbar_regular": B,
524
+ }
525
+
526
+ # Strategy variables (read-only properties, not function calls)
527
+ STRATEGY_VARIABLES: dict[str, PineType] = {
528
+ "strategy.position_size": F,
529
+ "strategy.position_avg_price": F,
530
+ "strategy.position_entry_name": S,
531
+ "strategy.equity": F,
532
+ "strategy.initial_capital": F,
533
+ "strategy.netprofit": F,
534
+ "strategy.netprofit_percent": F,
535
+ "strategy.openprofit": F,
536
+ "strategy.openprofit_percent": F,
537
+ "strategy.grossprofit": F,
538
+ "strategy.grossloss": F,
539
+ "strategy.closedtrades": I,
540
+ "strategy.opentrades": I,
541
+ "strategy.wintrades": I,
542
+ "strategy.losstrades": I,
543
+ "strategy.eventrades": I,
544
+ "strategy.max_contracts_held_all": F,
545
+ "strategy.max_contracts_held_long": F,
546
+ "strategy.max_contracts_held_short": F,
547
+ "strategy.max_drawdown": F,
548
+ "strategy.max_drawdown_percent": F,
549
+ "strategy.max_runup": F,
550
+ "strategy.max_runup_percent": F,
551
+ "strategy.avg_trade": F,
552
+ "strategy.avg_trade_percent": F,
553
+ "strategy.avg_winning_trade": F,
554
+ "strategy.avg_losing_trade": F,
555
+ "strategy.avg_winning_trade_percent": F,
556
+ "strategy.avg_losing_trade_percent": F,
557
+ "strategy.margin_liquidation_price": F,
558
+ "strategy.grossprofit_percent": F,
559
+ "strategy.grossloss_percent": F,
560
+ "strategy.account_currency": S,
561
+ "strategy.opentrades.capital_held": F,
562
+ # Direction constants
563
+ "strategy.long": B,
564
+ "strategy.short": B,
565
+ # Qty type constants
566
+ "strategy.fixed": I,
567
+ "strategy.cash": I,
568
+ "strategy.percent_of_equity": I,
569
+ # Commission type constants
570
+ "strategy.commission.percent": I,
571
+ "strategy.commission.cash_per_contract": I,
572
+ "strategy.commission.cash_per_order": I,
573
+ # OCA constants
574
+ "strategy.oca.cancel": I,
575
+ "strategy.oca.reduce": I,
576
+ "strategy.oca.none": I,
577
+ # Direction constants
578
+ "strategy.direction.all": I,
579
+ "strategy.direction.long": I,
580
+ "strategy.direction.short": I,
581
+ }
582
+
583
+ # Barstate variables
584
+ BARSTATE_VARIABLES: dict[str, PineType] = {
585
+ "barstate.isfirst": B,
586
+ "barstate.islast": B,
587
+ "barstate.ishistory": B,
588
+ "barstate.isrealtime": B,
589
+ "barstate.isnew": B,
590
+ "barstate.isconfirmed": B,
591
+ "barstate.islastconfirmedhistory": B,
592
+ }
593
+
594
+ # Syminfo variables
595
+ SYMINFO_VARIABLES: dict[str, PineType] = {
596
+ "syminfo.ticker": S,
597
+ "syminfo.tickerid": S,
598
+ "syminfo.basecurrency": S,
599
+ "syminfo.currency": S,
600
+ "syminfo.description": S,
601
+ "syminfo.mintick": F,
602
+ "syminfo.pointvalue": F,
603
+ "syminfo.type": S,
604
+ "syminfo.timezone": S,
605
+ "syminfo.session": S,
606
+ "syminfo.volumetype": S,
607
+ # --- Critical fix: were silently emitting 0; now na-accept ---
608
+ "syminfo.prefix": S,
609
+ "syminfo.root": S,
610
+ "syminfo.pricescale": F,
611
+ "syminfo.minmove": F,
612
+ # --- External-data fields: na-accept so scripts compile ---
613
+ "syminfo.mincontract": F,
614
+ "syminfo.current_contract": S,
615
+ "syminfo.expiration_date": I,
616
+ "syminfo.isin": S,
617
+ "syminfo.sector": S,
618
+ "syminfo.industry": S,
619
+ # --- LOW-tier na-accepts: financial/fundamental data ---
620
+ "syminfo.employees": F,
621
+ "syminfo.shareholders": F,
622
+ "syminfo.shares_outstanding_float": F,
623
+ "syminfo.shares_outstanding_total": F,
624
+ "syminfo.recommendations_buy": F,
625
+ "syminfo.recommendations_strong_buy": F,
626
+ "syminfo.recommendations_hold": F,
627
+ "syminfo.recommendations_sell": F,
628
+ "syminfo.recommendations_strong_sell": F,
629
+ "syminfo.recommendations_total": F,
630
+ "syminfo.recommendations_date": F,
631
+ "syminfo.target_price_average": F,
632
+ "syminfo.target_price_high": F,
633
+ "syminfo.target_price_low": F,
634
+ "syminfo.target_price_median": F,
635
+ "syminfo.target_price_date": F,
636
+ "syminfo.target_price_analysts_count": F,
637
+ # --- Syminfo derivations ---
638
+ "syminfo.main_tickerid": S,
639
+ "syminfo.country": S,
640
+ }
641
+
642
+ # Timeframe variables
643
+ TIMEFRAME_VARIABLES: dict[str, PineType] = {
644
+ "timeframe.period": S,
645
+ "timeframe.multiplier": I,
646
+ "timeframe.isintraday": B,
647
+ "timeframe.isdaily": B,
648
+ "timeframe.isweekly": B,
649
+ "timeframe.ismonthly": B,
650
+ "timeframe.isdwm": B,
651
+ "timeframe.main_period": S,
652
+ "timeframe.isticks": B,
653
+ }
654
+
655
+ # display.* plot_display constants (PineScript v6 — values for C++ codegen)
656
+ DISPLAY_VARIABLES: dict[str, PineType] = {
657
+ "display.all": I,
658
+ "display.none": I,
659
+ "display.pane": I,
660
+ "display.data_window": I,
661
+ "display.status_line": I,
662
+ "display.price_scale": I,
663
+ }
664
+
665
+
666
+
667
+
668
+ # ============================================================================
669
+ # TA functions that have implicit default source (1-arg overload)
670
+ # When called with fewer args than the primary signature, the source
671
+ # is implicitly `high` (for highest/pivothigh) or `low` (for lowest/pivotlow).
672
+ # ============================================================================
673
+
674
+ TA_DEFAULT_SOURCE = {
675
+ "highest": "high",
676
+ "lowest": "low",
677
+ "pivothigh": "high",
678
+ "pivotlow": "low",
679
+ }
680
+
681
+
682
+ # ============================================================================
683
+ # Lookup helpers
684
+ # ============================================================================
685
+
686
+ def resolve_overload(func: IntrinsicFunc, num_args: int) -> FuncSig:
687
+ """Pick the best matching overload for a given argument count."""
688
+ # Try exact match first
689
+ for sig in func.signatures:
690
+ required = sum(1 for p in sig.params if p.default is None)
691
+ total = len(sig.params)
692
+ if required <= num_args <= total:
693
+ return sig
694
+ # Fallback to primary
695
+ return func.primary
696
+
697
+
698
+ def merge_kwargs_to_positional(
699
+ sig: FuncSig,
700
+ args: list,
701
+ kwargs: dict,
702
+ ) -> list:
703
+ """Merge positional args and kwargs into a unified positional arg list
704
+ using the function signature's parameter names.
705
+
706
+ Returns a list of AST nodes in positional order.
707
+ """
708
+ merged = list(args)
709
+ for i, param in enumerate(sig.params):
710
+ if param.name in kwargs:
711
+ while len(merged) <= i:
712
+ merged.append(None)
713
+ if i >= len(args) or merged[i] is None:
714
+ merged[i] = kwargs[param.name]
715
+ # Trim trailing Nones
716
+ while merged and merged[-1] is None:
717
+ merged.pop()
718
+ return merged
719
+
720
+
721
+ def get_param_names(namespace: str | None, func_name: str) -> list[str] | None:
722
+ """Get parameter names for a function (for kwargs resolution).
723
+
724
+ Returns None if the function is not a known intrinsic.
725
+ """
726
+ if namespace == "ta" and func_name in TA_FUNCTIONS:
727
+ return TA_FUNCTIONS[func_name].param_names
728
+ if namespace == "math" and func_name in MATH_FUNCTIONS:
729
+ return MATH_FUNCTIONS[func_name].param_names
730
+ if namespace == "strategy" and func_name in STRATEGY_FUNCTIONS:
731
+ return STRATEGY_FUNCTIONS[func_name].param_names
732
+ if namespace == "str" and func_name in STR_FUNCTIONS:
733
+ return STR_FUNCTIONS[func_name].param_names
734
+ if namespace == "input" and func_name in INPUT_FUNCTIONS:
735
+ return INPUT_FUNCTIONS[func_name].param_names
736
+ if namespace == "map" and func_name in MAP_FUNCTIONS:
737
+ return MAP_FUNCTIONS[func_name].param_names
738
+ if namespace == "syminfo" and func_name in SYMINFO_FUNCTIONS:
739
+ return SYMINFO_FUNCTIONS[func_name].param_names
740
+ if namespace is None and func_name in BUILTIN_FUNCTIONS:
741
+ return BUILTIN_FUNCTIONS[func_name].param_names
742
+ return None
743
+
744
+
745
+ def get_return_type(namespace: str | None, func_name: str,
746
+ num_args: int = 0) -> PineType:
747
+ """Get return type for a function call."""
748
+ func = None
749
+ if namespace == "ta":
750
+ func = TA_FUNCTIONS.get(func_name)
751
+ elif namespace == "math":
752
+ func = MATH_FUNCTIONS.get(func_name)
753
+ elif namespace == "strategy":
754
+ func = STRATEGY_FUNCTIONS.get(func_name)
755
+ elif namespace == "str":
756
+ func = STR_FUNCTIONS.get(func_name)
757
+ elif namespace == "input":
758
+ func = INPUT_FUNCTIONS.get(func_name)
759
+ elif namespace == "map":
760
+ func = MAP_FUNCTIONS.get(func_name)
761
+ elif namespace == "syminfo":
762
+ func = SYMINFO_FUNCTIONS.get(func_name)
763
+ elif namespace is None:
764
+ func = BUILTIN_FUNCTIONS.get(func_name)
765
+
766
+ if func is None:
767
+ return PineType.UNKNOWN
768
+ sig = resolve_overload(func, num_args)
769
+ return sig.return_type
770
+
771
+
772
+ def is_intrinsic_function(namespace: str | None, func_name: str) -> bool:
773
+ """Check if a function is a known TradingView intrinsic."""
774
+ if namespace == "ta":
775
+ return func_name in TA_FUNCTIONS
776
+ if namespace == "math":
777
+ return func_name in MATH_FUNCTIONS
778
+ if namespace == "strategy":
779
+ return func_name in STRATEGY_FUNCTIONS
780
+ if namespace == "str":
781
+ return func_name in STR_FUNCTIONS
782
+ if namespace == "input":
783
+ return func_name in INPUT_FUNCTIONS
784
+ if namespace == "map":
785
+ return func_name in MAP_FUNCTIONS
786
+ if namespace == "syminfo":
787
+ return func_name in SYMINFO_FUNCTIONS
788
+ if namespace is None:
789
+ return func_name in BUILTIN_FUNCTIONS
790
+ return False
791
+
792
+
793
+ def is_intrinsic_variable(namespace: str | None, name: str) -> bool:
794
+ """Check if a name is a known TradingView built-in variable."""
795
+ if namespace is None:
796
+ return name in BUILTIN_VARIABLES
797
+ full = f"{namespace}.{name}"
798
+ if (full in STRATEGY_VARIABLES
799
+ or full in BARSTATE_VARIABLES
800
+ or full in SYMINFO_VARIABLES
801
+ or full in TIMEFRAME_VARIABLES
802
+ or full in BUILTIN_VARIABLES
803
+ or full in DISPLAY_VARIABLES):
804
+ return True
805
+ # e.g. strategy + "opentrades.capital_held" -> strategy.opentrades.capital_held
806
+ if namespace == "strategy" and "." in name:
807
+ return f"strategy.{name}" in STRATEGY_VARIABLES
808
+ return False