diff --git a/.gitignore b/.gitignore index 945c2c356..2c64e5a98 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,7 @@ _deps/ __pycache__/ *.pyc -# Playwright MCP traces +# MCP tool traces .playwright-mcp/ # Tutorial backtest output @@ -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/ diff --git a/CMakeLists.txt b/CMakeLists.txt index d930ba056..8884af82b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/pineforge/run_failure.hpp b/include/pineforge/run_failure.hpp index 5eefbaa00..ec46c0b58 100644 --- a/include/pineforge/run_failure.hpp +++ b/include/pineforge/run_failure.hpp @@ -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 diff --git a/include/pineforge/run_failure_codes.hpp b/include/pineforge/run_failure_codes.hpp index 0b7e2b852..28a145089 100644 --- a/include/pineforge/run_failure_codes.hpp +++ b/include/pineforge/run_failure_codes.hpp @@ -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 @@ -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 { diff --git a/runner/CMakeLists.txt b/runner/CMakeLists.txt index 208ed690e..718d4877c 100644 --- a/runner/CMakeLists.txt +++ b/runner/CMakeLists.txt @@ -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) diff --git a/scripts/check_run_failure_codes.py b/scripts/check_run_failure_codes.py index d966084a2..460d47a21 100644 --- a/scripts/check_run_failure_codes.py +++ b/scripts/check_run_failure_codes.py @@ -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: @@ -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") @@ -258,7 +258,7 @@ def render_codes_header(catalog: dict) -> str: names = ordered_codes(catalog) lines = [HEADER_BANNER, "#pragma once\n", "#include \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 {", diff --git a/scripts/run_strategy.py b/scripts/run_strategy.py index c0740fec2..3d0161dee 100644 --- a/scripts/run_strategy.py +++ b/scripts/run_strategy.py @@ -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 @@ -1465,7 +1465,7 @@ def at(value) -> Path: # PINEFORGE_REQUESTS_ROOT=, holding //requests.json # (pineforge-probe-requests/v1) and //files/ -- 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 @@ -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///`` 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 @@ -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).""" @@ -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 @@ -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: @@ -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 @@ -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 @@ -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" @@ -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, @@ -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 / @@ -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.""" diff --git a/scripts/symbol_calendar.py b/scripts/symbol_calendar.py index 53180ed32..5a8ec1b3d 100644 --- a/scripts/symbol_calendar.py +++ b/scripts/symbol_calendar.py @@ -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 @@ -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") diff --git a/scripts/verify_corpus.py b/scripts/verify_corpus.py index b9cd5f505..a4f7e876f 100755 --- a/scripts/verify_corpus.py +++ b/scripts/verify_corpus.py @@ -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 @@ -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" @@ -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 diff --git a/src/run_failure_registry.inc b/src/run_failure_registry.inc index dbe903be4..d84a88f11 100644 --- a/src/run_failure_registry.inc +++ b/src/run_failure_registry.inc @@ -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: diff --git a/src/ta_extremes_volume.cpp b/src/ta_extremes_volume.cpp index 9aa563316..27e4142c0 100644 --- a/src/ta_extremes_volume.cpp +++ b/src/ta_extremes_volume.cpp @@ -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 diff --git a/tests/confirmed_capabilities_refusals.cmake b/tests/confirmed_capabilities_refusals.cmake new file mode 100644 index 000000000..5c01e06cc --- /dev/null +++ b/tests/confirmed_capabilities_refusals.cmake @@ -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") diff --git a/tutorial/run.py b/tutorial/run.py index 19f0f97e0..8c460e616 100755 --- a/tutorial/run.py +++ b/tutorial/run.py @@ -163,7 +163,7 @@ def error_code(lib: ctypes.CDLL, state) -> str: def run_error(lib: ctypes.CDLL, state) -> str | None: """' ( )' 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"):