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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ _deps/
__pycache__/
*.pyc

# Playwright MCP traces
# MCP tool traces
.playwright-mcp/

# Tutorial backtest output
Expand All @@ -74,6 +74,6 @@ docs/production-readiness-findings.md
.ccache/
ci-diagnostics/

# Closed (TradingView-scraped, not redistributable) benchmark slots: maintainer-local only,
# Closed (third-party TradingView, not redistributable) benchmark slots: maintainer-local only,
# published to the maintainers' evidence store as a tarball (sha in benchmarks/results/selection.md).
benchmarks/assets-closed/
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ elseif(PINEFORGE_BUILD_BENCH_STRATEGIES)
"${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/assets/strategies/CMakeLists.txt")
if(EXISTS "${_bench_strat_cmake}")
add_subdirectory(benchmarks/assets/strategies)
# Maintainer-local closed slots (TradingView-scraped, never public):
# Maintainer-local closed slots (third-party TradingView, never public):
# built into the same bench_strategies target when the root exists.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/assets-closed/strategies/CMakeLists.txt")
add_subdirectory(benchmarks/assets-closed/strategies)
Expand Down
2 changes: 1 addition & 1 deletion include/pineforge/run_failure.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// typed arguments as one canonical JSON object
// (strategy_get_last_error_args()). The vocabulary, its classes, its argument
// kinds and closed value lists, and the English each code is written with,
// are published as docker/run_failure_codes.json (schema
// are published as run_failure_codes.json (schema
// pineforge-run-failure-catalog/v1); include/pineforge/run_failure_codes.hpp
// and src/run_failure_registry.inc are generated from it
// (scripts/check_run_failure_codes.py --write-registry) and the source guard
Expand Down
4 changes: 2 additions & 2 deletions include/pineforge/run_failure_codes.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by scripts/check_run_failure_codes.py --write-registry from
// docker/run_failure_codes.json. Do not edit: edit the catalog and
// run_failure_codes.json. Do not edit: edit the catalog and
// regenerate.

#pragma once
Expand All @@ -8,7 +8,7 @@

namespace pineforge {

// The closed run-failure vocabulary (docker/run_failure_codes.json),
// The closed run-failure vocabulary (run_failure_codes.json),
// in catalog order. `none` is no failure. The numbers are not on any
// wire: the C getters answer with the names.
enum class RunFailureCode : std::uint16_t {
Expand Down
3 changes: 1 addition & 2 deletions runner/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,7 @@ if(PINEFORGE_BUILD_TESTS AND PINEFORGE_BUILD_SOURCE_LAYER)
htf60_ema_gaps period_previous pooc_dual_stop pooc_limit_reversal pooc_slipped_short
pooc_short_exit constant_daily helper5 heikinashi_alias5 varip pooc_market pooc_close pooc_close_entry
pooc_close_short pooc_close_short_entry)
file(GLOB _pf_confirmed_refusal_sources CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/tests/fixtures/confirmed_capabilities/generated/refused_*.cpp")
include("${PROJECT_SOURCE_DIR}/tests/confirmed_capabilities_refusals.cmake")
set(_pf_confirmed_refusals)
foreach(refusal_source IN LISTS _pf_confirmed_refusal_sources)
get_filename_component(refusal_case "${refusal_source}" NAME_WE)
Expand Down
6 changes: 3 additions & 3 deletions scripts/check_run_failure_codes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""The run-failure code registry, held to its published catalog.

`docker/run_failure_codes.json` (schema pineforge-run-failure-catalog/v1) is
`run_failure_codes.json` (schema pineforge-run-failure-catalog/v1) is
the one source of the run-failure vocabulary: every code's class, retryable
flag, arguments (kind, closed values, optional), English templates and the
release it appeared in. The engine compiles two files generated from it:
Expand Down Expand Up @@ -246,7 +246,7 @@ def validate_catalog(catalog: dict, text: str | None = None) -> list[str]:


HEADER_BANNER = ("// Generated by scripts/check_run_failure_codes.py --write-registry from\n"
"// docker/run_failure_codes.json. Do not edit: edit the catalog and\n"
"// run_failure_codes.json. Do not edit: edit the catalog and\n"
"// regenerate.\n")


Expand All @@ -258,7 +258,7 @@ def render_codes_header(catalog: dict) -> str:
names = ordered_codes(catalog)
lines = [HEADER_BANNER, "#pragma once\n", "#include <cstdint>\n",
"namespace pineforge {\n",
"// The closed run-failure vocabulary (docker/run_failure_codes.json),",
"// The closed run-failure vocabulary (run_failure_codes.json),",
"// in catalog order. `none` is no failure. The numbers are not on any",
"// wire: the C getters answer with the names.",
"enum class RunFailureCode : std::uint16_t {",
Expand Down
48 changes: 23 additions & 25 deletions scripts/run_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ def _num(v):
syminfo_metadata["qty_step"] = qty_step
# syminfo.mincontract: on every campaign symbol TradingView's smallest
# contract IS its quantity step (tests/fixtures/syminfo_mincontract: 18
# lab tv read-outs, one per lane -- BINANCE:ETHUSDT.P 0.0001,
# reference read-outs, one per lane -- BINANCE:ETHUSDT.P 0.0001,
# BINANCE:BTCUSDT 0.00001, OANDA:EURUSD / OANDA:XAUUSD 0.01, NASDAQ:AAPL,
# NYSE:F, NSE:NIFTY, CME_MINI:ES1! and NQ1! 1). The fact is the lane
# template's: its environment's PINEFORGE_VERIFY_QTY_STEP
Expand Down Expand Up @@ -1465,7 +1465,7 @@ def at(value) -> Path:
# PINEFORGE_REQUESTS_ROOT=<dir>, holding <dir>/<slug>/requests.json
# (pineforge-probe-requests/v1) and <dir>/<slug>/files/<sha256> -- workflow
# docs/xsym-requests.md, "The environment contract". The frozen verifier
# (pineforge-lab 3bac0b7b scripts/verify-engine-local.py) names each probe's
# (the external verifier that defines this contract) names each probe's
# build directory ``bd = BUILD / slug`` (:1695) and hands ``str(bd)`` to this
# script (:1593-1596, :1614-1617) through ``_run_timed`` (:190-193), whose
# subprocess.run passes no env=, so the variable reaches this process and the
Expand Down Expand Up @@ -3137,10 +3137,8 @@ def _filter_trace_to_window(trace: list[dict], window: tuple[int, int] | None) -
def _tv_metrics_path(strategy_dir: Path, meta: dict, tv_path: Path) -> Path:
"""Where the tape's metrics.json lives: ``tv_metrics_json`` from the
probe's inputs.json when set (relative to ``strategy_dir``), else the
``metrics.json`` beside the tape — the scrapper's probe directory, the
case runner's ``data/<group>/<slug>/`` copy and ``lab tv``'s export all
write the four files (strategy.pine, tv_trades.csv, metrics.json,
meta.json) side by side."""
``metrics.json`` beside the tape — every producer writes the four files
(strategy.pine, tv_trades.csv, metrics.json, meta.json) side by side."""
name = str(meta.get("tv_metrics_json", "")).strip()
if name:
return strategy_dir / name
Expand All @@ -3152,10 +3150,10 @@ def _tv_metrics_range_end(metrics: dict) -> tuple[int, str] | None:
with the key it was read from: the ``to`` of the deep-backtest window,
as 00:00 UTC of that date.

Two spellings carry it. The ws-report-v1 exporter records the exact
window it sent to TradingView as ``wsProvenance.requestedRange.to`` (ms
Two spellings carry it. One records the exact requested
window as ``wsProvenance.requestedRange.to`` (ms
UTC), and every campaign tape's value is 1777593600000 = 2026-05-01
00:00 UTC for ``"to": "2026-05-01"``; the browser export (ETH, AAPL 15,
00:00 UTC for ``"to": "2026-05-01"``; the other (ETH, AAPL 15,
EURUSD) carries only the date. They agree wherever both exist, and the
integer is what TradingView actually saw, so it wins. None without a
range (a hand-made metrics.json, or none)."""
Expand Down Expand Up @@ -3205,12 +3203,12 @@ def _tv_tape_marks_open_rows(strategy_dir: Path, meta: dict) -> bool:
"""True when the tape MARKS a position still open at the range's end:
any row whose ``Signal`` is ``open`` (case- and space-insensitive).

Two exporters write the campaign's tapes, and they print the range-end
position differently. The ws-report-v1 exporter (286 of the 305 ETH
Two tape shapes appear in the campaign's tapes, and they print the range-end
position differently. One shape (286 of the 305 ETH
tapes; no marker in metrics.json beyond ``tapeChannel``) reports it as
an ordinary closed trade — exit row on the last bar at its close, EMPTY
Signal, counted in closedTrades — which is the shape the engine's
``open_at_end`` row emulates. The browser export (the scrapper's
``open_at_end`` row emulates. The other shape (the
3commas grid-bot tapes among them, 22/22/28 open lots each at
2026-05-01 08:00 +8; metrics.json without ``tapeChannel``) writes the
same row with Signal "Open". TV nets commission on BOTH legs of that row
Expand Down Expand Up @@ -3244,7 +3242,7 @@ def _load_tv_range_end(strategy_dir: Path, meta: dict) -> tuple[int, str] | None
(_apply_range_end_regime), whichever way the tape spells its range-end
row.

The rule is TradingView's own, read off the ws-report-v1 tapes'
The rule is TradingView's own, read off the reference tapes'
``returnedRange.to`` (the open of the last bar TV reported) against the
``requestedRange.to`` = 2026-05-01 00:00 UTC every campaign tape asked
for, and off where each lane's open-position row sits:
Expand All @@ -3261,12 +3259,12 @@ def _load_tv_range_end(strategy_dir: Path, meta: dict) -> tuple[int, str] | None
OANDA:XAUUSD 1D returned.to = 04-30 21:00 UTC;
NYSE:F 15 returned.to = 04-30 19:45 UTC;

and every open-position row (browser "Open" rows and ws rows with an
and every open-position row (date-only "Open" rows and unmarked rows with an
empty Signal alike) sits on exactly that bar: BTC/ES/NQ/XAU/EURUSD 15
and BINANCE:ETHUSDT.P 15 at 05-01 00:00 UTC (ETH: 228 of 397 scrapper
and BINANCE:ETHUSDT.P 15 at 05-01 00:00 UTC (ETH: 228 of 397 reference
tapes, @ 2261.44 = that bar's close), F 1D at 04-30 13:30 (@ 12.08),
ES/NQ 1D at 04-30 22:00, XAU 1D at 04-30 21:00, NIFTY 1D at 04-30
03:45, AAPL 15 at 04-30 19:45. Survey of 2026-09-02 over the scrapper's
03:45, AAPL 15 at 04-30 19:45. Survey of 2026-09-02 over the campaign's
17 lane directories (1,356 tapes; every one ``to: 2026-05-01``).

This bounds the feed the engine measures. The engine books a position
Expand Down Expand Up @@ -3349,10 +3347,10 @@ def _apply_range_end_regime(strategy_dir: Path, meta: dict, run_kwargs: dict) ->
rows leaves the feed as given.

The tape's spelling of that row no longer picks a regime. The
ws-report-v1 exporter prints the range-end position as an ordinary
reference tape prints the range-end position as an ordinary
closed trade (empty Signal, counted in closedTrades — the shape the
engine's ``open_at_end`` row emulates); the browser export prints the
same row with Signal "Open" (227 of the 396 scraped ETH tapes). Under
engine's ``open_at_end`` row emulates); the date-only tape prints the
same row with Signal "Open" (227 of the 396 reference ETH tapes). Under
v4 a marked tape was measured as the baseline had measured it — feed
unbounded three days past the range, the engine's marks withheld —
because the canonical grader paired the baseline's post-range closes
Expand Down Expand Up @@ -3450,7 +3448,7 @@ def format_trade_qty(qty: float) -> str:
# engine_orders.cpp record_range_end_close_trades) is
# ENGINE_RANGE_END_OPEN; every other cell is empty. The grader
# (scripts/verify_corpus.py ENGINE_RANGE_END_COLUMN) pairs such a row with
# the tape's own mark of the same lot — the browser export's Signal "Open".
# the tape's own mark of the same lot — the date-only tape's Signal "Open".
ENGINE_RANGE_END_COLUMN = "Engine range-end"
ENGINE_RANGE_END_OPEN = "open"

Expand All @@ -3468,13 +3466,13 @@ def write_engine_trades_csv(trades: list[dict], path: Path) -> None:

A trade flagged ``open_at_end`` (the engine's range-end close of a
position still open after the final bar) is written like any other
trade — TradingView's ws-report-v1 tape reports that position as an
trade — TradingView's reference tape reports that position as an
ordinary closed trade whose exit row is the last bar at its close with
an EMPTY Signal (orb-lite NYSE:F 1D — Exit short 2026-04-30 @ 12.08),
so the row must look like every other exit for the verifier to pair
it — with one addition: its exit row says so in the trailing
``Engine range-end`` column (ENGINE_RANGE_END_OPEN, "open"), the
engine's counterpart of the browser export's Signal "Open". The grader
engine's counterpart of the date-only tape's Signal "Open". The grader
pairs the two marks of one lot and gates neither exit nor pnl on them
(verify_corpus.py pair_range_end_marks); every other cell of the column
is empty, and readers keyed by column name (verify_corpus.parse_trades,
Expand Down Expand Up @@ -3614,10 +3612,10 @@ def _tv_first_bar(strategy_dir: Path, meta: dict) -> tuple[int, str] | None:
"""TradingView's first computed chart bar for the tape (UTC ms), with
where it was read: metrics.json ``wsProvenance.returnedRange.from``.

TradingView's deep backtest over the ws report channel computes the
TradingView's deep backtest in a recorded reference run computes the
script from the first chart bar at or after the requested ``from`` and
from no earlier bar, and records that bar's open as
``returnedRange.from``. Pinned by ``lab tv --no-note`` tapes
``returnedRange.from``. Pinned by recorded reference tapes
(tests/fixtures/run_harness_window): a script reads bar_index 0 on it
(rh-diag-firstbar, rh-diag-firstbar2 on BINANCE:ETHUSDT.P 15 over
2025-04-01 .. 2025-04-08 and .. 2026-05-01, OANDA:EURUSD / OANDA:XAUUSD /
Expand All @@ -3627,7 +3625,7 @@ def _tv_first_bar(strategy_dir: Path, meta: dict) -> tuple[int, str] | None:
runs: a stop, a limit and two market entries placed on 2025-03-31 bars
yield no trade (rh-prerange-stop, rh-prerange-limit, rh-prerange-fills).

None for a tape without it: the browser export records no returned range
None for a tape without it: the date-only reference records no returned range
(and its deep backtest computes bars before ``from``: an entry filled
one bar before the range start is exported), a hand-made metrics.json,
or none."""
Expand Down
6 changes: 3 additions & 3 deletions scripts/symbol_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
holidays (Good Friday), the rest of an early-close day (July 4, the Friday
after Thanksgiving) -- and TradingView reads that calendar for the bar after
the current one: ``time("", "", -1)``, ``session.islastbar`` and
``session.islastbar_regular`` (lab tv te_session_calendar_xau15-*, lane
``session.islastbar_regular`` (reference te_session_calendar_xau15-*, lane
TAIL-E). NASDAQ:AAPL's calendar carries the exchange's holidays and early
closes, which its feed shows too.

A calendar is captured from TradingView by a synthetic script
(tests/fixtures/symbol_calendar/xc-cal-*/strategy.pine, ``lab tv --no-note``)
(tests/fixtures/symbol_calendar/xc-cal-*/strategy.pine, recorded reference runs)
whose first chart bar of every session day spells that day's ``time("D")`` and
``time_close("D")`` and the next three days', so a day the chart holds no bar
of is read from the day before it. ``calendar_from_tape`` turns the tape into
Expand Down Expand Up @@ -79,7 +79,7 @@ def _sha256(path: Path) -> str:


def calendar_document(tape_dir: Path) -> dict:
"""The calendar document of a ``lab tv`` export directory."""
"""The calendar document of a reference run directory."""
metrics = json.loads((tape_dir / "metrics.json").read_text(encoding="utf-8"))
provenance = metrics.get("wsProvenance") or {}
sessions = calendar_from_tape(tape_dir / "tv_trades.csv")
Expand Down
8 changes: 4 additions & 4 deletions scripts/verify_corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class TradePair:
mfe: float = 0.0
mae: float = 0.0
# The exit leg is a mark of a position still open at the range's end,
# not a fill: TradingView's browser export says so with Signal "Open"
# not a fill: a date-only TradingView tape says so with Signal "Open"
# on the exit row; the engine says so in the exit row's trailing
# ``Engine range-end`` column (ENGINE_RANGE_END_COLUMN; its range-end
# close, ``open_at_end``). pair_range_end_marks pairs the two marks of
Expand Down Expand Up @@ -423,8 +423,8 @@ def parse_dt(s: str, tz) -> int:
# engine_trades.csv (run_strategy.py write_engine_trades_csv), "open" on the
# exit row of a trade the engine booked as its range-end close of a position
# still open after the final bar (pf_trade_t::open_at_end), empty elsewhere.
# TradingView's browser export marks the same row Signal "Open"; its
# ws-report-v1 export marks nothing and prices the row at the last bar's
# A date-only TradingView tape marks the same row Signal "Open"; a
# reference tape marks nothing and prices the row at the last bar's
# close, which is exactly the engine's row, so an unmarked tape pairs the
# engine's row as an ordinary closed trade and never sees this column.
ENGINE_RANGE_END_COLUMN = "Engine range-end"
Expand Down Expand Up @@ -548,7 +548,7 @@ def pair_range_end_marks(
return ``(pairs, tv_rest, eng_rest)`` — the raw rows of both sides with
the paired marks taken out.

A browser export prints a position still open at the range's end as a
A date-only TradingView tape prints a position still open at the range's end as a
trade whose exit row is Signal "Open", priced at the range end's last
close and netted of commission on both legs, to the cent (3commas-xlm
#615: qty 0.0912, entry 2189.13, mark 2261.44 -> 6.3511, TV 6.35). The
Expand Down
2 changes: 1 addition & 1 deletion src/run_failure_registry.inc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by scripts/check_run_failure_codes.py --write-registry from
// docker/run_failure_codes.json. Do not edit: edit the catalog and
// run_failure_codes.json. Do not edit: edit the catalog and
// regenerate.

// The registry src/run_failure.cpp validates every raised code against:
Expand Down
4 changes: 2 additions & 2 deletions src/ta_extremes_volume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ ExtremeRing::Result ExtremeRing::update(double src, bool advance, bool want_max)
// cadence's ta.highest(-close, 10) answers 0 too, ta.highest(high,
// 10) is unaffected) and re-pinned on OANDA:XAUUSD 1D for every
// source kind (low, close - 2000, sma(close, 5), low[1], hl2, the
// sign-mixed sma(close, 5) - close: 13/13 each). Every scraped
// tape in the population is a deep-backtest export that starts
// sign-mixed sma(close, 5) - close: 13/13 each). Every reference
// tape in the population is a deep-backtest run that starts
// cold at the range start, so this is the rule the campaign
// grades against (the round-5 "skip" reading was wrong:
// shiroi-qqe's stops on five 1D lanes are TV's SL = 0 from
Expand Down
10 changes: 10 additions & 0 deletions tests/confirmed_capabilities_refusals.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Confirmed-capabilities refusal registration: discover the generated
# refused_*.cpp fixtures that the confirmed-capabilities rows register.
#
# Included from runner/CMakeLists.txt at the position the discovery held
# before, inside the same guard. This is a move, not a rule change: the
# variable name, the glob pattern and the ordering are unchanged, so the same
# sources register in the same order. The consumers (`set(_pf_confirmed_refusals)`
# and its `foreach()`) stay in runner/CMakeLists.txt, downstream of this include.
file(GLOB _pf_confirmed_refusal_sources CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/tests/fixtures/confirmed_capabilities/generated/refused_*.cpp")
2 changes: 1 addition & 1 deletion tutorial/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def error_code(lib: ctypes.CDLL, state) -> str:
def run_error(lib: ctypes.CDLL, state) -> str | None:
"""'<text> (<code> <args JSON>)' when the run just made on state failed, else
None. A run failed when the engine reports a text, a code or a run status of 1
(strategy_last_run_status), as docker/run_json.py decides, so a script stopped
(strategy_last_run_status), as the release harness decides, so a script stopped
by runtime.error("") fails instead of printing a result."""
text = ""
if hasattr(lib, "strategy_get_last_error"):
Expand Down
Loading