pine2pyne 0.2.4__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.
- pine2pyne/__init__.py +84 -0
- pine2pyne/__main__.py +12 -0
- pine2pyne/ast_nodes.py +405 -0
- pine2pyne/ast_walk.py +93 -0
- pine2pyne/block_value_lowering.py +694 -0
- pine2pyne/bool_builtins.py +42 -0
- pine2pyne/bool_value_typing.py +580 -0
- pine2pyne/cli.py +247 -0
- pine2pyne/codegen.py +886 -0
- pine2pyne/compat/__init__.py +19 -0
- pine2pyne/compat/__main__.py +6 -0
- pine2pyne/compat/cli.py +114 -0
- pine2pyne/compat/comparison.py +105 -0
- pine2pyne/compat/corpus.py +73 -0
- pine2pyne/compat/report.py +95 -0
- pine2pyne/compat/runner.py +63 -0
- pine2pyne/condition_typing.py +260 -0
- pine2pyne/conditional_history_calls.py +96 -0
- pine2pyne/core/__init__.py +26 -0
- pine2pyne/core/contracts.py +125 -0
- pine2pyne/di.py +173 -0
- pine2pyne/emit/__init__.py +5 -0
- pine2pyne/emit/legacy.py +14 -0
- pine2pyne/emit/pynecomp/__init__.py +20 -0
- pine2pyne/emit/pynecomp/banner.py +30 -0
- pine2pyne/emit/pynecomp/blank_lines.py +69 -0
- pine2pyne/emit/pynecomp/emitter.py +61 -0
- pine2pyne/emit/pynecomp/imports.py +65 -0
- pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
- pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
- pine2pyne/emit/pynecomp/passes/base.py +69 -0
- pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
- pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
- pine2pyne/emit/pynecomp/passes/imports.py +112 -0
- pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
- pine2pyne/emit/pynecomp/passes/library.py +205 -0
- pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
- pine2pyne/emit/pynecomp/printer.py +288 -0
- pine2pyne/emit/pynecomp/unparser.py +186 -0
- pine2pyne/emitted_names.py +152 -0
- pine2pyne/errors.py +69 -0
- pine2pyne/for_loop_end_bound.py +172 -0
- pine2pyne/grouped_history_lowering.py +310 -0
- pine2pyne/import_resolver.py +469 -0
- pine2pyne/input_hoisting.py +195 -0
- pine2pyne/legacy_function_names.py +74 -0
- pine2pyne/legacy_sessions.py +68 -0
- pine2pyne/lexer.py +630 -0
- pine2pyne/library_collision.py +434 -0
- pine2pyne/namespace_members.py +247 -0
- pine2pyne/node_ids.py +235 -0
- pine2pyne/once_lowering.py +75 -0
- pine2pyne/overload_resolution.py +475 -0
- pine2pyne/parse_repair.py +398 -0
- pine2pyne/parser.py +1873 -0
- pine2pyne/pine_builtins.py +394 -0
- pine2pyne/pine_semantics.py +604 -0
- pine2pyne/pipeline/__init__.py +18 -0
- pine2pyne/pipeline/service.py +70 -0
- pine2pyne/pipeline/stages.py +73 -0
- pine2pyne/scope_renamer.py +965 -0
- pine2pyne/strategy_when_lowering.py +101 -0
- pine2pyne/strict_boolean.py +101 -0
- pine2pyne/symbol_table.py +166 -0
- pine2pyne/tokens.py +143 -0
- pine2pyne/transformer.py +2510 -0
- pine2pyne/type_inference.py +422 -0
- pine2pyne/udt_field_defaults.py +149 -0
- pine2pyne/value_blocks.py +357 -0
- pine2pyne-0.2.4.dist-info/METADATA +99 -0
- pine2pyne-0.2.4.dist-info/RECORD +75 -0
- pine2pyne-0.2.4.dist-info/WHEEL +5 -0
- pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
- pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
- pine2pyne-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mapping of Pine Script built-in functions, variables, and constants to PyneCore equivalents.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, FrozenSet, Set, Tuple
|
|
5
|
+
|
|
6
|
+
from .namespace_members import NAMESPACE_MEMBERS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Functions that need to be renamed
|
|
10
|
+
FUNCTION_RENAMES: Dict[str, str] = {
|
|
11
|
+
'array.from': 'array.from_items', # 'from' is Python keyword
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Module renames
|
|
15
|
+
MODULE_RENAMES: Dict[str, str] = {
|
|
16
|
+
'str': 'string', # str is Python built-in
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Type renames (Pine Script -> Python/PyneCore)
|
|
20
|
+
TYPE_RENAMES: Dict[str, str] = {
|
|
21
|
+
# Drawing types
|
|
22
|
+
'line': 'Line',
|
|
23
|
+
'label': 'Label',
|
|
24
|
+
'box': 'Box',
|
|
25
|
+
'table': 'Table',
|
|
26
|
+
'linefill': 'LineFill',
|
|
27
|
+
'polyline': 'Polyline',
|
|
28
|
+
'color': 'Color',
|
|
29
|
+
'chart.point': 'ChartPoint',
|
|
30
|
+
|
|
31
|
+
# Data structure types
|
|
32
|
+
'matrix': 'Matrix',
|
|
33
|
+
|
|
34
|
+
# Basic types (in type hints)
|
|
35
|
+
'string': 'str',
|
|
36
|
+
'int': 'int',
|
|
37
|
+
'float': 'float',
|
|
38
|
+
'bool': 'bool',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Built-in variables that are always Series
|
|
42
|
+
SERIES_VARIABLES: Set[str] = {
|
|
43
|
+
'open', 'high', 'low', 'close', 'volume',
|
|
44
|
+
'hl2', 'hlc3', 'ohlc4', 'hlcc4',
|
|
45
|
+
'bar_index', 'last_bar_index', 'last_bar_time',
|
|
46
|
+
'time', 'time_close', 'time_tradingday', 'timenow',
|
|
47
|
+
'dayofmonth', 'dayofweek', 'hour', 'minute', 'second', 'month', 'weekofyear', 'year',
|
|
48
|
+
}
|
|
49
|
+
# The builtin series variables that are also builtin functions, `time("D")` and `hour(t)`
|
|
50
|
+
# (the names both the functions and the variables of pine_reference_types.json list, card #89)
|
|
51
|
+
CALLABLE_SERIES_VARIABLES: Set[str] = {
|
|
52
|
+
'time', 'time_close',
|
|
53
|
+
'dayofmonth', 'dayofweek', 'hour', 'minute', 'second', 'month', 'weekofyear', 'year',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Constants that should be preserved as-is
|
|
57
|
+
CONSTANTS: Set[str] = {
|
|
58
|
+
# Strategy constants
|
|
59
|
+
'strategy.long',
|
|
60
|
+
'strategy.short',
|
|
61
|
+
'strategy.commission.percent',
|
|
62
|
+
'strategy.commission.cash_per_contract',
|
|
63
|
+
'strategy.commission.cash_per_order',
|
|
64
|
+
'strategy.percent',
|
|
65
|
+
'strategy.fixed',
|
|
66
|
+
'strategy.cash',
|
|
67
|
+
'strategy.percent_of_equity',
|
|
68
|
+
'strategy.closedtrades',
|
|
69
|
+
'strategy.opentrades',
|
|
70
|
+
|
|
71
|
+
# Color constants
|
|
72
|
+
'color.red', 'color.green', 'color.blue', 'color.white', 'color.black',
|
|
73
|
+
'color.yellow', 'color.orange', 'color.purple', 'color.gray',
|
|
74
|
+
'color.silver', 'color.maroon', 'color.fuchsia', 'color.lime',
|
|
75
|
+
'color.olive', 'color.navy', 'color.teal', 'color.aqua',
|
|
76
|
+
|
|
77
|
+
# Plot styles (need remapping to plot_style module)
|
|
78
|
+
'plot.style_line',
|
|
79
|
+
'plot.style_stepline',
|
|
80
|
+
'plot.style_histogram',
|
|
81
|
+
'plot.style_cross',
|
|
82
|
+
'plot.style_area',
|
|
83
|
+
'plot.style_columns',
|
|
84
|
+
'plot.style_circles',
|
|
85
|
+
'plot.style_linebr',
|
|
86
|
+
'plot.style_steplinebr',
|
|
87
|
+
|
|
88
|
+
# Barstate
|
|
89
|
+
'barstate.isfirst',
|
|
90
|
+
'barstate.islast',
|
|
91
|
+
'barstate.ishistory',
|
|
92
|
+
'barstate.isrealtime',
|
|
93
|
+
'barstate.isnew',
|
|
94
|
+
'barstate.isconfirmed',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Plot style constants need special handling
|
|
98
|
+
PLOT_STYLE_REMAP: Dict[str, str] = {
|
|
99
|
+
'plot.style_line': 'plot_style.style_line',
|
|
100
|
+
'plot.style_stepline': 'plot_style.style_stepline',
|
|
101
|
+
'plot.style_histogram': 'plot_style.style_histogram',
|
|
102
|
+
'plot.style_cross': 'plot_style.style_cross',
|
|
103
|
+
'plot.style_area': 'plot_style.style_area',
|
|
104
|
+
'plot.style_columns': 'plot_style.style_columns',
|
|
105
|
+
'plot.style_circles': 'plot_style.style_circles',
|
|
106
|
+
'plot.style_linebr': 'plot_style.style_linebr',
|
|
107
|
+
'plot.style_steplinebr': 'plot_style.style_steplinebr',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Modules that should be imported from pynecore.lib
|
|
111
|
+
# Synced with https://github.com/PyneSys/pynecore/tree/main/src/pynecore/lib
|
|
112
|
+
PYNECORE_LIB_MODULES: Set[str] = {
|
|
113
|
+
# Core modules (submodules as .py files in pynecore/lib/)
|
|
114
|
+
'script', 'input', 'plot', 'color', 'ta', 'math', 'strategy',
|
|
115
|
+
'array', 'matrix', 'map', 'string',
|
|
116
|
+
'timeframe', 'runtime', 'alert', 'label', 'line', 'box', 'table',
|
|
117
|
+
'linefill', 'polyline', 'log', 'barstate', 'session', 'syminfo',
|
|
118
|
+
'adjustment', 'barmerge', 'chart', 'dividends', 'earnings',
|
|
119
|
+
# Higher-timeframe / other-symbol data access. Present in upstream PyneCore
|
|
120
|
+
# (lib/request.py + lib/ticker.py); without them here the transpiler emits
|
|
121
|
+
# request.*/ticker.* calls it never imports -> NameError at runtime.
|
|
122
|
+
'request', 'ticker',
|
|
123
|
+
# Other upstream lib modules that were likewise never imported. Found by
|
|
124
|
+
# diffing pynecore/lib/*.py against this set — 'splits' is used by the
|
|
125
|
+
# request.splits samples, 'dayofweek' by session/date logic.
|
|
126
|
+
'dayofweek', 'splits', 'footprint', 'pivotpointtype', 'volume_row',
|
|
127
|
+
# Standalone functions (defined in pynecore/lib/__init__.py)
|
|
128
|
+
'currency', 'timestamp', 'max_bars_back',
|
|
129
|
+
# Plotting and coloring functions
|
|
130
|
+
'bgcolor', 'barcolor', 'alertcondition', 'plotarrow', 'plotbar', 'plotcandle',
|
|
131
|
+
'plotchar', 'plotshape', 'fill',
|
|
132
|
+
# Utility functions
|
|
133
|
+
'na', 'nz', 'fixnan',
|
|
134
|
+
# Constant namespaces
|
|
135
|
+
'xloc', 'yloc', 'position', 'size', 'shape', 'location',
|
|
136
|
+
'extend', 'font', 'text', 'display', 'scale', 'hline', 'format',
|
|
137
|
+
'order',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Drawing and structural types that should be imported from pynecore.types
|
|
141
|
+
DRAWING_TYPES: Set[str] = {
|
|
142
|
+
'Line', 'Label', 'Box', 'Table', 'LineFill', 'Polyline', 'Color', 'Matrix',
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Names a generated module may read from pynecore: a user binding of one of them is renamed
|
|
146
|
+
# `<name>__ren__` (PyneComp renames every such binding, whether or not the script reads the
|
|
147
|
+
# builtin: 270 renames in assets/pyne). pynecore.lib's modules and functions, its types, and
|
|
148
|
+
# the decorators and namespaces the module itself uses.
|
|
149
|
+
PYNECOMP_RESERVED_NAMES: FrozenSet[str] = frozenset(
|
|
150
|
+
PYNECORE_LIB_MODULES | DRAWING_TYPES | SERIES_VARIABLES
|
|
151
|
+
| {'method', 'str', 'Series', 'Persistent', 'PersistentSeries', 'NA', 'ask', 'bid', 'updatetime',
|
|
152
|
+
'dayofweek', 'pivotpointtype', 'volume_row'})
|
|
153
|
+
|
|
154
|
+
# Pine's type-cast functions of the drawing types (`box(na)`); each shares its name
|
|
155
|
+
# with a pynecore namespace module, which is not callable (card #8)
|
|
156
|
+
DRAWING_CAST_FUNCTIONS: Set[str] = {'box', 'label', 'line', 'linefill', 'table'}
|
|
157
|
+
|
|
158
|
+
# Chart types that should be imported from pynecore.types.chart
|
|
159
|
+
CHART_TYPES: Set[str] = {
|
|
160
|
+
'ChartPoint',
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# The namespaces whose functions a Pine method call can reach: pynecore's
|
|
164
|
+
# `method_call('m', receiver, ...)` picks one of them from the receiver's runtime
|
|
165
|
+
# class (core/pine_method.py). Which of them defines a method is read from the
|
|
166
|
+
# GENERATED namespace_members table, never from a hand-kept list (card #8).
|
|
167
|
+
METHOD_NAMESPACES = ('array', 'matrix', 'map', 'line', 'box', 'label', 'table', 'linefill', 'polyline')
|
|
168
|
+
|
|
169
|
+
# Members of those namespaces that are not methods of an object: the constructors
|
|
170
|
+
# (`new`, `new_*`, `from_items` for Pine's array.from), `X.all`, the typed na value,
|
|
171
|
+
# a module's `annotations` import, and the `style_*` constants.
|
|
172
|
+
NON_METHOD_MEMBERS: FrozenSet[str] = frozenset({'new', 'from_items', 'all', 'na_float', 'annotations'})
|
|
173
|
+
NON_METHOD_PREFIXES = ('new_', 'style_')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _namespaces_by_method() -> Dict[str, Tuple[str, ...]]:
|
|
177
|
+
"""Method name -> the METHOD_NAMESPACES that define it, in that order."""
|
|
178
|
+
namespaces_by_method: Dict[str, Tuple[str, ...]] = {}
|
|
179
|
+
for namespace in METHOD_NAMESPACES:
|
|
180
|
+
for member in NAMESPACE_MEMBERS.get(namespace, ()):
|
|
181
|
+
if member not in NON_METHOD_MEMBERS and not member.startswith(NON_METHOD_PREFIXES):
|
|
182
|
+
namespaces_by_method[member] = namespaces_by_method.get(member, ()) + (namespace,)
|
|
183
|
+
return namespaces_by_method
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
NAMESPACES_BY_METHOD: Dict[str, Tuple[str, ...]] = _namespaces_by_method()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def method_namespaces(method_name: str) -> Tuple[str, ...]:
|
|
190
|
+
"""Every method namespace that defines `method_name`; () when none does."""
|
|
191
|
+
return NAMESPACES_BY_METHOD.get(method_name, ())
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_function_name(pine_func: str) -> str:
|
|
195
|
+
"""Get the PyneCore function name for a Pine Script function."""
|
|
196
|
+
return FUNCTION_RENAMES.get(pine_func, pine_func)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_module_name(pine_module: str) -> str:
|
|
200
|
+
"""Get the PyneCore module name for a Pine Script module."""
|
|
201
|
+
return MODULE_RENAMES.get(pine_module, pine_module)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_type_name(pine_type: str) -> str:
|
|
205
|
+
"""Get the Python/PyneCore type name for a Pine Script type."""
|
|
206
|
+
# Strip .new suffix from UDT types (e.g., TradeStats.new -> TradeStats)
|
|
207
|
+
if '.new' in pine_type and pine_type and pine_type[0].isupper():
|
|
208
|
+
pine_type = pine_type.replace('.new', '')
|
|
209
|
+
return TYPE_RENAMES.get(pine_type, pine_type)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def is_series_variable(name: str) -> bool:
|
|
213
|
+
"""Check if a variable is a built-in Series variable."""
|
|
214
|
+
return name in SERIES_VARIABLES
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_constant(name: str) -> bool:
|
|
218
|
+
"""Check if a name is a built-in constant."""
|
|
219
|
+
return name in CONSTANTS
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def needs_plot_style_remap(name: str) -> bool:
|
|
223
|
+
"""Check if a name needs plot_style module remapping."""
|
|
224
|
+
return name in PLOT_STYLE_REMAP
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_plot_style_remap(name: str) -> str:
|
|
228
|
+
"""Get the remapped plot_style constant name."""
|
|
229
|
+
return PLOT_STYLE_REMAP.get(name, name)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# Input functions that extract to main() parameters
|
|
233
|
+
INPUT_FUNCTIONS: Set[str] = {
|
|
234
|
+
'input.int',
|
|
235
|
+
'input.float',
|
|
236
|
+
'input.bool',
|
|
237
|
+
'input.string',
|
|
238
|
+
'input.color',
|
|
239
|
+
'input.source',
|
|
240
|
+
'input.timeframe',
|
|
241
|
+
'input.session',
|
|
242
|
+
'input.symbol',
|
|
243
|
+
'input.price',
|
|
244
|
+
'input.time',
|
|
245
|
+
'input.enum',
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def is_input_function(func_name: str) -> bool:
|
|
250
|
+
"""Check if function is an input.* function."""
|
|
251
|
+
return func_name in INPUT_FUNCTIONS
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Pine Script input.* positional parameter names (after defval, title).
|
|
255
|
+
# Pine Script allows these as positional; PyneCore requires keyword-only.
|
|
256
|
+
INPUT_POSITIONAL_PARAMS: Dict[str, list[str]] = {
|
|
257
|
+
'input.int': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'active'],
|
|
258
|
+
'input.float': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'active'],
|
|
259
|
+
'input.bool': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
260
|
+
'input.string': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
261
|
+
'input.color': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
262
|
+
'input.timeframe': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
263
|
+
'input.session': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
264
|
+
# v6 reference: input.symbol has no options; input.source puts confirm last (#89 review)
|
|
265
|
+
'input.symbol': ['tooltip', 'inline', 'group', 'confirm', 'display', 'active'],
|
|
266
|
+
'input.source': ['tooltip', 'inline', 'group', 'display', 'active', 'confirm'],
|
|
267
|
+
'input.price': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
268
|
+
'input.time': ['tooltip', 'inline', 'group', 'confirm', 'display'],
|
|
269
|
+
'input.enum': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display', 'active'], # card #97
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Pine v2-v4 `input(defval, title, type=input.X)`: the keyword names the kind the v5+ `input.X` functions
|
|
273
|
+
# carry in their names (card #115). The key is the spelling after an optional `input.` prefix.
|
|
274
|
+
LEGACY_INPUT_TYPE_ARGUMENT_FUNCTIONS: Dict[str, str] = {
|
|
275
|
+
'integer': 'input.int', 'float': 'input.float', 'bool': 'input.bool', 'string': 'input.string',
|
|
276
|
+
'symbol': 'input.symbol', 'resolution': 'input.timeframe', 'source': 'input.source',
|
|
277
|
+
'session': 'input.session', 'time': 'input.time', 'color': 'input.color', 'price': 'input.price',
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# input.int/input.float have a second positional form after `title` (v6 reference): the options form,
|
|
281
|
+
# selected by a list third argument (pynecore's _bind_numeric_tail, _NUMERIC_OPTIONS_TAIL; card #95).
|
|
282
|
+
NUMERIC_INPUT_FUNCTIONS: FrozenSet[str] = frozenset({'input.int', 'input.float'})
|
|
283
|
+
NUMERIC_INPUT_OPTIONS_POSITIONAL_PARAMS: list[str] = ['options', 'tooltip', 'inline', 'group', 'confirm',
|
|
284
|
+
'display', 'active']
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def input_positional_param_names(func_name: str, positional_args: list) -> list[str]:
|
|
288
|
+
"""The names Pine gives the positional arguments of an input.* call after `defval` and `title`
|
|
289
|
+
(empty for a function this table does not list). The one place that decides which positional
|
|
290
|
+
form a call uses, for the transformer and the semantic pass alike."""
|
|
291
|
+
from .ast_nodes import ArrayLiteral
|
|
292
|
+
if func_name in NUMERIC_INPUT_FUNCTIONS and len(positional_args) > 2 \
|
|
293
|
+
and isinstance(positional_args[2], ArrayLiteral):
|
|
294
|
+
return NUMERIC_INPUT_OPTIONS_POSITIONAL_PARAMS
|
|
295
|
+
return INPUT_POSITIONAL_PARAMS.get(func_name, [])
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def bind_input_positional_args(func_name: str, positional_args: list) -> Tuple[list, dict]:
|
|
299
|
+
"""Split an input.* call's positional arguments into `defval, title` and the rest bound by
|
|
300
|
+
name: PyneCore takes everything after `title` as a keyword. A positional past the last name
|
|
301
|
+
is dropped, as before card #95."""
|
|
302
|
+
names = input_positional_param_names(func_name, positional_args)
|
|
303
|
+
if not names or len(positional_args) <= 2:
|
|
304
|
+
return list(positional_args), {}
|
|
305
|
+
return list(positional_args[:2]), dict(zip(names, positional_args[2:]))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Pine's positional parameter order of the script declarations (Pine v6 reference; card #55).
|
|
309
|
+
# The parser binds positionals to these names and codegen emits keywords, so pynecore's own
|
|
310
|
+
# order (its strategy inserts calc_on_every_history_tick at 9) does not matter. v4's study()
|
|
311
|
+
# binds as 'indicator'.
|
|
312
|
+
SCRIPT_DECLARATION_POSITIONAL_PARAMS: Dict[str, list[str]] = {
|
|
313
|
+
'indicator': ['title', 'shorttitle', 'overlay', 'format', 'precision', 'scale', 'max_bars_back',
|
|
314
|
+
'timeframe', 'timeframe_gaps', 'explicit_plot_zorder', 'max_lines_count',
|
|
315
|
+
'max_labels_count', 'max_boxes_count', 'calc_bars_count', 'max_polylines_count',
|
|
316
|
+
'dynamic_requests', 'behind_chart'],
|
|
317
|
+
'strategy': ['title', 'shorttitle', 'overlay', 'format', 'precision', 'scale', 'pyramiding',
|
|
318
|
+
'calc_on_order_fills', 'calc_on_every_tick', 'max_bars_back',
|
|
319
|
+
'backtest_fill_limits_assumption', 'default_qty_type', 'default_qty_value',
|
|
320
|
+
'initial_capital', 'currency', 'slippage', 'commission_type', 'commission_value',
|
|
321
|
+
'process_orders_on_close', 'close_entries_rule', 'margin_long', 'margin_short',
|
|
322
|
+
'explicit_plot_zorder', 'max_lines_count', 'max_labels_count', 'max_boxes_count',
|
|
323
|
+
'calc_bars_count', 'risk_free_rate', 'use_bar_magnifier',
|
|
324
|
+
'fill_orders_on_standard_ohlc', 'max_polylines_count', 'dynamic_requests',
|
|
325
|
+
'behind_chart'],
|
|
326
|
+
'library': ['title', 'overlay', 'dynamic_requests'],
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# How many leading SCRIPT_DECLARATION_POSITIONAL_PARAMS names every Pine version shares.
|
|
330
|
+
# A positional past them is bound only under //@version>=5 (v4 has no margin/zorder slots).
|
|
331
|
+
VERSION_INVARIANT_POSITIONAL_COUNT: Dict[str, int] = {'indicator': 7, 'strategy': 9, 'library': 2}
|
|
332
|
+
|
|
333
|
+
# v4 study() keywords that indicator() renamed (Pine v5 migration guide).
|
|
334
|
+
STUDY_KEYWORD_RENAMES: Dict[str, str] = {'resolution': 'timeframe', 'resolution_gaps': 'timeframe_gaps'}
|
|
335
|
+
|
|
336
|
+
# Pine functions whose result is a constant when their arguments are: a script declaration
|
|
337
|
+
# argument may be built from them (`pyramiding = BASE + int(50 / QTY)`) and still be hoisted.
|
|
338
|
+
DECLARATION_CONSTANT_FUNCTIONS: Set[str] = {'int', 'float', 'bool'}
|
|
339
|
+
DECLARATION_CONSTANT_FUNCTION_NAMESPACES = ('math.',)
|
|
340
|
+
# ... except the members of those namespaces that keep state between bars or draw a random number:
|
|
341
|
+
# hoisted, they would run once at import (card #78).
|
|
342
|
+
DECLARATION_STATEFUL_FUNCTIONS: Set[str] = {'math.sum', 'math.random'}
|
|
343
|
+
# Builtin namespaces whose members a script declaration argument may name (`strategy.fixed`,
|
|
344
|
+
# `format.price`); a member of any other builtin namespace (`syminfo.ticker`) is not a constant.
|
|
345
|
+
DECLARATION_CONSTANT_NAMESPACES = ('format.', 'scale.', 'currency.', 'strategy.', 'display.', 'math.', 'color.')
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# Python reserved words that cannot be used as function/variable names.
|
|
349
|
+
PYTHON_RESERVED_WORDS: Set[str] = {
|
|
350
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
|
|
351
|
+
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
|
|
352
|
+
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
|
|
353
|
+
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
|
|
354
|
+
'try', 'while', 'with', 'yield',
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
#: The suffix PyneComp gives a Pine name that would clash in Python: a reserved word
|
|
359
|
+
#: (`from` -> `from__ren__`) or a name pynecore defines (`color` -> `color__ren__`)
|
|
360
|
+
RENAMED_SUFFIX = '__ren__'
|
|
361
|
+
#: PyneComp's suffix for a global named like a function, a type or a namespace the script uses (`thma__global__`)
|
|
362
|
+
GLOBAL_SUFFIX = '__global__'
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def sanitize_identifier(name: str) -> str:
|
|
366
|
+
"""Rename Python reserved words used as identifiers, as PyneComp does (`lambda__ren__`)."""
|
|
367
|
+
if name in PYTHON_RESERVED_WORDS:
|
|
368
|
+
return f'{name}{RENAMED_SUFFIX}'
|
|
369
|
+
return name
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# Pine Script built-in "variable-functions": used without () in Pine Script
|
|
373
|
+
# but require () in PyneCore because they're implemented as functions.
|
|
374
|
+
# Values are the argument text of the call (all empty: each bare form takes none).
|
|
375
|
+
TA_VARIABLE_FUNCTIONS: Dict[str, str] = {
|
|
376
|
+
# No argument: pynecore's bare form reads hlc3 itself, and only a call with no
|
|
377
|
+
# arguments is hoisted out of a condition (card #8)
|
|
378
|
+
'ta.vwap': '',
|
|
379
|
+
'ta.tr': '', # no args needed
|
|
380
|
+
'ta.accdist': '',
|
|
381
|
+
'ta.obv': '',
|
|
382
|
+
'ta.iii': '',
|
|
383
|
+
'ta.nvi': '',
|
|
384
|
+
'ta.pvi': '',
|
|
385
|
+
'ta.pvt': '',
|
|
386
|
+
'ta.wad': '',
|
|
387
|
+
'ta.wvad': '',
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# Pine variables that pynecore implements as a function returning an array: as a
|
|
391
|
+
# method receiver (`box.all.size()`) they become `array.size(box.all)` (card #8)
|
|
392
|
+
DRAWING_ALL_VARIABLES: Set[str] = {
|
|
393
|
+
f'{namespace}.all' for namespace in ('box', 'label', 'line', 'linefill', 'polyline', 'table')
|
|
394
|
+
}
|