Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions pineforge_codegen/codegen/visit_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,54 @@ def _visit_subscript(self, node: Subscript) -> str:
if cpp_t not in ("double", "int", "bool"):
cpp_t = "double"
member = self._inline_history_member("hist_call", node)
ta_site = self._get_ta_site(node.object)
ta_name = (
self._ta_name_from_site(ta_site)
if ta_site is not None
else ""
)
if (
ta_site is not None
and ta_name in {"highest", "lowest"}
and self._ta_site_uses_precalc(ta_site)
):
# Static chart Highest/Lowest sites already advance in the
# full-bar precalculation pass, including when the source call
# sits behind a Pine-v6 lazy boolean edge. Their direct history
# operator must use that same chart-bar clock. Advancing only
# ``_hist_call`` when the lazy RHS is reached changes ``[1]``
# into "previous evaluation" and can permanently miss a first
# qualifying signal (Filter V2's source-bound v6 oracle).
#
# Keep evaluation lazy: this branch only reads the immutable
# precalculated result when the expression is reached. Dynamic
# runs, UDF sites, and non-precalculated calls retain the
# established call-local history fallback below.
ta_mem = self._ta_member_name(ta_site)
precalc = f"_precalc_{ta_mem}"
return (
f"([&]() -> {cpp_t} {{ "
f"if (_use_precalc) {{ "
f"auto _pf_hist_offset_raw = ({idx}); "
f"if (is_na(_pf_hist_offset_raw)) return na<{cpp_t}>(); "
f"long double _pf_hist_offset_numeric = "
f"static_cast<long double>(_pf_hist_offset_raw); "
f"if (bar_index_ < 0 || _pf_hist_offset_numeric < 0.0L || "
f"_pf_hist_offset_numeric > "
f"static_cast<long double>(bar_index_)) "
f"return na<{cpp_t}>(); "
f"std::size_t _pf_hist_offset = "
f"static_cast<std::size_t>(_pf_hist_offset_numeric); "
f"std::size_t _pf_hist_bar = "
f"static_cast<std::size_t>(bar_index_) - _pf_hist_offset; "
f"if (_pf_hist_bar >= {precalc}.size()) "
f"return na<{cpp_t}>(); "
f"return {precalc}[(std::size_t)_pf_hist_bar]; }} "
f"{cpp_t} _hv = ({inner}); "
f"if (history_advances_new_bar()) {member}.push(_hv); "
f"else {member}.update(_hv); "
f"return {member}[(int)({idx})]; }}())"
)
return (
f"([&]() -> {cpp_t} {{ "
f"{cpp_t} _hv = ({inner}); "
Expand Down
42 changes: 42 additions & 0 deletions tests/test_codegen_validation_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,48 @@ def test_ta_precalc_lazy_scope_routes_recursive_ema_only():
assert len(re.findall(r"_use_precalc \? _precalc__ta_sma_", cpp)) >= 2


def test_precalculated_extrema_direct_history_uses_chart_bar_clock():
"""``ta.highest/lowest(...)[k]`` must index their precalculated series.

The surrounding Pine-v6 boolean remains lazy. Only the already-computed
direct-call history changes from a sparse evaluation clock to the chart-bar
clock used by the same TA site's ``_precalc_*`` values.
"""
cpp = _cpp(
"pred = close > open\n"
"lo = pred and close < ta.lowest(low, 20)[1]\n"
"hi = pred and close > ta.highest(high, 10)[2]\n"
"plot((lo or hi) ? 1 : 0)"
)

assert "pred &&" in cpp
assert "std::vector<double> _precalc__ta_lowest_1" in cpp
assert "std::vector<double> _precalc__ta_highest_2" in cpp
assert "static_cast<std::size_t>(bar_index_) - _pf_hist_offset" in cpp
assert "_pf_hist_offset_numeric < 0.0L" in cpp
assert "static_cast<long double>(bar_index_)" in cpp
assert "return _precalc__ta_lowest_1[(std::size_t)_pf_hist_bar]" in cpp
assert "return _precalc__ta_highest_2[(std::size_t)_pf_hist_bar]" in cpp
# Dynamic/non-precalculated execution retains the rollback-safe synthetic
# history fallback rather than silently becoming eager.
assert cpp.count("if (history_advances_new_bar()) _hist_call_") >= 2


def test_nonprecalculated_extrema_direct_history_keeps_evaluation_clock():
"""Unsafe extrema inputs must retain their call-local history clock."""
cpp = _cpp(
"src = close\n"
"pred = close > open\n"
"signal = pred and close > ta.highest(src, 20)[1]\n"
"plot(signal ? 1 : 0)"
)

assert "std::vector<double> _precalc__ta_highest" not in cpp
assert "_pf_hist_bar" not in cpp
assert "_ta_highest_1.compute(src)" in cpp
assert "if (history_advances_new_bar()) _hist_call_" in cpp


def test_recursive_ema_lazy_edges_preserve_eager_operand_positions():
"""Only OR RHS / ternary arms opt out; eager positions still precalc."""
cpp = _cpp(
Expand Down
Loading