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,163 @@
1
+ """Output data structures emitted by the analyzer (its contract with the codegen).
2
+
3
+ These dataclasses form the contract that ``Analyzer.analyze()`` exports
4
+ to the codegen. Pulling them into a sibling module breaks the
5
+ ``base`` <-> ``call_handlers`` import cycle: previously
6
+ ``analyzer/base.py`` imported ``CallHandlers`` from
7
+ ``analyzer/call_handlers.py`` and ``call_handlers.py`` imported
8
+ ``FuncInfo`` / ``TACallSite`` / ``FixnanCallSite`` / ``SecurityCallInfo``
9
+ back from ``base.py``. Putting the dataclasses here gives a strict
10
+ DAG (``call_handlers``, ``base``, ``__init__`` all import from
11
+ ``contracts``; nothing reaches back).
12
+
13
+ The module is named ``contracts.py`` rather than the more obvious
14
+ ``dataclasses.py`` so it does NOT shadow Python's stdlib
15
+ ``dataclasses`` module.
16
+
17
+ Adding new analyzer outputs: append a new ``@dataclass`` here and
18
+ re-export it through ``analyzer/__init__.py``. Do NOT define them on
19
+ ``Analyzer.base`` — that's how the cycle drifts back in.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field
25
+ from typing import Any
26
+
27
+ from ..symbols import TypeSpec
28
+
29
+
30
+ @dataclass
31
+ class TACallSite:
32
+ """One ``ta.*`` function call-site -> one C++ member instance."""
33
+ member_name: str # e.g., "_ta_rsi_1"
34
+ class_name: str # e.g., "ta::RSI"
35
+ ctor_args: list[str] # e.g., ["14"]
36
+ compute_args: list # AST nodes for compute() call args
37
+ returns_tuple: bool # e.g., MACD, supertrend
38
+ node: Any = None # the FuncCall AST node
39
+ is_static: bool = False # true if global scope & arguments are recursively static
40
+
41
+
42
+ @dataclass
43
+ class FuncInfo:
44
+ """Resolved info for a Pine function (or UDT instance method)."""
45
+ name: str
46
+ param_types: list # list of PineType
47
+ return_type: Any # PineType
48
+ node: Any = None # the FuncDef AST node
49
+ returns_tuple: bool = False
50
+ tuple_element_count: int = 0
51
+ # UDT instance methods: name is "TypeName.methodName"; node is synthetic FuncDef.
52
+ is_udt_method: bool = False
53
+ udt_type_name: str | None = None
54
+ # Parallel to ``node.params``; each entry is the AST default expression
55
+ # for that parameter, or ``None`` if no default was declared. Currently
56
+ # populated only for UDT instance methods so codegen can fill in
57
+ # missing args at the call site (see
58
+ # ``data/validation/udt-method-probe-04-default-param``).
59
+ param_defaults: list = field(default_factory=list)
60
+ # When the function's body returns a UDT instance (last expression is
61
+ # ``TypeName.new(...)`` for a user-defined type), this is the UDT
62
+ # struct name. The codegen uses it to (a) emit the function's C++
63
+ # return type as ``Sample`` instead of the generic ``double`` fallback
64
+ # and (b) thread the UDT through to the caller's symbol table so
65
+ # ``Sample s = build_sample(...)`` then ``s.score()`` dispatches
66
+ # correctly. Probe: data/validation/udt-method-probe-20-udt-return-from-func.
67
+ udt_return_type: str | None = None
68
+
69
+
70
+ @dataclass
71
+ class FixnanCallSite:
72
+ """Per-call-site state for ``fixnan(...)`` (one previous-value member each)."""
73
+ member_name: str # e.g., "_prev_fixnan_1"
74
+ pine_type: Any # PineType
75
+
76
+
77
+ @dataclass
78
+ class MutableGlobalInfo:
79
+ """Bookkeeping for a global variable that the codegen may need to shadow inside ``request.security`` evaluators."""
80
+ name: str
81
+ pine_type: Any
82
+ is_series: bool = False
83
+ is_var: bool = False
84
+ decl_node: Any = None
85
+ source_stmts: list = field(default_factory=list)
86
+
87
+
88
+ @dataclass
89
+ class SecurityCallInfo:
90
+ """Resolved metadata for one ``request.security(...)`` /
91
+ ``request.security_lower_tf(...)`` call-site.
92
+
93
+ ``is_lower_tf_array`` distinguishes the two builtins. When true the
94
+ codegen lowers the call to a ``std::vector<T>`` accumulator (one
95
+ element per synthesised lower-TF sub-bar of the current chart bar)
96
+ rather than a scalar ``_req_sec_<id>``; the runtime side rejects
97
+ the call at validation time if the requested TF is not strictly
98
+ finer than the chart's input TF."""
99
+ sec_id: int
100
+ timeframe: Any
101
+ expression: Any
102
+ returns_tuple: bool
103
+ tuple_size: int
104
+ gaps: Any = None
105
+ lookahead: Any = None
106
+ ta_range: Any = None
107
+ depends_on_mutable_globals: bool = False
108
+ mutable_globals: tuple[str, ...] = ()
109
+ is_lower_tf_array: bool = False
110
+
111
+
112
+ @dataclass
113
+ class AnalyzerContext:
114
+ """Output bundle of the analyzer; consumed by the codegen.
115
+
116
+ Keep a single class instead of a dict so consumers can rely on
117
+ typed attribute access. Fields with mutable defaults use
118
+ ``field(default_factory=...)`` so multiple ``Analyzer`` runs
119
+ don't share state."""
120
+ ast: Any # Program node
121
+ symbols: Any # SymbolTable
122
+ ta_call_sites: list = field(default_factory=list)
123
+ series_vars: set = field(default_factory=set)
124
+ series_bar_fields: set = field(default_factory=set)
125
+ var_members: list = field(default_factory=list) # [(name, PineType, init_expr_str)]
126
+ func_infos: list = field(default_factory=list)
127
+ fixnan_sites: list = field(default_factory=list)
128
+ strategy_params: dict = field(default_factory=dict)
129
+ diagnostics: list = field(default_factory=list) # warnings
130
+ filename: str = "<stdin>"
131
+ global_var_decls: list = field(default_factory=list) # [(name, PineType)] non-var global scope vars
132
+ global_expr_map: dict = field(default_factory=dict) # name -> defining AST expr (global, non-var)
133
+ var_member_init_exprs: dict = field(default_factory=dict) # var-member name -> init AST expr
134
+ # Per-call-site TA member cloning for user functions:
135
+ func_ta_ranges: dict = field(default_factory=dict) # func_name -> (start_idx, end_idx)
136
+ func_call_cs_map: dict = field(default_factory=dict) # call_node_id -> (func_name, call_site_index)
137
+ func_call_site_counts: dict = field(default_factory=dict) # func_name -> int
138
+ # UDT / enum definitions:
139
+ udt_defs: dict = field(default_factory=dict) # type_name -> {field_name: PineType}
140
+ enum_defs: dict = field(default_factory=dict) # enum_name -> [member names]
141
+ enum_member_strings: dict = field(default_factory=dict) # enum_name -> [str values]
142
+ # request.security calls (see SecurityCallInfo):
143
+ security_calls: list = field(default_factory=list)
144
+ global_mutable_infos: dict = field(default_factory=dict) # name -> MutableGlobalInfo
145
+ # Per-function var_members + series_vars (used when emitting per-function call-site variants):
146
+ func_var_members: dict = field(default_factory=dict)
147
+ func_series_vars: dict = field(default_factory=dict)
148
+ # var_name -> UDT type name for variables instantiated via TypeName.new(...)
149
+ udt_var_types: dict[str, str] = field(default_factory=dict)
150
+ # var_name -> structured collection/UDT type metadata
151
+ collection_types: dict[str, TypeSpec] = field(default_factory=dict)
152
+ # UDT name -> field_name -> structured type metadata
153
+ udt_field_type_specs: dict[str, dict[str, TypeSpec]] = field(default_factory=dict)
154
+ # ``// @pf-trace name=expr`` pragmas in source order. Populated by
155
+ # :func:`pineforge_codegen.pragmas.extract_pf_trace_pragmas` from
156
+ # the original source text and attached after :class:`Analyzer`
157
+ # finishes (the analyzer never sees pragma expressions because the
158
+ # lexer strips comments). Codegen iterates this list to emit the
159
+ # ``if (trace_enabled_) { trace(...); ... }`` block at the bottom
160
+ # of every ``on_bar()``; an empty list means zero overhead is
161
+ # emitted. Stored as ``list`` (not ``list[PfTracePragma]``) to
162
+ # keep this module free of imports back into ``pragmas.py``.
163
+ pf_trace_pragmas: list = field(default_factory=list)
@@ -0,0 +1,118 @@
1
+ """Error / warning emission and diagnostic-string helpers for the analyzer.
2
+
3
+ The historic ``analyzer.py`` had a small cluster of helpers that
4
+ all dealt with one concern: turning a problem the analyzer noticed
5
+ into a ``Diagnostic`` (warning) or ``CompileError`` (fatal), with
6
+ useful source locations and rough printable forms of expressions.
7
+ This mixin collects them in one place. ``Analyzer`` mixes
8
+ ``DiagnosticsHelper`` in alongside the other analyzer mixins.
9
+
10
+ Mixin contract -- host class must provide the following attributes:
11
+
12
+ - ``self._diagnostics`` (``list[Diagnostic]``): warning sink that
13
+ ``_warn`` appends to. Errors are raised as ``CompileError`` and
14
+ do not pass through this list.
15
+ - ``self._filename`` (``str``): file name used as the fallback
16
+ ``SourceLocation`` when a caller does not pass one.
17
+ - ``self._symbols`` (``SymbolTable``): consulted by
18
+ ``_warn_if_unknown_source_id`` to skip the warning when the
19
+ identifier is a user-defined series variable.
20
+
21
+ The mixin avoids importing from ``base.py`` to stay free of import
22
+ cycles. ``BAR_FIELDS`` and ``tv_input_choices`` come from sibling
23
+ modules, mirroring how the host class consumes them.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from ..ast_nodes import (
29
+ ASTNode, BinOp, BoolLiteral, FuncCall, Identifier, MemberAccess,
30
+ NaLiteral, NumberLiteral, StringLiteral, Subscript, Ternary, UnaryOp,
31
+ )
32
+ from ..errors import CompileError, Diagnostic, Level, Phase, SourceLocation
33
+ from .. import tv_input_choices as tv_in
34
+ from .tables import BAR_FIELDS
35
+
36
+
37
+ class DiagnosticsHelper:
38
+ """Diagnostic emission + location + expression-stringification helpers.
39
+
40
+ Mixed into ``Analyzer``; not meant to be instantiated standalone.
41
+ The methods here are the only path the analyzer uses to record a
42
+ warning or raise a compile error, so keeping them together makes
43
+ the diagnostic surface easy to audit.
44
+ """
45
+
46
+ def _error(self, message: str, loc: SourceLocation | None = None) -> None:
47
+ """Raise a compile error."""
48
+ if loc is None:
49
+ loc = SourceLocation(file=self._filename, line=1, col=1, end_col=1)
50
+ diag = Diagnostic(
51
+ level=Level.ERROR,
52
+ phase=Phase.ANALYZER,
53
+ location=loc,
54
+ message=message,
55
+ )
56
+ raise CompileError([diag])
57
+
58
+ def _warn(self, message: str, loc: SourceLocation | None = None) -> None:
59
+ """Record a warning diagnostic."""
60
+ if loc is None:
61
+ loc = SourceLocation(file=self._filename, line=1, col=1, end_col=1)
62
+ diag = Diagnostic(
63
+ level=Level.WARNING,
64
+ phase=Phase.ANALYZER,
65
+ location=loc,
66
+ message=message,
67
+ )
68
+ self._diagnostics.append(diag)
69
+
70
+ def _input_diag_loc(self, node: FuncCall, expr: ASTNode | None) -> SourceLocation:
71
+ if expr is not None and getattr(expr, "loc", None):
72
+ return expr.loc
73
+ if node.loc:
74
+ return node.loc
75
+ return SourceLocation(file=self._filename, line=1, col=1, end_col=1)
76
+
77
+ def _warn_if_unknown_source_id(
78
+ self, name: str, expr: ASTNode, node: FuncCall,
79
+ ) -> None:
80
+ if name in tv_in.INPUT_SOURCE_SERIES_IDS or name in BAR_FIELDS:
81
+ return
82
+ sym = self._symbols.resolve(name)
83
+ if sym is not None and getattr(sym, "is_series", False):
84
+ return
85
+ loc = self._input_diag_loc(node, expr)
86
+ self._warn(
87
+ f"input defval '{name}' is not a known chart series (open, high, low, close, …); "
88
+ "verify spelling or use a series variable.",
89
+ loc,
90
+ )
91
+
92
+ def _expr_to_str(self, node: ASTNode) -> str:
93
+ """Convert an expression node to a rough string representation."""
94
+ if isinstance(node, NumberLiteral):
95
+ return str(node.value)
96
+ if isinstance(node, StringLiteral):
97
+ return f'"{node.value}"'
98
+ if isinstance(node, BoolLiteral):
99
+ return "true" if node.value else "false"
100
+ if isinstance(node, NaLiteral):
101
+ return "na"
102
+ if isinstance(node, Identifier):
103
+ return node.name
104
+ if isinstance(node, MemberAccess):
105
+ return f"{self._expr_to_str(node.object)}.{node.member}"
106
+ if isinstance(node, BinOp):
107
+ return f"{self._expr_to_str(node.left)} {node.op} {self._expr_to_str(node.right)}"
108
+ if isinstance(node, UnaryOp):
109
+ return f"{node.op}{self._expr_to_str(node.operand)}"
110
+ if isinstance(node, FuncCall):
111
+ args = ", ".join(self._expr_to_str(a) for a in node.args)
112
+ callee_str = self._expr_to_str(node.callee)
113
+ return f"{callee_str}({args})"
114
+ if isinstance(node, Subscript):
115
+ return f"{self._expr_to_str(node.object)}[{self._expr_to_str(node.index)}]"
116
+ if isinstance(node, Ternary):
117
+ return f"{self._expr_to_str(node.condition)} ? {self._expr_to_str(node.true_val)} : {self._expr_to_str(node.false_val)}"
118
+ return "<?>"
@@ -0,0 +1,204 @@
1
+ """Static lookup tables consumed by the analyzer.
2
+
3
+ The historic ``analyzer.py`` carried these module-level tables at the
4
+ top of the file. Pulling them out into a dedicated module mirrors the
5
+ codegen package layout (``codegen/tables.py``) and gives the future
6
+ analyzer mixins a single place to import from while letting
7
+ ``base.py`` stay focused on the ``Analyzer`` class itself.
8
+
9
+ External consumers (``codegen/base.py``, ``support_checker.py``, and
10
+ external tests) import these names through the package facade
11
+ (``from pineforge_codegen.analyzer import …``); that contract is
12
+ preserved by re-exports in ``analyzer/__init__.py``.
13
+
14
+ Naming overlap note: ``BAR_FIELDS`` here is a ``set[str]`` of Pine
15
+ identifiers treated as series-of-bar fields by the *analyzer*. The
16
+ codegen package owns a same-named ``BAR_FIELDS`` ``dict[str, str]``
17
+ that maps those identifiers to C++ accessors. The two never share an
18
+ import path (each lives in its own ``tables.py``), so the name reuse
19
+ is a deliberate, parallel-package convention rather than a collision.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from ..symbols import PineType
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # TA mapping tables
29
+ # ---------------------------------------------------------------------------
30
+
31
+ TA_CLASS_MAP = {
32
+ "sma": "ta::SMA",
33
+ "ema": "ta::EMA",
34
+ "rma": "ta::RMA",
35
+ "rsi": "ta::RSI",
36
+ "atr": "ta::ATR",
37
+ "tr": "ta::TR",
38
+ "macd": "ta::MACD",
39
+ "stoch": "ta::Stoch",
40
+ "crossover": "ta::Crossover",
41
+ "crossunder": "ta::Crossunder",
42
+ "cross": "ta::Cross",
43
+ "highest": "ta::Highest",
44
+ "lowest": "ta::Lowest",
45
+ "change": "ta::Change",
46
+ "supertrend": "ta::Supertrend",
47
+ "dmi": "ta::DMI",
48
+ "sar": "ta::SAR",
49
+ "bb": "ta::BB",
50
+ "kc": "ta::KC",
51
+ "wma": "ta::WMA",
52
+ "hma": "ta::HMA",
53
+ "stdev": "ta::StdDev",
54
+ "pivothigh": "ta::PivotHigh",
55
+ "pivotlow": "ta::PivotLow",
56
+ # Task 6
57
+ "sum": "math::Sum",
58
+ # Task 7 Batch 1
59
+ "linreg": "ta::Linreg",
60
+ "percentrank": "ta::PercentRank",
61
+ "vwma": "ta::VWMA",
62
+ # Task 7 Batch 2
63
+ "mom": "ta::Mom",
64
+ "roc": "ta::ROC",
65
+ "rising": "ta::Rising",
66
+ "falling": "ta::Falling",
67
+ "cci": "ta::CCI",
68
+ "cum": "ta::Cum",
69
+ # Task 7 Batch 3
70
+ "variance": "ta::Variance",
71
+ "median": "ta::Median",
72
+ "highestbars": "ta::HighestBars",
73
+ "lowestbars": "ta::LowestBars",
74
+ # Batch 4 — remaining TA functions
75
+ "alma": "ta::ALMA",
76
+ "swma": "ta::SWMA",
77
+ "mfi": "ta::MFI",
78
+ "cmo": "ta::CMO",
79
+ "tsi": "ta::TSI",
80
+ "wpr": "ta::WPR",
81
+ "cog": "ta::COG",
82
+ "bbw": "ta::BBW",
83
+ "kcw": "ta::KCW",
84
+ "barssince": "ta::BarsSince",
85
+ "valuewhen": "ta::ValueWhen",
86
+ "correlation": "ta::Correlation",
87
+ "percentile_nearest_rank": "ta::PercentileNearestRank",
88
+ "percentile_linear_interpolation": "ta::PercentileLinearInterpolation",
89
+ # Task 5 — Volume indicators + statistical
90
+ "vwap": "ta::VWAP",
91
+ # 3-arg form: ta.vwap(source, anchor, stdev_mult) → tuple [vwap, upper, lower]
92
+ "vwap_bands": "ta::VWAPBands",
93
+ "obv": "ta::OBV",
94
+ "accdist": "ta::AccDist",
95
+ "nvi": "ta::NVI",
96
+ "pvi": "ta::PVI",
97
+ "pvt": "ta::PVT",
98
+ "wad": "ta::WAD",
99
+ "wvad": "ta::WVAD",
100
+ "iii": "ta::III",
101
+ "mode": "ta::Mode",
102
+ "range": "ta::Range",
103
+ "dev": "ta::Dev",
104
+ "max": "ta::AllTimeMax",
105
+ "min": "ta::AllTimeMin",
106
+ "rci": "ta::RCI",
107
+ }
108
+
109
+ # Which arg index is the period/length (goes to constructor)
110
+ TA_PERIOD_ARG = {
111
+ "sma": 1, "ema": 1, "rma": 1, "rsi": 1, "atr": 0,
112
+ "highest": 1, "lowest": 1, "change": 1,
113
+ "wma": 1, "hma": 1, "stdev": 1,
114
+ # Task 6
115
+ "sum": 1,
116
+ # Task 7 Batch 1
117
+ "linreg": 1, "percentrank": 1, "vwma": 1,
118
+ # Task 7 Batch 2
119
+ "mom": 1, "roc": 1, "rising": 1, "falling": 1, "cci": 1,
120
+ # cum has no period arg — handled in TA_NO_CTOR
121
+ # Task 7 Batch 3
122
+ "variance": 1, "median": 1, "highestbars": 1, "lowestbars": 1,
123
+ # Batch 4
124
+ "cmo": 1, "cog": 1, "correlation": 2,
125
+ "percentile_nearest_rank": 1, "percentile_linear_interpolation": 1,
126
+ # Task 5
127
+ "mode": 1, "range": 1, "dev": 1,
128
+ "rci": 1,
129
+ }
130
+
131
+ # Functions that return tuples
132
+ TA_TUPLE_RETURNS = {"macd", "supertrend", "dmi", "bb", "kc", "vwap_bands"}
133
+
134
+ # Functions with multiple constructor args
135
+ TA_MULTI_CTOR = {
136
+ "macd": [1, 2, 3], # fast, slow, signal
137
+ "stoch": [3], # length (high, low are compute args)
138
+ "supertrend": [0, 1], # factor, atr_period
139
+ "dmi": [0, 1], # di_length, adx_smoothing
140
+ "bb": [1, 2], # length, mult
141
+ "kc": [1, 2], # length, mult
142
+ "sar": [0, 1, 2], # start, increment, maximum
143
+ "pivothigh": [0, 1], # left_bars, right_bars
144
+ "pivotlow": [0, 1], # left_bars, right_bars
145
+ # Batch 4
146
+ "alma": [1, 2, 3], # length, offset, sigma
147
+ "mfi": [1], # length (src is compute arg, vol implicit)
148
+ # Pine signature: ta.tsi(source, short_length, long_length) — positions
149
+ # 1 and 2 carry the lengths that initialize the four nested EMAs;
150
+ # source flows to compute() at position 0. The previous `[0, 1]` was
151
+ # copy-paste off-by-one (caught by a TV-anchored sweep that found
152
+ # engine TSI returning 0.0 for every bar because compute() was being
153
+ # called with the long_length integer literal as the "source").
154
+ "tsi": [1, 2], # short_length, long_length
155
+ "wpr": [0], # length
156
+ "bbw": [1, 2], # length, mult
157
+ "kcw": [1, 2], # length, mult
158
+ "tr": [0], # handle_na (compile-time bool)
159
+ }
160
+
161
+ # No-state functions (no constructor args, stateless or self-contained)
162
+ TA_NO_CTOR = {"crossover", "crossunder", "cross", "cum", "swma", "barssince", "valuewhen",
163
+ "max", "min",
164
+ "obv", "accdist", "nvi", "pvi", "pvt", "wad", "wvad", "iii", "vwap"}
165
+
166
+ # TA parameter names are now in signatures.py (sigs.get_param_names)
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Built-in variables
171
+ # ---------------------------------------------------------------------------
172
+
173
+ BUILTIN_VARS = {
174
+ # Price
175
+ "open": PineType.FLOAT, "high": PineType.FLOAT, "low": PineType.FLOAT,
176
+ "close": PineType.FLOAT, "volume": PineType.FLOAT,
177
+ # Derived price
178
+ "hl2": PineType.FLOAT, "hlc3": PineType.FLOAT, "hlcc4": PineType.FLOAT,
179
+ "ohlc4": PineType.FLOAT,
180
+ # Bar info
181
+ "bar_index": PineType.INT, "time": PineType.INT, "time_close": PineType.INT,
182
+ "last_bar_index": PineType.INT, "last_bar_time": PineType.INT, "timenow": PineType.INT,
183
+ "time_tradingday": PineType.INT,
184
+ # Time & date
185
+ "hour": PineType.INT, "minute": PineType.INT, "second": PineType.INT,
186
+ "dayofmonth": PineType.INT, "dayofweek": PineType.INT,
187
+ "month": PineType.INT, "year": PineType.INT, "weekofyear": PineType.INT,
188
+ # Special
189
+ "na": PineType.NA,
190
+ }
191
+
192
+ # Bar-related price fields (these are series that map to bar.* in C++)
193
+ BAR_FIELDS = {"open", "high", "low", "close", "volume", "hl2", "hlc3", "ohlc4", "hlcc4"}
194
+
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # Skip functions (visual -- parse but don't generate code)
198
+ # ---------------------------------------------------------------------------
199
+
200
+ SKIP_FUNCS = {
201
+ "plot", "plotshape", "plotchar", "plotcandle", "fill", "hline",
202
+ "bgcolor", "barcolor", "alert", "alertcondition",
203
+ "max_bars_back",
204
+ }