opencode-pine2pyne 0.1.0__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.
- opencode_pine2pyne-0.1.0.dist-info/METADATA +123 -0
- opencode_pine2pyne-0.1.0.dist-info/RECORD +23 -0
- opencode_pine2pyne-0.1.0.dist-info/WHEEL +5 -0
- opencode_pine2pyne-0.1.0.dist-info/entry_points.txt +2 -0
- opencode_pine2pyne-0.1.0.dist-info/licenses/LICENSE +201 -0
- opencode_pine2pyne-0.1.0.dist-info/top_level.txt +1 -0
- pine2pyne/README.md +178 -0
- pine2pyne/TRANSPILER_BEST_PRACTICES.md +425 -0
- pine2pyne/TRANSPILER_USAGE.md +264 -0
- pine2pyne/__init__.py +86 -0
- pine2pyne/__main__.py +12 -0
- pine2pyne/ast_nodes.py +352 -0
- pine2pyne/cli.py +212 -0
- pine2pyne/codegen.py +836 -0
- pine2pyne/errors.py +48 -0
- pine2pyne/import_resolver.py +407 -0
- pine2pyne/lexer.py +546 -0
- pine2pyne/parser.py +1510 -0
- pine2pyne/pine_builtins.py +464 -0
- pine2pyne/symbol_table.py +156 -0
- pine2pyne/tokens.py +143 -0
- pine2pyne/transformer.py +2327 -0
- pine2pyne/type_inference.py +328 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mapping of Pine Script built-in functions, variables, and constants to PyneCore equivalents.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Functions that need to be renamed
|
|
8
|
+
FUNCTION_RENAMES: Dict[str, str] = {
|
|
9
|
+
'array.from': 'array.from_items', # 'from' is Python keyword
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# Module renames
|
|
13
|
+
MODULE_RENAMES: Dict[str, str] = {
|
|
14
|
+
'str': 'string', # str is Python built-in
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Type renames (Pine Script -> Python/PyneCore)
|
|
18
|
+
TYPE_RENAMES: Dict[str, str] = {
|
|
19
|
+
# Drawing types
|
|
20
|
+
'line': 'Line',
|
|
21
|
+
'label': 'Label',
|
|
22
|
+
'box': 'Box',
|
|
23
|
+
'table': 'Table',
|
|
24
|
+
'linefill': 'LineFill',
|
|
25
|
+
'polyline': 'Polyline',
|
|
26
|
+
'color': 'Color',
|
|
27
|
+
'chart.point': 'ChartPoint',
|
|
28
|
+
|
|
29
|
+
# Data structure types
|
|
30
|
+
'matrix': 'Matrix',
|
|
31
|
+
|
|
32
|
+
# Basic types (in type hints)
|
|
33
|
+
'string': 'str',
|
|
34
|
+
'int': 'int',
|
|
35
|
+
'float': 'float',
|
|
36
|
+
'bool': 'bool',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Built-in variables that are always Series
|
|
40
|
+
SERIES_VARIABLES: Set[str] = {
|
|
41
|
+
'open', 'high', 'low', 'close', 'volume',
|
|
42
|
+
'hl2', 'hlc3', 'ohlc4', 'hlcc4',
|
|
43
|
+
'bar_index', 'last_bar_index', 'last_bar_time',
|
|
44
|
+
'time', 'time_close', 'time_tradingday', 'timenow',
|
|
45
|
+
'dayofmonth', 'dayofweek', 'hour', 'minute', 'second', 'month', 'weekofyear', 'year',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Constants that should be preserved as-is
|
|
49
|
+
CONSTANTS: Set[str] = {
|
|
50
|
+
# Strategy constants
|
|
51
|
+
'strategy.long',
|
|
52
|
+
'strategy.short',
|
|
53
|
+
'strategy.commission.percent',
|
|
54
|
+
'strategy.commission.cash_per_contract',
|
|
55
|
+
'strategy.commission.cash_per_order',
|
|
56
|
+
'strategy.percent',
|
|
57
|
+
'strategy.fixed',
|
|
58
|
+
'strategy.cash',
|
|
59
|
+
'strategy.percent_of_equity',
|
|
60
|
+
'strategy.closedtrades',
|
|
61
|
+
'strategy.opentrades',
|
|
62
|
+
|
|
63
|
+
# Color constants
|
|
64
|
+
'color.red', 'color.green', 'color.blue', 'color.white', 'color.black',
|
|
65
|
+
'color.yellow', 'color.orange', 'color.purple', 'color.gray',
|
|
66
|
+
'color.silver', 'color.maroon', 'color.fuchsia', 'color.lime',
|
|
67
|
+
'color.olive', 'color.navy', 'color.teal', 'color.aqua',
|
|
68
|
+
|
|
69
|
+
# Plot styles (need remapping to plot_style module)
|
|
70
|
+
'plot.style_line',
|
|
71
|
+
'plot.style_stepline',
|
|
72
|
+
'plot.style_histogram',
|
|
73
|
+
'plot.style_cross',
|
|
74
|
+
'plot.style_area',
|
|
75
|
+
'plot.style_columns',
|
|
76
|
+
'plot.style_circles',
|
|
77
|
+
'plot.style_linebr',
|
|
78
|
+
'plot.style_steplinebr',
|
|
79
|
+
|
|
80
|
+
# Barstate
|
|
81
|
+
'barstate.isfirst',
|
|
82
|
+
'barstate.islast',
|
|
83
|
+
'barstate.ishistory',
|
|
84
|
+
'barstate.isrealtime',
|
|
85
|
+
'barstate.isnew',
|
|
86
|
+
'barstate.isconfirmed',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Plot style constants need special handling
|
|
90
|
+
PLOT_STYLE_REMAP: Dict[str, str] = {
|
|
91
|
+
'plot.style_line': 'plot_style.style_line',
|
|
92
|
+
'plot.style_stepline': 'plot_style.style_stepline',
|
|
93
|
+
'plot.style_histogram': 'plot_style.style_histogram',
|
|
94
|
+
'plot.style_cross': 'plot_style.style_cross',
|
|
95
|
+
'plot.style_area': 'plot_style.style_area',
|
|
96
|
+
'plot.style_columns': 'plot_style.style_columns',
|
|
97
|
+
'plot.style_circles': 'plot_style.style_circles',
|
|
98
|
+
'plot.style_linebr': 'plot_style.style_linebr',
|
|
99
|
+
'plot.style_steplinebr': 'plot_style.style_steplinebr',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Modules that should be imported from pynecore.lib
|
|
103
|
+
# Synced with https://github.com/PyneSys/pynecore/tree/main/src/pynecore/lib
|
|
104
|
+
PYNECORE_LIB_MODULES: Set[str] = {
|
|
105
|
+
# Core modules (submodules as .py files in pynecore/lib/)
|
|
106
|
+
'script', 'input', 'plot', 'color', 'ta', 'math', 'strategy',
|
|
107
|
+
'array', 'matrix', 'map', 'string',
|
|
108
|
+
'timeframe', 'runtime', 'alert', 'label', 'line', 'box', 'table',
|
|
109
|
+
'linefill', 'polyline', 'log', 'barstate', 'session', 'syminfo',
|
|
110
|
+
'adjustment', 'barmerge', 'chart', 'dividends', 'earnings',
|
|
111
|
+
# Higher-timeframe / other-symbol data access. Present in upstream PyneCore
|
|
112
|
+
# (lib/request.py + lib/ticker.py); without them here the transpiler emits
|
|
113
|
+
# request.*/ticker.* calls it never imports -> NameError at runtime.
|
|
114
|
+
'request', 'ticker',
|
|
115
|
+
# Other upstream lib modules that were likewise never imported. Found by
|
|
116
|
+
# diffing pynecore/lib/*.py against this set — 'splits' is used by the
|
|
117
|
+
# request.splits samples, 'dayofweek' by session/date logic.
|
|
118
|
+
'dayofweek', 'splits', 'footprint', 'pivotpointtype', 'volume_row',
|
|
119
|
+
# Standalone functions (defined in pynecore/lib/__init__.py)
|
|
120
|
+
'currency', 'timestamp', 'max_bars_back',
|
|
121
|
+
# Plotting and coloring functions
|
|
122
|
+
'bgcolor', 'barcolor', 'alertcondition', 'plotarrow', 'plotbar', 'plotcandle',
|
|
123
|
+
'plotchar', 'plotshape', 'fill',
|
|
124
|
+
# Utility functions
|
|
125
|
+
'na', 'nz', 'fixnan',
|
|
126
|
+
# Constant namespaces
|
|
127
|
+
'xloc', 'yloc', 'position', 'size', 'shape', 'location',
|
|
128
|
+
'extend', 'font', 'text', 'display', 'scale', 'hline', 'format',
|
|
129
|
+
'order',
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Drawing and structural types that should be imported from pynecore.types
|
|
133
|
+
DRAWING_TYPES: Set[str] = {
|
|
134
|
+
'Line', 'Label', 'Box', 'Table', 'LineFill', 'Polyline', 'Color', 'Matrix',
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Chart types that should be imported from pynecore.types.chart
|
|
138
|
+
CHART_TYPES: Set[str] = {
|
|
139
|
+
'ChartPoint',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# PyneCore object method transformations
|
|
143
|
+
# Maps method names to the module that should be used for the function call
|
|
144
|
+
# Format: method_name -> (module_name, requires_transformation)
|
|
145
|
+
PYNECORE_METHOD_TRANSFORMS: dict[str, str] = {
|
|
146
|
+
# Matrix methods
|
|
147
|
+
'add_row': 'matrix',
|
|
148
|
+
'add_col': 'matrix',
|
|
149
|
+
'columns': 'matrix',
|
|
150
|
+
'rows': 'matrix',
|
|
151
|
+
'fill': 'matrix',
|
|
152
|
+
'avg': 'matrix',
|
|
153
|
+
'max': 'matrix',
|
|
154
|
+
'min': 'matrix',
|
|
155
|
+
'median': 'matrix',
|
|
156
|
+
'mode': 'matrix',
|
|
157
|
+
'sum': 'matrix',
|
|
158
|
+
'concat': 'matrix',
|
|
159
|
+
'copy': 'matrix', # Also used by labels/lines but context-dependent
|
|
160
|
+
'det': 'matrix',
|
|
161
|
+
'diff': 'matrix',
|
|
162
|
+
'eigenvalues': 'matrix',
|
|
163
|
+
'eigenvectors': 'matrix',
|
|
164
|
+
'inv': 'matrix',
|
|
165
|
+
'kron': 'matrix',
|
|
166
|
+
'mult': 'matrix',
|
|
167
|
+
'pinv': 'matrix',
|
|
168
|
+
'pow': 'matrix',
|
|
169
|
+
'rank': 'matrix',
|
|
170
|
+
'remove_col': 'matrix',
|
|
171
|
+
'remove_row': 'matrix',
|
|
172
|
+
'reshape': 'matrix',
|
|
173
|
+
'reverse': 'matrix',
|
|
174
|
+
'col': 'matrix',
|
|
175
|
+
'row': 'matrix',
|
|
176
|
+
'set': 'matrix', # Also used by labels/lines
|
|
177
|
+
'sort': 'matrix',
|
|
178
|
+
'submatrix': 'matrix',
|
|
179
|
+
'swap_columns': 'matrix',
|
|
180
|
+
'swap_rows': 'matrix',
|
|
181
|
+
'trace': 'matrix',
|
|
182
|
+
'transpose': 'matrix',
|
|
183
|
+
|
|
184
|
+
# Map methods
|
|
185
|
+
'put': 'map',
|
|
186
|
+
'put_all': 'map',
|
|
187
|
+
'get': 'map', # Also used by arrays
|
|
188
|
+
'contains': 'map',
|
|
189
|
+
'remove': 'map',
|
|
190
|
+
'clear': 'map',
|
|
191
|
+
'keys': 'map',
|
|
192
|
+
'values': 'map',
|
|
193
|
+
'size': 'map', # Also used by arrays
|
|
194
|
+
|
|
195
|
+
# Array methods (handled differently - array methods stay as is mostly)
|
|
196
|
+
|
|
197
|
+
# Label methods
|
|
198
|
+
'set_x': 'label',
|
|
199
|
+
'set_y': 'label',
|
|
200
|
+
'set_xy': 'label',
|
|
201
|
+
'set_text': 'label',
|
|
202
|
+
'set_color': 'label',
|
|
203
|
+
'set_textcolor': 'label',
|
|
204
|
+
'set_size': 'label',
|
|
205
|
+
'set_style': 'label',
|
|
206
|
+
'set_textalign': 'label',
|
|
207
|
+
'set_tooltip': 'label',
|
|
208
|
+
'get_x': 'label',
|
|
209
|
+
'get_y': 'label',
|
|
210
|
+
'get_text': 'label',
|
|
211
|
+
'delete': 'label', # Also line, box
|
|
212
|
+
|
|
213
|
+
# Line methods
|
|
214
|
+
'set_x1': 'line',
|
|
215
|
+
'set_y1': 'line',
|
|
216
|
+
'set_x2': 'line',
|
|
217
|
+
'set_y2': 'line',
|
|
218
|
+
'set_xy1': 'line',
|
|
219
|
+
'set_xy2': 'line',
|
|
220
|
+
'set_extend': 'line',
|
|
221
|
+
'set_width': 'line',
|
|
222
|
+
'get_x1': 'line',
|
|
223
|
+
'get_y1': 'line',
|
|
224
|
+
'get_x2': 'line',
|
|
225
|
+
'get_y2': 'line',
|
|
226
|
+
'get_price': 'line',
|
|
227
|
+
|
|
228
|
+
# Box methods
|
|
229
|
+
'set_left': 'box',
|
|
230
|
+
'set_right': 'box',
|
|
231
|
+
'set_top': 'box',
|
|
232
|
+
'set_bottom': 'box',
|
|
233
|
+
'set_lefttop': 'box',
|
|
234
|
+
'set_rightbottom': 'box',
|
|
235
|
+
'set_border_color': 'box',
|
|
236
|
+
'set_border_width': 'box',
|
|
237
|
+
'set_border_style': 'box',
|
|
238
|
+
'set_bgcolor': 'box',
|
|
239
|
+
'set_text': 'box',
|
|
240
|
+
'set_text_color': 'box',
|
|
241
|
+
'set_text_size': 'box',
|
|
242
|
+
'get_left': 'box',
|
|
243
|
+
'get_right': 'box',
|
|
244
|
+
'get_top': 'box',
|
|
245
|
+
'get_bottom': 'box',
|
|
246
|
+
|
|
247
|
+
# Table methods
|
|
248
|
+
'cell': 'table',
|
|
249
|
+
'merge_cells': 'table',
|
|
250
|
+
'set_position': 'table',
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Canonical module-to-methods sets for heuristic method → module resolution.
|
|
254
|
+
# Used when the transformer cannot determine the type from symbol table and
|
|
255
|
+
# must infer the module from the method name alone.
|
|
256
|
+
# Built as the UNION of all inline set definitions in transformer.py.
|
|
257
|
+
|
|
258
|
+
LABEL_METHODS: set[str] = {
|
|
259
|
+
'set_x', 'set_y', 'set_xy', 'set_text', 'set_color', 'set_textcolor',
|
|
260
|
+
'set_size', 'set_style', 'set_textalign', 'set_tooltip',
|
|
261
|
+
'get_x', 'get_y', 'get_text', 'copy', 'delete',
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
LINE_METHODS: set[str] = {
|
|
265
|
+
'set_x1', 'set_y1', 'set_x2', 'set_y2', 'set_xy1', 'set_xy2',
|
|
266
|
+
'set_extend', 'set_width', 'set_xloc',
|
|
267
|
+
'get_x1', 'get_y1', 'get_x2', 'get_y2', 'get_price', 'copy', 'delete',
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
BOX_METHODS: set[str] = {
|
|
271
|
+
'set_left', 'set_right', 'set_top', 'set_bottom',
|
|
272
|
+
'set_lefttop', 'set_rightbottom',
|
|
273
|
+
'set_border_color', 'set_border_width', 'set_border_style',
|
|
274
|
+
'set_bgcolor', 'set_text', 'set_text_color', 'set_text_size',
|
|
275
|
+
'get_left', 'get_right', 'get_top', 'get_bottom', 'copy', 'delete',
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
TABLE_METHODS: set[str] = {
|
|
279
|
+
'cell', 'merge_cells', 'set_position', 'cell_set_text',
|
|
280
|
+
# Remaining table.py members whose first argument is a Table. The cell_set_*
|
|
281
|
+
# methods are table-unique, so method-style calls (`myTable.cell_set_*(...)`)
|
|
282
|
+
# resolve unambiguously; the set_border_*/set_frame_* setters are table-only
|
|
283
|
+
# too. Without these, a method-style call was left unrewritten and failed with
|
|
284
|
+
# e.g. `'Table' object has no attribute 'cell_set_text_formatting'`.
|
|
285
|
+
'cell_set_bgcolor', 'cell_set_height', 'cell_set_text_color',
|
|
286
|
+
'cell_set_text_font_family', 'cell_set_text_formatting', 'cell_set_text_halign',
|
|
287
|
+
'cell_set_text_size', 'cell_set_text_valign', 'cell_set_tooltip', 'cell_set_width',
|
|
288
|
+
'set_bgcolor', 'set_border_color', 'set_border_width',
|
|
289
|
+
'set_frame_color', 'set_frame_width',
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
MAP_METHODS: set[str] = {'put', 'put_all', 'get', 'remove', 'clear', 'keys', 'values', 'size', 'contains'}
|
|
293
|
+
|
|
294
|
+
MATRIX_METHODS: set[str] = {
|
|
295
|
+
'add_row', 'add_col', 'get', 'set',
|
|
296
|
+
# Matrix-UNIQUE members of lib/matrix.py (no array/map/drawing collision), so
|
|
297
|
+
# method-style calls like `m.rows()`, `m.columns()`, `t.transpose()` route to
|
|
298
|
+
# the matrix module instead of hitting a Matrix property/int with `()`. The
|
|
299
|
+
# AMBIGUOUS names shared with arrays (concat, sort, copy, get/set, fill,
|
|
300
|
+
# reverse, sum, max, min, avg, median, mode, delete) are deliberately NOT
|
|
301
|
+
# added here — they need the type-aware resolver (see R7/R11) since the
|
|
302
|
+
# unknown-type name-heuristic gives arrays priority.
|
|
303
|
+
'col', 'columns', 'det', 'diff', 'eigenvalues', 'eigenvectors',
|
|
304
|
+
'elements_count', 'inv', 'is_antidiagonal', 'is_antisymmetric', 'is_binary',
|
|
305
|
+
'is_diagonal', 'is_identity', 'is_square', 'is_stochastic', 'is_symmetric',
|
|
306
|
+
'is_triangular', 'is_zero', 'kron', 'mult', 'pinv', 'pow', 'rank',
|
|
307
|
+
'remove_col', 'remove_row', 'reshape', 'row', 'rows', 'submatrix',
|
|
308
|
+
'swap_columns', 'swap_rows', 'trace', 'transpose',
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# Array-unique methods that unambiguously map to the array module.
|
|
312
|
+
# Does NOT include shared methods (get, set, size, etc.) to avoid
|
|
313
|
+
# misresolving map/matrix calls when type info is unavailable.
|
|
314
|
+
ARRAY_METHODS: set[str] = {
|
|
315
|
+
'push', 'pop', 'shift', 'unshift', 'slice', 'insert', 'indexof', 'lastindexof',
|
|
316
|
+
'includes', 'sort', 'sort_indices', 'reverse', 'concat', 'first', 'last',
|
|
317
|
+
'join', 'percentile_linear_interpolation', 'percentile_nearest_rank', 'percentrank',
|
|
318
|
+
'abs', 'binary_search', 'binary_search_leftmost', 'binary_search_rightmost',
|
|
319
|
+
'every', 'some', 'range', 'standardize', 'stdev', 'variance', 'covariance',
|
|
320
|
+
'fill',
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# Map-unique methods (methods that ONLY belong to map, not array)
|
|
324
|
+
MAP_UNIQUE_METHODS: set[str] = {'put', 'put_all', 'keys', 'values', 'contains'}
|
|
325
|
+
|
|
326
|
+
# Methods shared between array, map, and matrix.
|
|
327
|
+
# These can only be resolved via type-aware path (symbol table lookup).
|
|
328
|
+
# In heuristic fallback, prefer array since it's the most common collection.
|
|
329
|
+
SHARED_COLLECTION_METHODS: set[str] = {
|
|
330
|
+
'get', 'set', 'size', 'clear', 'remove', 'copy',
|
|
331
|
+
'avg', 'min', 'max', 'sum', 'median', 'mode',
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def get_function_name(pine_func: str) -> str:
|
|
336
|
+
"""Get the PyneCore function name for a Pine Script function."""
|
|
337
|
+
return FUNCTION_RENAMES.get(pine_func, pine_func)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def get_module_name(pine_module: str) -> str:
|
|
341
|
+
"""Get the PyneCore module name for a Pine Script module."""
|
|
342
|
+
return MODULE_RENAMES.get(pine_module, pine_module)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def get_type_name(pine_type: str) -> str:
|
|
346
|
+
"""Get the Python/PyneCore type name for a Pine Script type."""
|
|
347
|
+
# Strip .new suffix from UDT types (e.g., TradeStats.new -> TradeStats)
|
|
348
|
+
if '.new' in pine_type and pine_type and pine_type[0].isupper():
|
|
349
|
+
pine_type = pine_type.replace('.new', '')
|
|
350
|
+
return TYPE_RENAMES.get(pine_type, pine_type)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def is_series_variable(name: str) -> bool:
|
|
354
|
+
"""Check if a variable is a built-in Series variable."""
|
|
355
|
+
return name in SERIES_VARIABLES
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def is_constant(name: str) -> bool:
|
|
359
|
+
"""Check if a name is a built-in constant."""
|
|
360
|
+
return name in CONSTANTS
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def needs_plot_style_remap(name: str) -> bool:
|
|
364
|
+
"""Check if a name needs plot_style module remapping."""
|
|
365
|
+
return name in PLOT_STYLE_REMAP
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_plot_style_remap(name: str) -> str:
|
|
369
|
+
"""Get the remapped plot_style constant name."""
|
|
370
|
+
return PLOT_STYLE_REMAP.get(name, name)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
# Input functions that extract to main() parameters
|
|
374
|
+
INPUT_FUNCTIONS: Set[str] = {
|
|
375
|
+
'input.int',
|
|
376
|
+
'input.float',
|
|
377
|
+
'input.bool',
|
|
378
|
+
'input.string',
|
|
379
|
+
'input.color',
|
|
380
|
+
'input.source',
|
|
381
|
+
'input.timeframe',
|
|
382
|
+
'input.session',
|
|
383
|
+
'input.symbol',
|
|
384
|
+
'input.price',
|
|
385
|
+
'input.time',
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def is_input_function(func_name: str) -> bool:
|
|
390
|
+
"""Check if function is an input.* function."""
|
|
391
|
+
return func_name in INPUT_FUNCTIONS
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# Pine Script input.* positional parameter names (after defval, title).
|
|
395
|
+
# Pine Script allows these as positional; PyneCore requires keyword-only.
|
|
396
|
+
INPUT_POSITIONAL_PARAMS: Dict[str, list[str]] = {
|
|
397
|
+
'input.int': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'options'],
|
|
398
|
+
'input.float': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'options'],
|
|
399
|
+
'input.bool': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
400
|
+
'input.string': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
401
|
+
'input.color': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
402
|
+
'input.timeframe': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
403
|
+
'input.session': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
404
|
+
'input.symbol': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
405
|
+
'input.source': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
406
|
+
'input.price': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
407
|
+
'input.time': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# Python reserved words that cannot be used as function/variable names.
|
|
412
|
+
PYTHON_RESERVED_WORDS: Set[str] = {
|
|
413
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
|
|
414
|
+
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
|
|
415
|
+
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
|
|
416
|
+
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
|
|
417
|
+
'try', 'while', 'with', 'yield',
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def sanitize_identifier(name: str) -> str:
|
|
422
|
+
"""Rename Python reserved words used as identifiers by appending underscore."""
|
|
423
|
+
if name in PYTHON_RESERVED_WORDS:
|
|
424
|
+
return f'{name}_'
|
|
425
|
+
return name
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# TA functions that return Series
|
|
429
|
+
TA_FUNCTIONS: Set[str] = {
|
|
430
|
+
'ta.sma', 'ta.ema', 'ta.wma', 'ta.vwma', 'ta.swma', 'ta.alma',
|
|
431
|
+
'ta.rma', 'ta.hma', 'ta.linreg', 'ta.rsi', 'ta.macd',
|
|
432
|
+
'ta.stoch', 'ta.cci', 'ta.mfi', 'ta.obv', 'ta.sar',
|
|
433
|
+
'ta.atr', 'ta.tr', 'ta.bb', 'ta.bbw', 'ta.kc', 'ta.kcw',
|
|
434
|
+
'ta.dmi', 'ta.adx', 'ta.supertrend', 'ta.highest', 'ta.lowest',
|
|
435
|
+
'ta.valuewhen', 'ta.barssince', 'ta.change', 'ta.roc',
|
|
436
|
+
'ta.mom', 'ta.cum', 'ta.dev', 'ta.stdev', 'ta.variance',
|
|
437
|
+
'ta.correlation', 'ta.median', 'ta.mode', 'ta.range',
|
|
438
|
+
'ta.cog', 'ta.percentile_linear_interpolation',
|
|
439
|
+
'ta.percentile_nearest_rank', 'ta.percentrank', 'ta.pivothigh',
|
|
440
|
+
'ta.pivotlow', 'ta.rising', 'ta.falling', 'ta.cross', 'ta.crossover',
|
|
441
|
+
'ta.crossunder',
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def is_ta_function(func_name: str) -> bool:
|
|
446
|
+
"""Check if function is a ta.* function."""
|
|
447
|
+
return func_name in TA_FUNCTIONS
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# Pine Script built-in "variable-functions": used without () in Pine Script
|
|
451
|
+
# but require () in PyneCore because they're implemented as functions.
|
|
452
|
+
# Values are the default arguments to pass when auto-calling.
|
|
453
|
+
TA_VARIABLE_FUNCTIONS: Dict[str, str] = {
|
|
454
|
+
'ta.vwap': '(high + low + close) / 3', # default source is hlc3
|
|
455
|
+
'ta.tr': '', # no args needed
|
|
456
|
+
'ta.accdist': '',
|
|
457
|
+
'ta.obv': '',
|
|
458
|
+
'ta.iii': '',
|
|
459
|
+
'ta.nvi': '',
|
|
460
|
+
'ta.pvi': '',
|
|
461
|
+
'ta.pvt': '',
|
|
462
|
+
'ta.wad': '',
|
|
463
|
+
'ta.wvad': '',
|
|
464
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Symbol table for tracking variable declarations and scopes.
|
|
3
|
+
|
|
4
|
+
Used by the transformer to determine variable types and manage scope.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Dict, List, Optional, Set
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VariableKind(Enum):
|
|
11
|
+
"""Classification of variable kinds in Pine Script."""
|
|
12
|
+
VAR = "var" # Persistent variable (var keyword)
|
|
13
|
+
VARIP = "varip" # Persistent intrabar variable
|
|
14
|
+
INPUT = "input" # Input parameter
|
|
15
|
+
SERIES = "series" # Series variable (default for globals)
|
|
16
|
+
SIMPLE = "simple" # Simple (non-series) variable
|
|
17
|
+
FUNCTION = "function" # Function name
|
|
18
|
+
PARAMETER = "parameter" # Function parameter
|
|
19
|
+
TYPE = "type" # User-defined type (UDT) declaration
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Symbol:
|
|
23
|
+
"""Represents a symbol (variable, function) in the symbol table."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
name: str,
|
|
28
|
+
kind: VariableKind,
|
|
29
|
+
type_hint: Optional[str] = None,
|
|
30
|
+
is_global: bool = False,
|
|
31
|
+
line: int = 0,
|
|
32
|
+
column: int = 0,
|
|
33
|
+
):
|
|
34
|
+
self.name = name
|
|
35
|
+
self.kind = kind
|
|
36
|
+
self.type_hint = type_hint
|
|
37
|
+
self.is_global = is_global
|
|
38
|
+
self.line = line
|
|
39
|
+
self.column = column
|
|
40
|
+
self.is_series = False # Will be set by type inference
|
|
41
|
+
self.is_indexed = False # True if variable is indexed with [n]
|
|
42
|
+
self.is_from_ta_function = False # True if assigned from ta.* function
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Scope:
|
|
46
|
+
"""Represents a lexical scope."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, parent: Optional['Scope'] = None):
|
|
49
|
+
self.parent = parent
|
|
50
|
+
self.symbols: Dict[str, Symbol] = {}
|
|
51
|
+
|
|
52
|
+
def define(self, symbol: Symbol) -> None:
|
|
53
|
+
"""Define a symbol in this scope."""
|
|
54
|
+
self.symbols[symbol.name] = symbol
|
|
55
|
+
|
|
56
|
+
def lookup(self, name: str) -> Optional[Symbol]:
|
|
57
|
+
"""Look up a symbol in this scope or parent scopes."""
|
|
58
|
+
if name in self.symbols:
|
|
59
|
+
return self.symbols[name]
|
|
60
|
+
if self.parent:
|
|
61
|
+
return self.parent.lookup(name)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def lookup_local(self, name: str) -> Optional[Symbol]:
|
|
65
|
+
"""Look up a symbol only in this scope (not parent)."""
|
|
66
|
+
return self.symbols.get(name)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SymbolTable:
|
|
70
|
+
"""Manages symbol definitions and scopes."""
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.global_scope = Scope()
|
|
74
|
+
self.current_scope = self.global_scope
|
|
75
|
+
self.builtin_symbols = self._init_builtins()
|
|
76
|
+
|
|
77
|
+
def _init_builtins(self) -> Set[str]:
|
|
78
|
+
"""Initialize built-in Pine Script variables."""
|
|
79
|
+
return {
|
|
80
|
+
# Price data
|
|
81
|
+
'open', 'high', 'low', 'close', 'volume',
|
|
82
|
+
'hl2', 'hlc3', 'ohlc4', 'hlcc4',
|
|
83
|
+
# Bar data
|
|
84
|
+
'bar_index', 'time', 'timenow',
|
|
85
|
+
# Bar state
|
|
86
|
+
'barstate.isfirst', 'barstate.islast', 'barstate.ishistory',
|
|
87
|
+
'barstate.isrealtime', 'barstate.isnew', 'barstate.isconfirmed',
|
|
88
|
+
# Constants
|
|
89
|
+
'na', 'true', 'false',
|
|
90
|
+
# Color constants
|
|
91
|
+
'color.red', 'color.green', 'color.blue', 'color.white', 'color.black',
|
|
92
|
+
'color.yellow', 'color.orange', 'color.purple', 'color.gray',
|
|
93
|
+
# Plot styles
|
|
94
|
+
'plot.style_line', 'plot.style_stepline', 'plot.style_histogram',
|
|
95
|
+
'plot.style_cross', 'plot.style_area', 'plot.style_columns',
|
|
96
|
+
'plot.style_circles', 'plot.style_linebr', 'plot.style_steplinebr',
|
|
97
|
+
# Strategy constants
|
|
98
|
+
'strategy.long', 'strategy.short',
|
|
99
|
+
'strategy.commission.percent', 'strategy.commission.cash_per_contract',
|
|
100
|
+
'strategy.commission.cash_per_order',
|
|
101
|
+
'strategy.percent', 'strategy.fixed', 'strategy.cash',
|
|
102
|
+
'strategy.percent_of_equity',
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def enter_scope(self) -> None:
|
|
106
|
+
"""Enter a new scope."""
|
|
107
|
+
self.current_scope = Scope(parent=self.current_scope)
|
|
108
|
+
|
|
109
|
+
def exit_scope(self) -> None:
|
|
110
|
+
"""Exit the current scope."""
|
|
111
|
+
if self.current_scope.parent:
|
|
112
|
+
self.current_scope = self.current_scope.parent
|
|
113
|
+
|
|
114
|
+
def define(self, symbol: Symbol) -> None:
|
|
115
|
+
"""Define a symbol in the current scope."""
|
|
116
|
+
self.current_scope.define(symbol)
|
|
117
|
+
|
|
118
|
+
def lookup(self, name: str) -> Optional[Symbol]:
|
|
119
|
+
"""Look up a symbol in current or parent scopes."""
|
|
120
|
+
# Check builtins first
|
|
121
|
+
if name in self.builtin_symbols:
|
|
122
|
+
return Symbol(name, VariableKind.SERIES, is_global=True)
|
|
123
|
+
|
|
124
|
+
return self.current_scope.lookup(name)
|
|
125
|
+
|
|
126
|
+
def lookup_global(self, name: str) -> Optional[Symbol]:
|
|
127
|
+
"""Look up a symbol only in global scope."""
|
|
128
|
+
return self.global_scope.lookup_local(name)
|
|
129
|
+
|
|
130
|
+
def is_builtin(self, name: str) -> bool:
|
|
131
|
+
"""Check if name is a built-in symbol."""
|
|
132
|
+
return name in self.builtin_symbols
|
|
133
|
+
|
|
134
|
+
def get_all_globals(self) -> List[Symbol]:
|
|
135
|
+
"""Get all symbols defined in global scope."""
|
|
136
|
+
return list(self.global_scope.symbols.values())
|
|
137
|
+
|
|
138
|
+
def mark_as_indexed(self, name: str) -> None:
|
|
139
|
+
"""Mark a variable as being indexed (e.g., close[1])."""
|
|
140
|
+
symbol = self.lookup(name)
|
|
141
|
+
if symbol:
|
|
142
|
+
symbol.is_indexed = True
|
|
143
|
+
symbol.is_series = True # Indexed variables are always Series
|
|
144
|
+
|
|
145
|
+
def mark_as_series(self, name: str) -> None:
|
|
146
|
+
"""Mark a variable as a Series type."""
|
|
147
|
+
symbol = self.lookup(name)
|
|
148
|
+
if symbol:
|
|
149
|
+
symbol.is_series = True
|
|
150
|
+
|
|
151
|
+
def mark_from_ta_function(self, name: str) -> None:
|
|
152
|
+
"""Mark a variable as assigned from a ta.* function."""
|
|
153
|
+
symbol = self.lookup(name)
|
|
154
|
+
if symbol:
|
|
155
|
+
symbol.is_from_ta_function = True
|
|
156
|
+
symbol.is_series = True # ta.* functions return Series
|