@pineforge/codegen-pyodide 1.3.0 → 1.4.0
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.
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +6 -5
- package/pineforge_codegen/analyzer/base.py +3 -3
- package/pineforge_codegen/analyzer/types.py +7 -3
- package/pineforge_codegen/codegen/base.py +26 -16
- package/pineforge_codegen/codegen/checked_settings.py +113 -2
- package/pineforge_codegen/codegen/collection_history.py +163 -12
- package/pineforge_codegen/codegen/emit_top.py +24 -8
- package/pineforge_codegen/codegen/helpers.py +19 -1
- package/pineforge_codegen/codegen/host_members.py +1 -0
- package/pineforge_codegen/codegen/input.py +23 -8
- package/pineforge_codegen/codegen/run_stops.py +52 -0
- package/pineforge_codegen/codegen/security.py +12 -5
- package/pineforge_codegen/codegen/tables.py +132 -28
- package/pineforge_codegen/codegen/tv_number_format.py +9 -2
- package/pineforge_codegen/codegen/types.py +85 -4
- package/pineforge_codegen/codegen/visit_call.py +174 -55
- package/pineforge_codegen/codegen/visit_expr.py +32 -6
- package/pineforge_codegen/codegen/visit_stmt.py +3 -0
- package/pineforge_codegen/collection_history.py +122 -20
- package/pineforge_codegen/diagnostics_catalog.json +1 -0
- package/pineforge_codegen/external_requests.py +30 -9
- package/pineforge_codegen/matrix_overloads.py +7 -0
- package/pineforge_codegen/parser.py +7 -2
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +82 -0
- package/pineforge_codegen-1.4.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +7 -3
- package/pineforge_codegen-1.3.0.tar.gz +0 -0
|
@@ -14,20 +14,167 @@ TradingView's read-only history (fixtures/array_history_tv).
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
from ..collection_history import (
|
|
17
|
+
COLLECTION_HISTORY_ARRAY_VALUE_CPP,
|
|
17
18
|
COLLECTION_HISTORY_CLASS_CPP,
|
|
18
19
|
COLLECTION_HISTORY_CPP,
|
|
19
20
|
COLLECTION_HISTORY_GENERIC_MATRIX_CPP,
|
|
20
21
|
COLLECTION_HISTORY_MATRIX_CPP,
|
|
22
|
+
NA_MATRIX_MESSAGE,
|
|
21
23
|
history_annotation,
|
|
22
24
|
literal_offset,
|
|
23
25
|
)
|
|
24
|
-
from ..ast_nodes import FuncCall, Identifier, MemberAccess
|
|
26
|
+
from ..ast_nodes import Assignment, FuncCall, FuncDef, Identifier, MemberAccess, MethodDef, Ternary, VarDecl
|
|
27
|
+
from ..limits import iter_ast_nodes
|
|
25
28
|
from ..symbols import TypeSpec
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class CollectionHistoryEmitter:
|
|
29
32
|
"""CodeGen mixin: the history members of array and matrix variables."""
|
|
30
33
|
|
|
34
|
+
def _matrix_history_value_names(self) -> set[str]:
|
|
35
|
+
cached = getattr(self, "_matrix_history_value_bindings", None)
|
|
36
|
+
if cached is not None:
|
|
37
|
+
return cached
|
|
38
|
+
names = set()
|
|
39
|
+
bindings = {}
|
|
40
|
+
callable_depth = None
|
|
41
|
+
for node, depth in iter_ast_nodes(self.ctx.ast):
|
|
42
|
+
if callable_depth is not None:
|
|
43
|
+
if depth > callable_depth:
|
|
44
|
+
continue
|
|
45
|
+
callable_depth = None
|
|
46
|
+
if isinstance(node, (FuncDef, MethodDef)):
|
|
47
|
+
callable_depth = depth
|
|
48
|
+
continue
|
|
49
|
+
if isinstance(node, VarDecl):
|
|
50
|
+
name, value = node.name, node.value
|
|
51
|
+
elif isinstance(node, Assignment) and isinstance(node.target, Identifier):
|
|
52
|
+
name, value = node.target.name, node.value
|
|
53
|
+
else:
|
|
54
|
+
continue
|
|
55
|
+
values = [value]
|
|
56
|
+
while values:
|
|
57
|
+
source = values.pop()
|
|
58
|
+
annotation = history_annotation(source)
|
|
59
|
+
if annotation is not None and annotation["kind"] == "matrix":
|
|
60
|
+
names.add(name)
|
|
61
|
+
elif isinstance(source, Identifier):
|
|
62
|
+
bindings.setdefault(source.name, []).append(name)
|
|
63
|
+
elif isinstance(source, Ternary):
|
|
64
|
+
values.extend((source.true_val, source.false_val))
|
|
65
|
+
pending = list(names)
|
|
66
|
+
while pending:
|
|
67
|
+
source = pending.pop()
|
|
68
|
+
for name in bindings.get(source, ()):
|
|
69
|
+
if name not in names:
|
|
70
|
+
names.add(name)
|
|
71
|
+
pending.append(name)
|
|
72
|
+
self._matrix_history_value_bindings = names
|
|
73
|
+
return names
|
|
74
|
+
|
|
75
|
+
def _checked_matrix_history_receiver(self, receiver, receiver_node):
|
|
76
|
+
if (isinstance(receiver_node, Identifier)
|
|
77
|
+
and receiver_node.name in self._matrix_history_value_names()
|
|
78
|
+
and not self._collection_name_is_lexically_shadowed(receiver_node.name)
|
|
79
|
+
and receiver_node.name not in self._current_func_param_types):
|
|
80
|
+
return (f'([&]() -> auto& {{ if (is_na({receiver})) '
|
|
81
|
+
f'_PF_COLLECTION_STOP("na_reference", "matrix", "{NA_MATRIX_MESSAGE}"); '
|
|
82
|
+
f'return {receiver}; }}())')
|
|
83
|
+
return receiver
|
|
84
|
+
|
|
85
|
+
def _array_history_value_names(self) -> dict[str, TypeSpec]:
|
|
86
|
+
cached = getattr(self, "_array_history_value_specs", None)
|
|
87
|
+
if cached is not None:
|
|
88
|
+
return cached
|
|
89
|
+
bindings = {}
|
|
90
|
+
specs = {}
|
|
91
|
+
callable_depth = None
|
|
92
|
+
for node, depth in iter_ast_nodes(self.ctx.ast):
|
|
93
|
+
if callable_depth is not None:
|
|
94
|
+
if depth > callable_depth:
|
|
95
|
+
continue
|
|
96
|
+
callable_depth = None
|
|
97
|
+
if isinstance(node, (FuncDef, MethodDef)):
|
|
98
|
+
callable_depth = depth
|
|
99
|
+
continue
|
|
100
|
+
if isinstance(node, VarDecl):
|
|
101
|
+
name, value = node.name, node.value
|
|
102
|
+
elif isinstance(node, Assignment) and isinstance(node.target, Identifier):
|
|
103
|
+
name, value = node.target.name, node.value
|
|
104
|
+
else:
|
|
105
|
+
continue
|
|
106
|
+
annotation = history_annotation(value)
|
|
107
|
+
if annotation is not None and annotation["use"] == "reference":
|
|
108
|
+
specs[name] = annotation["spec"]
|
|
109
|
+
else:
|
|
110
|
+
values = [value]
|
|
111
|
+
while values:
|
|
112
|
+
source_value = values.pop()
|
|
113
|
+
if isinstance(source_value, Identifier):
|
|
114
|
+
bindings.setdefault(source_value.name, []).append(name)
|
|
115
|
+
elif isinstance(source_value, Ternary):
|
|
116
|
+
values.extend((source_value.true_val, source_value.false_val))
|
|
117
|
+
pending = list(specs)
|
|
118
|
+
while pending:
|
|
119
|
+
source = pending.pop()
|
|
120
|
+
for name in bindings.get(source, ()):
|
|
121
|
+
if name not in specs:
|
|
122
|
+
specs[name] = specs[source]
|
|
123
|
+
pending.append(name)
|
|
124
|
+
for name, spec in specs.items():
|
|
125
|
+
self._array_vars.add(name)
|
|
126
|
+
self._collection_types[name] = spec
|
|
127
|
+
self._array_history_value_specs = specs
|
|
128
|
+
return specs
|
|
129
|
+
|
|
130
|
+
def _array_history_value_cpp_type(self, name: str) -> str | None:
|
|
131
|
+
spec = self._array_history_value_names().get(name)
|
|
132
|
+
if spec is None:
|
|
133
|
+
return None
|
|
134
|
+
spec = self._widen_array_spec_for_name(name, spec)
|
|
135
|
+
return f"_PFArrayHistoryValue<{self._type_spec_to_cpp(spec)}>"
|
|
136
|
+
|
|
137
|
+
def _array_history_value_expr_cpp_type(self, value) -> str | None:
|
|
138
|
+
values = [value]
|
|
139
|
+
while values:
|
|
140
|
+
source = values.pop()
|
|
141
|
+
if isinstance(source, Identifier):
|
|
142
|
+
if (self._collection_name_is_lexically_shadowed(source.name)
|
|
143
|
+
or source.name in self._current_func_param_types):
|
|
144
|
+
continue
|
|
145
|
+
cpp_type = self._array_history_value_cpp_type(source.name)
|
|
146
|
+
if cpp_type is not None:
|
|
147
|
+
return cpp_type
|
|
148
|
+
elif isinstance(source, Ternary):
|
|
149
|
+
values.extend((source.true_val, source.false_val))
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _legacy_array_history_element(self, receiver: str, spec: TypeSpec,
|
|
153
|
+
offset: str, mutable: bool = False) -> str:
|
|
154
|
+
element_cpp = self._type_spec_to_cpp(spec.element)
|
|
155
|
+
if self._array_history_value_names():
|
|
156
|
+
receiver = f"_pf_array_id({receiver})"
|
|
157
|
+
if mutable:
|
|
158
|
+
return (
|
|
159
|
+
f"([&]() -> decltype(auto) {{ "
|
|
160
|
+
f"auto& _pf_history_array = {receiver}; "
|
|
161
|
+
f"const int _pf_history_offset = {offset}; "
|
|
162
|
+
f"if (_pf_history_offset < 0 || "
|
|
163
|
+
f"static_cast<size_t>(_pf_history_offset) >= _pf_history_array.size()) "
|
|
164
|
+
f'_PF_ARRAY_STOP("index_out_of_bounds", nullptr, '
|
|
165
|
+
f'"Array history element index is out of bounds."); '
|
|
166
|
+
f"return _pf_history_array.at(_pf_history_offset); }}())"
|
|
167
|
+
)
|
|
168
|
+
return (
|
|
169
|
+
f"([&]() -> {element_cpp} {{ "
|
|
170
|
+
f"const auto& _pf_history_array = {receiver}; "
|
|
171
|
+
f"const int _pf_history_offset = {offset}; "
|
|
172
|
+
f"if (_pf_history_offset < 0 || "
|
|
173
|
+
f"static_cast<size_t>(_pf_history_offset) >= _pf_history_array.size()) "
|
|
174
|
+
f"return na<{element_cpp}>(); "
|
|
175
|
+
f"return _pf_history_array[_pf_history_offset]; }}())"
|
|
176
|
+
)
|
|
177
|
+
|
|
31
178
|
def _collection_history_variables(self) -> list:
|
|
32
179
|
"""The declarations whose history the script reads, in key order."""
|
|
33
180
|
history = getattr(self.ctx, "collection_history", None) or {}
|
|
@@ -59,11 +206,12 @@ class CollectionHistoryEmitter:
|
|
|
59
206
|
|
|
60
207
|
def _emit_collection_history_helper(self, lines: list[str]) -> None:
|
|
61
208
|
variables = self._collection_history_variables()
|
|
62
|
-
if not variables:
|
|
209
|
+
if not variables and not self._array_history_value_names():
|
|
63
210
|
return
|
|
64
211
|
float_spec = TypeSpec.primitive("float")
|
|
65
212
|
matrices = [v.spec for v in variables if v.kind == "matrix"]
|
|
66
213
|
lines.append(COLLECTION_HISTORY_CPP.strip("\n"))
|
|
214
|
+
lines.append(COLLECTION_HISTORY_ARRAY_VALUE_CPP.strip("\n"))
|
|
67
215
|
if any(spec.element == float_spec for spec in matrices):
|
|
68
216
|
lines.append(COLLECTION_HISTORY_MATRIX_CPP.strip("\n"))
|
|
69
217
|
if any(spec.element != float_spec for spec in matrices):
|
|
@@ -146,6 +294,9 @@ class CollectionHistoryEmitter:
|
|
|
146
294
|
current = self._visit_expr(node.object)
|
|
147
295
|
use = annotation["use"]
|
|
148
296
|
offset = literal_offset(node)
|
|
297
|
+
if use == "reference" and offset == 0:
|
|
298
|
+
cpp_type = self._type_spec_to_cpp(annotation["spec"])
|
|
299
|
+
return f"_PFArrayHistoryValue<{cpp_type}>({current})"
|
|
149
300
|
if use == "loop" or offset == 0:
|
|
150
301
|
return current
|
|
151
302
|
member = self._collection_history_member(self._collection_history_of(annotation))
|
|
@@ -153,6 +304,9 @@ class CollectionHistoryEmitter:
|
|
|
153
304
|
dynamic = offset is None
|
|
154
305
|
if use == "change":
|
|
155
306
|
return f"{member}.changed({index}, {current})"
|
|
307
|
+
if use == "reference":
|
|
308
|
+
return (f"{member}.reference({index}, {current})" if dynamic
|
|
309
|
+
else f"{member}.reference({index})")
|
|
156
310
|
if use == "copy":
|
|
157
311
|
return (f"{member}.value({index}, {current})" if dynamic
|
|
158
312
|
else f"{member}.value({index})")
|
|
@@ -168,7 +322,10 @@ class CollectionHistoryEmitter:
|
|
|
168
322
|
current = self._visit_expr(node.object)
|
|
169
323
|
offset = literal_offset(node)
|
|
170
324
|
if offset == 0:
|
|
171
|
-
|
|
325
|
+
nullable_array = (isinstance(node.object, Identifier)
|
|
326
|
+
and node.object.name in self._array_history_value_names())
|
|
327
|
+
return (f"is_na({current})" if annotation["kind"] == "matrix"
|
|
328
|
+
or nullable_array else "false")
|
|
172
329
|
member = self._collection_history_member(self._collection_history_of(annotation))
|
|
173
330
|
index = self._collection_history_offset_cpp(node)
|
|
174
331
|
if offset is None:
|
|
@@ -179,19 +336,13 @@ class CollectionHistoryEmitter:
|
|
|
179
336
|
"""A matrix method called on a matrix's history, lowered as the
|
|
180
337
|
variable's own method call is (visit_call: a matrix variable's
|
|
181
338
|
method)."""
|
|
182
|
-
from .tables import MATRIX_METHOD_KWARGS, MATRIX_METHODS, _merge_kwargs
|
|
183
|
-
|
|
184
339
|
recv = self._visit_expr(receiver)
|
|
185
340
|
self._check_matrix_method_allowed(method, spec, node)
|
|
186
|
-
|
|
187
|
-
raw_args = (
|
|
188
|
-
_merge_kwargs(node.args, node.kwargs, param_names, lambda a: a)
|
|
189
|
-
if param_names and node.kwargs else list(node.args)
|
|
190
|
-
)
|
|
341
|
+
raw_args = self._matrix_method_arg_nodes(method, node)
|
|
191
342
|
args = self._matrix_bool_value_args(
|
|
192
|
-
method, [self._visit_expr(a) for a in raw_args], raw_args, spec)
|
|
343
|
+
method, [self._visit_expr(a) if a is not None else None for a in raw_args], raw_args, spec)
|
|
193
344
|
try:
|
|
194
|
-
return
|
|
345
|
+
return self._matrix_method_expr(recv, method, args, raw_args, node)
|
|
195
346
|
except IndexError:
|
|
196
347
|
self._codegen_error(
|
|
197
348
|
node,
|
|
@@ -84,6 +84,8 @@ from __future__ import annotations
|
|
|
84
84
|
|
|
85
85
|
import re
|
|
86
86
|
|
|
87
|
+
from .run_stops import RUN_STOP_SHIMS_CPP
|
|
88
|
+
|
|
87
89
|
from ..ast_nodes import (
|
|
88
90
|
BoolLiteral, ExprStmt, ForInStmt, ForStmt, FuncCall, Identifier, IfStmt,
|
|
89
91
|
MemberAccess, SwitchStmt, TupleAssign, TupleLiteral, VarDecl, WhileStmt,
|
|
@@ -218,6 +220,7 @@ class TopLevelEmitter:
|
|
|
218
220
|
# in favour of fully qualified names emitted at each call site.
|
|
219
221
|
lines.append("using namespace pineforge;")
|
|
220
222
|
lines.append("")
|
|
223
|
+
lines.append(RUN_STOP_SHIMS_CPP)
|
|
221
224
|
self._emit_ta_compat_shims(lines)
|
|
222
225
|
if getattr(self, "_uses_drawing", False):
|
|
223
226
|
lines.append(DRAWING_LIFETIME_CPP)
|
|
@@ -1894,8 +1897,17 @@ class TopLevelEmitter:
|
|
|
1894
1897
|
|
|
1895
1898
|
def _emit_extern_c(self, lines: list[str]) -> None:
|
|
1896
1899
|
lines.append('extern "C" {')
|
|
1900
|
+
lines.append("#if defined(PINEFORGE_HAS_RUN_FAILURE_CODES_V1) && defined(PF_SETTINGS_API_VERSION)")
|
|
1901
|
+
lines.append(" int strategy_create_checked(const char* params_json, void** out, char* error, size_t error_capacity);")
|
|
1902
|
+
lines.append("#endif")
|
|
1897
1903
|
lines.append(" void* strategy_create(const char* params_json) {")
|
|
1904
|
+
lines.append("#if defined(PINEFORGE_HAS_RUN_FAILURE_CODES_V1) && defined(PF_SETTINGS_API_VERSION)")
|
|
1905
|
+
lines.append(" void* out = nullptr;")
|
|
1906
|
+
lines.append(" strategy_create_checked(nullptr, &out, nullptr, 0);")
|
|
1907
|
+
lines.append(" return out;")
|
|
1908
|
+
lines.append("#else")
|
|
1898
1909
|
lines.append(" try { return new GeneratedStrategy(); } catch (...) { return nullptr; }")
|
|
1910
|
+
lines.append("#endif")
|
|
1899
1911
|
lines.append(" }")
|
|
1900
1912
|
lines.append(" void run_backtest(void* s, Bar* bars, int n, ReportC* out) {")
|
|
1901
1913
|
lines.append(" try {")
|
|
@@ -1912,7 +1924,7 @@ class TopLevelEmitter:
|
|
|
1912
1924
|
lines.append(" if (!strat->_pf_refuse_failed_setting(out)) strat->fill_report(out);")
|
|
1913
1925
|
lines.append(' } catch (const std::exception& _pf_error) {')
|
|
1914
1926
|
lines.append(' if (out) *out = ReportC{};')
|
|
1915
|
-
lines.append(' if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest", _pf_error
|
|
1927
|
+
lines.append(' if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest", _pf_error);')
|
|
1916
1928
|
lines.append(' } catch (...) {')
|
|
1917
1929
|
lines.append(' if (out) *out = ReportC{};')
|
|
1918
1930
|
lines.append(' if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest", "unknown C++ exception");')
|
|
@@ -1969,7 +1981,7 @@ class TopLevelEmitter:
|
|
|
1969
1981
|
" void run_backtest_full(void* s, Bar* bars, int n, const char* input_tf, const char* script_tf,",
|
|
1970
1982
|
" int bar_magnifier, int magnifier_samples, int magnifier_dist, ReportC* out) {",
|
|
1971
1983
|
" try { _pf_run_backtest_full_impl(s, bars, n, input_tf, script_tf, bar_magnifier, magnifier_samples, magnifier_dist, out); }",
|
|
1972
|
-
' catch (const std::exception& _pf_error) { if (out) *out = ReportC{}; if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest_full", _pf_error
|
|
1984
|
+
' catch (const std::exception& _pf_error) { if (out) *out = ReportC{}; if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest_full", _pf_error); }',
|
|
1973
1985
|
' catch (...) { if (out) *out = ReportC{}; if (s) static_cast<GeneratedStrategy*>(s)->_pf_record_failure("run_backtest_full", "unknown C++ exception"); }',
|
|
1974
1986
|
" }",
|
|
1975
1987
|
])
|
|
@@ -1982,20 +1994,24 @@ class TopLevelEmitter:
|
|
|
1982
1994
|
lines.append(" void strategy_set_input(void* s, const char* key, const char* value) {")
|
|
1983
1995
|
lines.append(" if (!s || !key || !value) return;")
|
|
1984
1996
|
lines.append(" try { static_cast<GeneratedStrategy*>(s)->set_input(key, value); }")
|
|
1985
|
-
lines.append(' catch (const std::exception& _pf_error) { static_cast<GeneratedStrategy*>(s)
|
|
1986
|
-
lines.append(' catch (...) { static_cast<GeneratedStrategy*>(s)
|
|
1997
|
+
lines.append(' catch (const std::exception& _pf_error) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_input", _pf_error.what(), nullptr); }')
|
|
1998
|
+
lines.append(' catch (...) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_input", "unknown C++ exception", nullptr); }')
|
|
1987
1999
|
lines.append(" }")
|
|
1988
2000
|
lines.append(" void strategy_set_override(void* s, const char* key, const char* value) {")
|
|
1989
2001
|
lines.append(" if (!s || !key || !value) return;")
|
|
1990
2002
|
lines.append(" try { static_cast<GeneratedStrategy*>(s)->set_strategy_override(key, value); }")
|
|
1991
|
-
lines.append('
|
|
1992
|
-
lines.append(' catch (
|
|
2003
|
+
lines.append('#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1')
|
|
2004
|
+
lines.append(' catch (const std::invalid_argument& _pf_error) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_override", _pf_error.what(), "unparseable_value"); }')
|
|
2005
|
+
lines.append(' catch (const std::out_of_range& _pf_error) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_override", _pf_error.what(), "unparseable_value"); }')
|
|
2006
|
+
lines.append('#endif')
|
|
2007
|
+
lines.append(' catch (const std::exception& _pf_error) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_override", _pf_error.what(), nullptr); }')
|
|
2008
|
+
lines.append(' catch (...) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_override", "unknown C++ exception", nullptr); }')
|
|
1993
2009
|
lines.append(" }")
|
|
1994
2010
|
lines.append(" void strategy_set_magnifier_volume_weighted(void* s, int on) {")
|
|
1995
2011
|
lines.append(" if (!s) return;")
|
|
1996
2012
|
lines.append(" try { static_cast<GeneratedStrategy*>(s)->set_magnifier_volume_weighted(on != 0); }")
|
|
1997
|
-
lines.append(' catch (const std::exception& _pf_error) { static_cast<GeneratedStrategy*>(s)
|
|
1998
|
-
lines.append(' catch (...) { static_cast<GeneratedStrategy*>(s)
|
|
2013
|
+
lines.append(' catch (const std::exception& _pf_error) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_magnifier_volume_weighted", _pf_error.what(), nullptr); }')
|
|
2014
|
+
lines.append(' catch (...) { _PF_SETTING_FAILURE(static_cast<GeneratedStrategy*>(s), "strategy_set_magnifier_volume_weighted", "unknown C++ exception", nullptr); }')
|
|
1999
2015
|
lines.append(" }")
|
|
2000
2016
|
from .checked_settings import emit_settings_exports
|
|
2001
2017
|
emit_settings_exports(lines)
|
|
@@ -262,12 +262,18 @@ CPP_STANDARD_MACROS = frozenset("""
|
|
|
262
262
|
PINEFORGE_HAS_NATIVE_LOWERING_V1 PINEFORGE_HAS_NATIVE_LIVE_V1
|
|
263
263
|
PINEFORGE_HAS_AUX_SECURITY_FEED_V1 PINEFORGE_HAS_SCRIPT_RUN_PREPARE_V1
|
|
264
264
|
PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1
|
|
265
|
+
PINEFORGE_HAS_RUN_FAILURE_CODES_V1
|
|
266
|
+
_PF_NO_DATA_STOP _PF_OTHER_SYMBOL_STOP _PF_ARRAY_STOP _PF_COLLECTION_STOP
|
|
267
|
+
_PF_NA_STOP _PF_LIMIT_STOP _PF_UNSUPPORTED_STOP _PF_STRING_STOP
|
|
268
|
+
_PF_ENGINE_INVARIANT _PF_INVARIANT_AT _PF_SETTING_FAILURE
|
|
265
269
|
PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1
|
|
266
270
|
PINEFORGE_NO_STRATEGY_DECLS PF_PINE_TIME_HAS_SESSION_DAY
|
|
267
271
|
PF_PINE_TIME_SESSION_DAY_ARGS PF_VWAP_HAS_SESSION_ANCHOR
|
|
268
272
|
PF_VWAP_SESSION_ANCHOR_ARGS PF_ALMA_HAS_FLOOR
|
|
269
273
|
PF_KC_HAS_USE_TRUE_RANGE PF_VWAP_HAS_ANCHOR_INPUT
|
|
270
274
|
PF_PIVOT_LEVELS_HAS_ANCHOR
|
|
275
|
+
PF_ABI_VERSION PF_NATIVE_API_VERSION
|
|
276
|
+
PF_CAPABILITIES_API_VERSION PF_SETTINGS_API_VERSION
|
|
271
277
|
""".split())
|
|
272
278
|
|
|
273
279
|
# Identifiers used by the emitter as class/type names, namespaces, and
|
|
@@ -283,6 +289,9 @@ CPP_EMITTER_NAMES = frozenset("""
|
|
|
283
289
|
commit_script_state set_strategy_override set_input
|
|
284
290
|
set_magnifier_volume_weighted strategy_declares_bar_magnifier fill_report run
|
|
285
291
|
strategy_capabilities_api_version strategy_capabilities_receipt
|
|
292
|
+
strategy_confirmed_bar_api_version strategy_confirmed_bar_receipt
|
|
293
|
+
strategy_order_shapes_api_version strategy_order_shapes_receipt
|
|
294
|
+
strategy_settings_api_version strategy_get_effective_settings
|
|
286
295
|
precalculate
|
|
287
296
|
strategy_entry strategy_close strategy_close_all strategy_exit
|
|
288
297
|
strategy_exit_cancel_bracket strategy_cancel strategy_cancel_all strategy_order
|
|
@@ -291,6 +300,9 @@ CPP_EMITTER_NAMES = frozenset("""
|
|
|
291
300
|
live_position_size pending_order_count market_admission_journal
|
|
292
301
|
pine_time pine_time_close pine_time_tradingday pine_random
|
|
293
302
|
pine_runtime_error pine_enum_str_at pine_session_ismarket
|
|
303
|
+
pine_no_data_stop pine_other_symbol_stop pine_array_stop pine_collection_stop
|
|
304
|
+
pine_na_stop pine_limit_stop pine_unsupported_stop pine_string_stop
|
|
305
|
+
pine_engine_invariant note_run_failure note_run_failure_unknown
|
|
294
306
|
pine_session_ispostmarket pine_session_ispremarket _PFSessionMarket
|
|
295
307
|
_pf_session_market_ script_tf_
|
|
296
308
|
tf_change tf_is_daily tf_is_intraday tf_is_monthly tf_is_seconds
|
|
@@ -317,7 +329,8 @@ CPP_EMITTER_NAMES = frozenset("""
|
|
|
317
329
|
get_input_int get_input_float get_input_bool get_input_string
|
|
318
330
|
_pf_record_failure _pf_refuse_failed_setting _pf_settings_declared_config
|
|
319
331
|
_pf_record_setting_failure _pf_require_settings_ok
|
|
320
|
-
_pf_setting_failed_ _pf_setting_failure_
|
|
332
|
+
_pf_setting_failed_ _pf_setting_failure_ _pf_setting_failure_info_
|
|
333
|
+
_pf_setting_error_base _pf_latched_setting_error _pf_invariant_at
|
|
321
334
|
_pf_settings_inputs _pf_settings_overrides _pf_set_input_checked
|
|
322
335
|
_pf_set_override_checked _pf_settings_receipt
|
|
323
336
|
_pf_close_entries_rule_word _pf_default_qty_type_word _pf_commission_type_word
|
|
@@ -369,12 +382,16 @@ CPP_TEMPORARY_NAMES = frozenset("""
|
|
|
369
382
|
__pf_s __pf_r __pf_p __pf_i __pf_j __pf_t __pf_it __pf_m __pf_c __pf_n
|
|
370
383
|
__pf_k __pf_b __pf_ma __pf_mb __pf_f __pf_d __pf_best __pf_bc __pf_rank
|
|
371
384
|
__pf_idx __pf_occ
|
|
385
|
+
_pf_number_text
|
|
386
|
+
_pf_substring_text _pf_substring_begin _pf_substring_end _pf_substring_error
|
|
387
|
+
_pf_array_new_size _pf_array_new_value _pf_array_new_size_type _pf_array_new_error
|
|
372
388
|
""".split())
|
|
373
389
|
_CPP_TEMPORARY_PATTERN = re.compile(
|
|
374
390
|
r"_v\d+|_secval_\d+(?:_v\d+)?|_tuple_result_\d+|_tuple_unused_\d+|__switch_val_\d+"
|
|
375
391
|
r"|_for_(?:start|end|end_eval)_\d+|_pf_(?:str|array|round)_a\d+"
|
|
376
392
|
r"|_pf_every_bar_ta_\d+|_pf_shared_\d+_\d+|__pf_array\w*|__pf_raw_\w+"
|
|
377
393
|
r"|_pf_collection_hist_\w+"
|
|
394
|
+
r"|_pf_substring_arg\d+"
|
|
378
395
|
)
|
|
379
396
|
|
|
380
397
|
|
|
@@ -432,6 +449,7 @@ class NamingHelper:
|
|
|
432
449
|
.replace("\n", "\\n")
|
|
433
450
|
.replace("\r", "\\r")
|
|
434
451
|
.replace("\t", "\\t")
|
|
452
|
+
.replace("\0", "\\000")
|
|
435
453
|
)
|
|
436
454
|
|
|
437
455
|
@staticmethod
|
|
@@ -148,6 +148,7 @@ HOST_MEMBER_NAMES = frozenset({
|
|
|
148
148
|
"set_pine_risk_max_intraday_filled_orders",
|
|
149
149
|
"set_pine_risk_max_intraday_loss",
|
|
150
150
|
"set_pine_risk_max_position_size",
|
|
151
|
+
"set_recorded_series",
|
|
151
152
|
"set_strategy_override",
|
|
152
153
|
"signed_position_size",
|
|
153
154
|
"snapshot_script_state",
|
|
@@ -25,6 +25,7 @@ from ..ast_nodes import (
|
|
|
25
25
|
MemberAccess,
|
|
26
26
|
NumberLiteral,
|
|
27
27
|
StringLiteral,
|
|
28
|
+
UnaryOp,
|
|
28
29
|
VarDecl,
|
|
29
30
|
)
|
|
30
31
|
from .. import signatures as sigs
|
|
@@ -453,7 +454,7 @@ class InputHelper:
|
|
|
453
454
|
# Input manifest extraction (host UI override-form source of truth)
|
|
454
455
|
# ------------------------------------------------------------------
|
|
455
456
|
|
|
456
|
-
def _literal_or_none(self, node):
|
|
457
|
+
def _literal_or_none(self, node, *, signed: bool = False):
|
|
457
458
|
"""Return a JSON scalar for a *const* literal AST node, else None.
|
|
458
459
|
|
|
459
460
|
``None`` signals non-const (an identifier, computed expression, …) so
|
|
@@ -469,6 +470,10 @@ class InputHelper:
|
|
|
469
470
|
return node.value
|
|
470
471
|
if isinstance(node, NumberLiteral):
|
|
471
472
|
return node.value
|
|
473
|
+
if signed and isinstance(node, UnaryOp) and node.op in ("+", "-"):
|
|
474
|
+
value = self._literal_or_none(node.operand, signed=True)
|
|
475
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
476
|
+
return value if node.op == "+" else -value
|
|
472
477
|
# enum member ref like ``Dir.Up`` -> "Dir.Up" (string tag)
|
|
473
478
|
if isinstance(node, MemberAccess) and isinstance(node.object, Identifier):
|
|
474
479
|
return f"{node.object.name}.{node.member}"
|
|
@@ -503,12 +508,17 @@ class InputHelper:
|
|
|
503
508
|
``var = input.*(...)`` declaration and an inline call inside an
|
|
504
509
|
expression (``ta.ema(close, input.int(9, "Fast"))``) alike.
|
|
505
510
|
|
|
506
|
-
Each entry: ``{title, type, default[, min, max, step,
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
511
|
+
Each entry: ``{title, type, default, supported[, min, max, step,
|
|
512
|
+
options]}``. ``supported``, a string input's default and options, a
|
|
513
|
+
source input's default and choices, an enum's choices and a typed
|
|
514
|
+
numeric input's literal default, bounds and options are the values
|
|
515
|
+
of the checked-settings receipt (the descriptor ``generate()``
|
|
516
|
+
builds with it). The optional keys are emitted only when the
|
|
517
|
+
corresponding signature argument is a const literal; a bound/option
|
|
518
|
+
referencing a non-literal is omitted (never crashes). ``title`` is
|
|
519
|
+
the key the emitted C++ reads the input by: the title argument, else
|
|
520
|
+
the name of the declaration holding the call
|
|
521
|
+
(``pine_spelling.input_binding_names``), else "".
|
|
512
522
|
"""
|
|
513
523
|
return [self._input_manifest_entry(node, name)
|
|
514
524
|
for node, name in self._global_input_calls_with_names()]
|
|
@@ -528,7 +538,7 @@ class InputHelper:
|
|
|
528
538
|
title = self._get_input_title(node, var_name=var_name)
|
|
529
539
|
default_node = self._get_input_default(node)
|
|
530
540
|
default_val = (
|
|
531
|
-
self._literal_or_none(default_node)
|
|
541
|
+
self._literal_or_none(default_node, signed=True)
|
|
532
542
|
if default_node is not None
|
|
533
543
|
else None
|
|
534
544
|
)
|
|
@@ -581,4 +591,9 @@ class InputHelper:
|
|
|
581
591
|
# any non-const element -> omit the whole options list
|
|
582
592
|
if vals and all(isinstance(v, str) for v in vals):
|
|
583
593
|
entry["options"] = vals
|
|
594
|
+
descriptors = getattr(self, "_input_settings_metadata", None)
|
|
595
|
+
if descriptors is None:
|
|
596
|
+
raise RuntimeError("the input manifest reads the settings descriptors "
|
|
597
|
+
"generate() builds: call generate() first")
|
|
598
|
+
entry.update(descriptors[id(node)])
|
|
584
599
|
return entry
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Optional coded run stops, with the original throws on older engines."""
|
|
2
|
+
|
|
3
|
+
from .helpers import NamingHelper
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUN_STOP_SHIMS_CPP = r"""
|
|
7
|
+
#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1
|
|
8
|
+
#define _PF_NO_DATA_STOP(function, call, line, english) ::pineforge::pine_no_data_stop(function, call, line, english)
|
|
9
|
+
#define _PF_OTHER_SYMBOL_STOP(function, symbol, call, line, english) ::pineforge::pine_other_symbol_stop(function, symbol, call, line, english)
|
|
10
|
+
#define _PF_ARRAY_STOP(reason, method, english) ::pineforge::pine_array_stop(reason, method, std::string(english).c_str())
|
|
11
|
+
#define _PF_COLLECTION_STOP(reason, object, english) ::pineforge::pine_collection_stop(object, reason, english)
|
|
12
|
+
#define _PF_NA_STOP(object, english) ::pineforge::pine_na_stop(object, english)
|
|
13
|
+
#define _PF_LIMIT_STOP(limit, max, english) ::pineforge::pine_limit_stop(limit, max, english)
|
|
14
|
+
#define _PF_UNSUPPORTED_STOP(reason, line, english) ::pineforge::pine_unsupported_stop(reason, line, english)
|
|
15
|
+
#define _PF_STRING_STOP(reason, english) ::pineforge::pine_string_stop(reason, english)
|
|
16
|
+
#define _PF_ENGINE_INVARIANT(english, legacy_type) ::pineforge::pine_engine_invariant(english)
|
|
17
|
+
#define _PF_INVARIANT_AT(container, index) _pf_invariant_at(container, index)
|
|
18
|
+
#define _PF_SETTING_FAILURE(strategy, entrypoint, message, reason) (strategy)->_pf_record_setting_failure(entrypoint, message, [] { return reason ? ::pineforge::RunFailureInfo(::pineforge::RunFailureCode::setting_rejected, {{"entrypoint", entrypoint}, {"reason", reason}}) : ::pineforge::RunFailureInfo(::pineforge::RunFailureCode::setting_rejected, {{"entrypoint", entrypoint}}); })
|
|
19
|
+
template <typename Container, typename Index>
|
|
20
|
+
decltype(auto) _pf_invariant_at(Container& container, Index index) {
|
|
21
|
+
try { return container.at(index); }
|
|
22
|
+
catch (const std::out_of_range& error) { ::pineforge::pine_engine_invariant(error.what()); }
|
|
23
|
+
}
|
|
24
|
+
#else
|
|
25
|
+
#define _PF_NO_DATA_STOP(function, call, line, english) pine_runtime_error(std::string(english))
|
|
26
|
+
#define _PF_OTHER_SYMBOL_STOP(function, symbol, call, line, english) pine_runtime_error(std::string(english))
|
|
27
|
+
#define _PF_ARRAY_STOP(reason, method, english) pine_runtime_error(english)
|
|
28
|
+
#define _PF_COLLECTION_STOP(reason, object, english) pine_runtime_error(english)
|
|
29
|
+
#define _PF_NA_STOP(object, english) throw std::runtime_error(english)
|
|
30
|
+
#define _PF_LIMIT_STOP(limit, max, english) throw std::length_error(english)
|
|
31
|
+
#define _PF_UNSUPPORTED_STOP(reason, line, english) pine_runtime_error(std::string(english))
|
|
32
|
+
#define _PF_STRING_STOP(reason, english) pine_runtime_error(std::string(english))
|
|
33
|
+
#define _PF_ENGINE_INVARIANT(english, legacy_type) throw legacy_type(english)
|
|
34
|
+
#define _PF_INVARIANT_AT(container, index) (container).at(index)
|
|
35
|
+
#define _PF_SETTING_FAILURE(strategy, entrypoint, message, reason) (strategy)->_pf_record_setting_failure(entrypoint, message)
|
|
36
|
+
#endif
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def request_stop(marker: dict) -> str:
|
|
41
|
+
"""Metadata comes only from the original request's compile-time spelling."""
|
|
42
|
+
escape = NamingHelper._cpp_string_escape
|
|
43
|
+
function = f'"{escape(marker["function"])}"'
|
|
44
|
+
call = f'"{escape(marker["call"])}"'
|
|
45
|
+
english = f'"{escape(marker["message"])}"'
|
|
46
|
+
arguments = f'{function}, {call}, {marker["line"]}, {english}'
|
|
47
|
+
if marker["kind"] == "other_symbol":
|
|
48
|
+
literal = marker["symbol_literal"]
|
|
49
|
+
symbol = "nullptr" if literal is None else f'"{escape(literal)}"'
|
|
50
|
+
arguments = f'{function}, {symbol}, {call}, {marker["line"]}, {english}'
|
|
51
|
+
return f"_PF_OTHER_SYMBOL_STOP({arguments})"
|
|
52
|
+
return f"_PF_NO_DATA_STOP({arguments})"
|
|
@@ -2655,7 +2655,7 @@ class SecurityEmitter:
|
|
|
2655
2655
|
|
|
2656
2656
|
def _security_expr_hist_series_names(self, sec_id: int) -> list[str]:
|
|
2657
2657
|
names = []
|
|
2658
|
-
for (sid, _node_id), meta in
|
|
2658
|
+
for (sid, _node_id), meta in self._security_expr_hist_by_node.items():
|
|
2659
2659
|
if sid == sec_id:
|
|
2660
2660
|
names.append(meta["name"])
|
|
2661
2661
|
return names
|
|
@@ -7227,7 +7227,8 @@ class SecurityEmitter:
|
|
|
7227
7227
|
"request.security: a Heikin-Ashi request inside another request's "
|
|
7228
7228
|
"expression reads that request's Heikin-Ashi bars, which PineForge "
|
|
7229
7229
|
"does not build")
|
|
7230
|
-
|
|
7230
|
+
line = expr_node.loc.line if expr_node.loc else 1
|
|
7231
|
+
return (f'([&]() -> {cpp_t} {{ _PF_UNSUPPORTED_STOP("nested_heikinashi_request", {line}, "{message}"); '
|
|
7231
7232
|
f"return {na_value}; }}())")
|
|
7232
7233
|
|
|
7233
7234
|
site = self._get_ta_site(expr_node)
|
|
@@ -7886,8 +7887,9 @@ class SecurityEmitter:
|
|
|
7886
7887
|
def _security_variant_order_key(self, signature: tuple, binding_stack) -> tuple:
|
|
7887
7888
|
"""The order of a TA site's requested-context variants (``_v0``,
|
|
7888
7889
|
``_v1``, ...): where each argument binding's value is written in the
|
|
7889
|
-
source, then the signature
|
|
7890
|
-
|
|
7890
|
+
source, then the signature's stable scope snapshot, preserving
|
|
7891
|
+
traversal order at ties. The signature names a bound node by its
|
|
7892
|
+
``id()``, so ordering by its raw ``repr`` followed memory addresses, and
|
|
7891
7893
|
the same script could number its variants differently from run to
|
|
7892
7894
|
run or between CPython and Pyodide."""
|
|
7893
7895
|
frames = []
|
|
@@ -7909,4 +7911,9 @@ class SecurityEmitter:
|
|
|
7909
7911
|
frames.append(("arguments", tuple(items)))
|
|
7910
7912
|
else:
|
|
7911
7913
|
frames.append(("locals", tuple(sorted(frame.keys()))))
|
|
7912
|
-
|
|
7914
|
+
def stable_part(value):
|
|
7915
|
+
if isinstance(value, tuple):
|
|
7916
|
+
return tuple(stable_part(item) for item in value)
|
|
7917
|
+
return 0 if isinstance(value, int) else value
|
|
7918
|
+
|
|
7919
|
+
return tuple(frames), repr(stable_part(signature))
|