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.
- pineforge_codegen/__init__.py +53 -0
- pineforge_codegen/analyzer/__init__.py +60 -0
- pineforge_codegen/analyzer/base.py +1563 -0
- pineforge_codegen/analyzer/call_handlers.py +895 -0
- pineforge_codegen/analyzer/contracts.py +163 -0
- pineforge_codegen/analyzer/diagnostics.py +118 -0
- pineforge_codegen/analyzer/tables.py +204 -0
- pineforge_codegen/analyzer/types.py +250 -0
- pineforge_codegen/ast_nodes.py +293 -0
- pineforge_codegen/codegen/__init__.py +78 -0
- pineforge_codegen/codegen/base.py +1381 -0
- pineforge_codegen/codegen/emit_top.py +875 -0
- pineforge_codegen/codegen/helpers.py +163 -0
- pineforge_codegen/codegen/helpers_syminfo.py +134 -0
- pineforge_codegen/codegen/input.py +189 -0
- pineforge_codegen/codegen/security.py +1564 -0
- pineforge_codegen/codegen/ta.py +298 -0
- pineforge_codegen/codegen/tables.py +613 -0
- pineforge_codegen/codegen/types.py +573 -0
- pineforge_codegen/codegen/visit_call.py +1305 -0
- pineforge_codegen/codegen/visit_expr.py +701 -0
- pineforge_codegen/codegen/visit_stmt.py +729 -0
- pineforge_codegen/errors.py +98 -0
- pineforge_codegen/lexer.py +531 -0
- pineforge_codegen/parser.py +1198 -0
- pineforge_codegen/pragmas.py +117 -0
- pineforge_codegen/signatures.py +808 -0
- pineforge_codegen/support_checker.py +1111 -0
- pineforge_codegen/symbols.py +118 -0
- pineforge_codegen/tokens.py +406 -0
- pineforge_codegen/tv_input_choices.py +86 -0
- pineforge_codegen-0.6.5.dist-info/METADATA +462 -0
- pineforge_codegen-0.6.5.dist-info/RECORD +35 -0
- pineforge_codegen-0.6.5.dist-info/WHEEL +4 -0
- 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
|
+
}
|