Skip to content
Draft
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ The corpus feed is a 1-minute Binance ETH/USDT:USDT tape; `scripts/derive_corpus
[`benchmarks/`](benchmarks/) runs **200 strategies** through PineForge, [PyneCore](https://github.com/PyneSys/pynecore), [PineTS](https://github.com/LuxAlgo/PineTS) and [vectorbt](https://github.com/polakowo/vectorbt). Every engine gets the same 53,929-bar Binance ETH/USDT perpetual 15m feed, and each trade list is graded against TradingView's own export (266,451 trades):

- **100 corpus probes** (slots 001–100) are drawn by mechanism family from the public corpus. Their fixtures are in the public [`benchmarks/assets`](https://github.com/pineforge-4pass/pineforge-benchmarks-assets) submodule.
- **100 closed strategies** (slots 101–200) are TradingView-scraped community scripts on `BINANCE:ETHUSDT.P` 15m. Their artifacts are in the maintainers' evidence store (sha `c77a9c70…`) and are not public.
- **100 closed strategies** (slots 101–200) are third-party community scripts published on TradingView, on `BINANCE:ETHUSDT.P` 15m. Their artifacts are in the maintainers' evidence store (sha `c77a9c70…`) and are not public.
- PyneSys rejects slot 192's source, so slot 201, from the same stratum, stands in for it in the PyneCore count. PineForge runs all 201 slots.

PyneCore sources are official PyneSys cloud-compiler output, with no hand-ports. PineTS runs only the canonical indicator script: the harness has no PineTS strategy runner, although PineTS has shipped a `strategy.*` namespace since 0.9.17. vectorbt runs the 13 hand-written ports that load. `bash benchmarks/run_all.sh` reproduces the public half with no API keys once the `benchmarks/assets` submodule, uv and Node ≥ 20 are in place ([recipe](benchmarks/README.md#reproduce)).
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ SKIP_BUILD=1 SKIP_SPEED=1 JOBS=8 bash benchmarks/run_all.sh
cat benchmarks/results/summary.md
```

**Maintainers: the full 201 slots.** Fetch the closed root from the maintainers' private evidence store (`lab evidence get`, a maintainer-only tool, with the campaign environment sourced). Its directory is gitignored, and every harness step picks it up when it exists:
**Maintainers: the full 201 slots.** Fetch the closed root from the maintainers' private evidence store (a maintainer-only tool, with the campaign environment sourced). Its directory is gitignored, and every harness step picks it up when it exists:

```bash
lab evidence get c77a9c70891b5e4e97405fe6673389c06652a0cc449b380a4d982f1709e62019 --out /tmp/bench-closed.tar.gz
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
(pineforge-benchmarks-assets, Apache-2.0) whose tree is ``data/`` (pinned OHLCV)
and ``strategies/`` (the 100 public slots: ``.pine`` sources, ``tv_trades.csv``,
engine trade lists, cloud-compiled Pyne, ``_indicators/``). Only the 101 closed
slots are private: TradingView-scraped third-party scripts that maintainers
slots are private: third-party TradingView scripts that maintainers
extract from the evidence store into ``benchmarks/assets-closed/`` (gitignored).

If ``benchmarks/assets/strategies`` is missing, we fall back to inline
Expand All @@ -24,7 +24,7 @@
ASSETS = BENCH
STRATEGIES = ASSETS / "strategies"
DATA = ASSETS / "data"
# Maintainer-local closed slots (TradingView-scraped, never public); absent in
# Maintainer-local closed slots (third-party TradingView, never public); absent in
# public checkouts, where STRATEGY_ROOTS is the public root alone.
CLOSED_STRATEGIES = BENCH / "assets-closed" / "strategies"
STRATEGY_ROOTS = [STRATEGIES] + ([CLOSED_STRATEGIES] if CLOSED_STRATEGIES.is_dir() else [])
Expand Down
7 changes: 3 additions & 4 deletions benchmarks/select_population.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Select the benchmark population: 100 corpus probes + 100 closed scraped scripts.
"""Select the benchmark population: 100 corpus probes + 100 closed reference scripts.

Deterministic (``--seed``, default 20260921). Writes
``benchmarks/results/selection.json`` (machine manifest) and
Expand All @@ -19,12 +19,11 @@
member is drawn per bin.

Closed half (private artifacts, public manifest)
Drawn from the campaign population document (``lab population show`` ->
``lab evidence get <fileSha256>``): ``source == "scrapper"``,
Drawn from the campaign population document: ``source == "scrapper"``,
``symbol == "BINANCE:ETHUSDT.P"``, ``timeframe == "15"`` -- the only
dataset the bench feed (Binance ETH/USDT-USDT perp 15m) and the TV tapes
agree on -- with anomaly-surface probes excluded. The tape bytes are read
from the scrapper evidence tree and must hash to the population's
from the recorded evidence tree and must hash to the population's
``tvTradesSha256`` pin. A probe is excluded when its tape window is not
inside the bench feed, or when the campaign's latest verify report ran it
on the 1m feed (finer-TF ``request.security`` / ``request.security_lower_tf``:
Expand Down
10 changes: 5 additions & 5 deletions docs/pine-adapter-kernel-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
> `src/source/pine_strategy_host.cpp`) or in the surviving helpers of
> `src/engine_orders.cpp`. None of it is a kernel contract, and a native host
> that does not attach the Pine frontend sees none of it. The text is kept
> verbatim so the measurement provenance (campaign notes, `lab tv` pins, tape
> verbatim so the measurement provenance (campaign notes, reference pins, tape
> names) survives the move.

## 1. TradingView's ten-significant-digit money rule
Expand All @@ -30,7 +30,7 @@ pinned and implemented, with the per-rule scopes described below:
1. SIZING — a default percent_of_equity order is floored from the
ROUNDED equity (calc_qty): the lot count flips one lot early/late
when the exact equity is within half a money unit of a lot boundary
(~200 lab tv capital sweeps on OANDA:EURUSD 15 2025-04-01, scratch
(~200 reference capital sweeps on OANDA:EURUSD 15 2025-04-01, scratch
famr-adm-*: revc 998763.3420484 -> 922832.66, 998763.3420503 ->
922832.67; S2 1001759.6342676 -> one lot BELOW the exact floor).
The divisor is the price AS TRADINGVIEW HOLDS IT — the tick count
Expand Down Expand Up @@ -479,7 +479,7 @@ from_entry called before the entry. Its tapes are

One reference model of these rules reproduces all 563 tapes row for row, with
no counterexample. Every tape is a synthetic script written for these rules
and exported with the lab's TradingView exporter. The tapes are committed with
and recorded as a TradingView reference tape. The tapes are committed with
their scripts, parameters and bar windows. The tests replay each tape through
`PineStrategyHost` and compare TradingView's rows with the engine's report
rows; a tape the engine does not reproduce must be listed in its test, with
Expand Down Expand Up @@ -891,7 +891,7 @@ No callback or stable-frame ABI read occurs between its two fills.
```

Since R5 lane PAR-MARGIN the adapter keeps the placement quantity above
100 % as well: eight `lab tv` tapes of a 390-400 % stop on NYSE:F
100 % as well: eight recorded reference tapes of a 390-400 % stop on NYSE:F
(`tests/fixtures/margin_entry_bar/pm-m10-*`) open the placement quotient, never
the fill's (`tests/test_adapter_margin_schedule_differential.cpp`, "M10 on
tapes"). The margin call those tapes book at a half-cent low is TradingView's
Expand Down Expand Up @@ -1251,7 +1251,7 @@ clears each part, and exactly the re-issued short tapes depart. It also
replays every tape as a stream with both parts on and off, and requires the
per-bar broker-state hashes with the average part on and off to be equal
wherever the trades are, so a held average that outlived its pass fails it.
The scraped `job-2947` probe on BINANCE:ETHUSDT.P 15 lost its 2026-01-08
The reference `job-2947` probe on BINANCE:ETHUSDT.P 15 lost its 2026-01-08
23:30 UTC short and every later trade: a late exit observer behind a
cooldown fired one bar early.

Expand Down
4 changes: 2 additions & 2 deletions docs/pine_v6_audit_master.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
| **Audit date** | 2026-05-21 (initial); enriched same day with F1–F22 clarifications |
| **Last update** | 2026-06-10 — audit-fix sweep (codegen-oss `7bd20eb` (orig. `974cda7`), engine `6aa1d13`): remaining class-A/H rejections + semantic emission fixes; corpus regenerated (`f143504`), parity unchanged 245 excellent + 1 anomaly |
| **Audited doc** | `pine_v6_coverage_detail.md` (1039 lines, 941 identifiers) |
| **Method** | 4 parallel C++/Pine v6 expert agents, line-by-line, Playwright MCP for Pine v6 ref + grep on engine + transpiler. Follow-up: 2 clarification agents with live transpile harness. |
| **Method** | 4 parallel C++/Pine v6 expert agents, line-by-line, the Pine v6 reference + grep on engine + transpiler. Follow-up: 2 clarification agents with live transpile harness. |
| **Status** | Historical record, not re-audited since 2026-06-10: where it disagrees with `pine_v6_coverage_detail.md` or `coverage.md`, those pages hold. Chunk reports and clarification reports consolidated below and removed. |

## Headline
Expand Down Expand Up @@ -179,7 +179,7 @@ Identifier resolves to a different runtime symbol than spec.
## Methodology

Phase 1 — 4 parallel chunk agents:
1. Loaded official Pine v6 reference via Playwright MCP (`https://www.tradingview.com/pine-script-reference/v6/` — JS SPA, `<h3>` enumeration).
1. Loaded the official Pine v6 reference (`https://www.tradingview.com/pine-script-reference/v6/`) — 941 identifiers.
2. Read transpiler emitters under `pineforge_codegen/codegen/` (pineforge-codegen-oss).
3. Read engine headers under `include/pineforge/`.
4. Verified bucket + backing + notes + emitted-value for EVERY row (no skipping, no spot-check exemption).
Expand Down
15 changes: 8 additions & 7 deletions docs/pine_v6_coverage_detail.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
| **Generated** | 2026-05-17 (post Pine v6 HIGH+MEDIUM sprint) |
| **Reconciled** | 2026-07-18 — vs codegen-oss `cefeec8` / engine `06bb724`; map type + 11 map functions moved from 🔧 to ✅ after bounded runtime integration |
| **Audit trail** | `pine_v6_audit_master.md` (2026-05-21 audit + per-fix [RESOLVED] tags), pineforge-engine#122, pineforge-codegen-oss#78 |
| **Pine v6 reference** | https://www.tradingview.com/pine-script-reference/v6/ (JS-rendered, scraped 2026-05-16) |
| **Pine v6 reference** | https://www.tradingview.com/pine-script-reference/v6/ (checked 2026-05-16) |
| **PineForge engine version** | v0.12.0-24-g06bb724 (commit `06bb724`) |
| **Total Pine v6 identifiers** | 941 |

Expand All @@ -33,7 +33,7 @@

## Methodology

**Step 1 — Pine v6 catalog:** Playwright navigated to `https://www.tradingview.com/pine-script-reference/v6/` (JS SPA). The full DOM was evaluated via `document.querySelectorAll('h3')` which returned 941 entries — every identifier in the reference, ordered as the site renders them (Types → Variables → Constants → Functions → Keywords → Operators → Annotations).
**Step 1 — Pine v6 catalog:** the Pine v6 reference (`https://www.tradingview.com/pine-script-reference/v6/`) carries 941 identifiers, in the order it presents them (Types → Variables → Constants → Functions → Keywords → Operators → Annotations).

**Step 2 — PineForge runtime inventory:** Read all public headers under `include/pineforge/*.hpp`, `src/engine_internal.hpp`, and `docs/coverage.md`. Extracted every exported class, free function, and inline.

Expand Down Expand Up @@ -1056,11 +1056,12 @@ vs pre-sprint baseline 227 excellent + 1 anomaly: **+6 excellent, 0 regressions*

## Reproducibility

To re-derive the Pine v6 identifier list:
1. Open a Playwright browser to `https://www.tradingview.com/pine-script-reference/v6/`
2. Wait for the JS SPA to fully render (all sections must be in DOM)
3. Execute: `Array.from(document.querySelectorAll('h3')).map(h => h.textContent.trim())`
4. The result is the 941-entry flat list used above
How the Pine v6 identifier list was checked:
1. Open the Pine v6 reference at `https://www.tradingview.com/pine-script-reference/v6/`
2. The identifier list is the 941 entries the reference carries (Types → Variables →
Constants → Functions → Keywords → Operators → Annotations)
3. In the order the reference presents them, that is the flat list used above
4. Re-check it against the reference when the reference changes

To re-derive the PineForge runtime surface:
1. Read all `*.hpp` under `include/pineforge/` and catalog every class, free function, and inline helper
Expand Down
28 changes: 14 additions & 14 deletions scripts/test_run_strategy_range_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
range-end close (open_at_end, at that bar's close). That final bar must be
TV's last bar, not the feed's: the BINANCE:ETHUSDT.P 15 chart feed runs on
to 2026-05-04 15:00 UTC while every ETH tape's range ends 2026-05-01 (228 of
397 scrapper tapes with an Open row at 05-01 08:00 (+8) = 05-01 00:00 UTC @
397 reference tapes with an Open row at 05-01 08:00 (+8) = 05-01 00:00 UTC @
2261.44 = that bar's close). Unbounded, the row would be booked on 05-04
15:00 @ 2365.09, a different bar ~4.6% off TV's, and fail exit/pnl against
TV's Open row where it was previously simply absent.

run_strategy.py therefore bounds the feed (``ohlcv_end_ms``) to
TradingView's RANGE END, read from the tape's metrics.json — the bars
opening at or before ``to`` as 00:00 UTC (``wsProvenance.requestedRange.to``
when the ws exporter recorded it, else the ``to`` date) — and writes the
when the tape records it, else the ``to`` date) — and writes the
range-end close rows, each marked "open" in the CSV's trailing ``Engine
range-end`` column. Not the tape's last trade row: a first cut did that
and the full-population gate failed hard.regression on
Expand All @@ -26,10 +26,10 @@
every unmarked lane measured was clean: f-1d 10/10 engine-0 -> excellent,
waranyutrkm 110/110 at 100%, the 8 unmarked range-end ETH tapes unchanged.

The same for every tape. The ws-report-v1 exporter prints the range-end
The same for every tape. The reference tape prints the range-end
position as an ordinary closed trade with an empty Signal (every 1D lane,
most 15m); the browser export prints the same row with Signal "Open" (227
of the 396 scraped ETH tapes). Under v4 a marked tape was measured as the
most 15m); 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 — unbounded, the marks withheld — because the
canonical grader paired the baseline's post-range closes with TV's Open
rows on entry time, and v2 (bound + emit) moved pnlP90 on the three
Expand Down Expand Up @@ -81,7 +81,7 @@ def _utc_ms(y: int, m: int, d: int, hh: int = 0, mm: int = 0) -> int:
return int(datetime(y, m, d, hh, mm, tzinfo=timezone.utc).timestamp() * 1000)


# Every campaign tape: from 2025-04-01 to 2026-05-01, i.e. the ws exporter's
# Every campaign tape: from 2025-04-01 to 2026-05-01, i.e. the tape's own
# requestedRange {from: 1743465600000, to: 1777593600000}.
RANGE_TO_MS = 1777593600000
assert RANGE_TO_MS == _utc_ms(2026, 5, 1)
Expand Down Expand Up @@ -116,7 +116,7 @@ def _write_metrics(path: Path, metrics: dict) -> None:
path.write_text(json.dumps(metrics, indent=2), encoding="utf-8")


# The scrapper's browser export (ETH, AAPL 15, EURUSD): the range as dates.
# The date-only reference (ETH, AAPL 15, EURUSD): the range as dates.
BROWSER_METRICS = {
"strategy": "waranyutrkm-asian-box-breakout-eda-tuned",
"symbol": "BINANCE:ETHUSDT.P", "interval": "15",
Expand All @@ -125,7 +125,7 @@ def _write_metrics(path: Path, metrics: dict) -> None:
"deepBacktesting": True, "trades": 110,
}

# The ws-report-v1 export (F, BTC, ES, NQ, XAU, NIFTY, AAPL 1D): the dates
# The recorded reference run (F, BTC, ES, NQ, XAU, NIFTY, AAPL 1D): the dates
# plus the exact window sent, and the range TV returned.
WS_METRICS_F_1D = {
"validationEligible": True, "strategy": "orb-lite",
Expand Down Expand Up @@ -173,7 +173,7 @@ def test_no_range(self) -> None:

def test_rule_matches_every_lanes_returned_range(self) -> None:
# TradingView's own returnedRange.to on the ws tapes (survey of the
# scrapper's lane directories, 2026-09-02) is the open of the last
# reference lane directories, 2026-09-02) is the open of the last
# bar at or before requestedRange.to = 05-01 00:00 UTC: the bar
# OPENING at `to` is inside the range, the next one is not, and
# `to` is UTC midnight even on CME's America/Chicago chart.
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_closed_last_trade_bounds_at_the_range_end_not_the_tape(self) -> None:
self.assertNotEqual(_load_tv_range_end_ms(d, TZ8), LAST_EXIT)

def test_open_position_row_sits_on_the_range_end(self) -> None:
# Browser export: the Open row at 05-01 08:00 (+8) IS the range end.
# Date-only reference: the Open row at 05-01 08:00 (+8) IS the range end.
with tempfile.TemporaryDirectory() as tmp:
d = Path(tmp)
_write_tape(d / "tv_trades.csv", [
Expand Down Expand Up @@ -248,7 +248,7 @@ def test_metrics_without_a_range_falls_back_to_the_tapes_last_row(self) -> None:
def test_no_metrics_falls_back_to_the_tapes_last_row(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
d = Path(tmp)
# Browser tape ending on its Open row: the fallback is the range end.
# Date-only tape ending on its Open row: the fallback is the range end.
_write_tape(d / "tv_trades.csv", [
(1, "Exit long", "2026-04-29 10:15", "X", "2310.5"),
(1, "Entry long", "2026-04-28 09:00", "A", "2290.0"),
Expand Down Expand Up @@ -419,7 +419,7 @@ def test_docker_pretrim_applies_both_bounds(self) -> None:

# --- the range-end row and the tape's marker --------------------------

# The 3commas grid-bot shape (browser export): closed lots, then the lots
# The 3commas grid-bot shape (date-only reference): closed lots, then the lots
# still open at the range end, each marked "Open" on its exit row at
# 05-01 08:00 (+8) @ 2261.44.
BROWSER_OPEN_TAPE = [
Expand All @@ -431,7 +431,7 @@ def test_docker_pretrim_applies_both_bounds(self) -> None:
(1, "Entry long", "2026-04-28 09:00", "A", "2290.0"),
]

# The same position on a ws-report-v1 tape: the exit row has no Signal.
# The same position on a reference tape: the exit row has no Signal.
WS_OPEN_TAPE = [
(3, "Exit long", "2026-05-01 08:00", "", "2261.44"),
(3, "Entry long", "2026-04-30 20:45", "B", "2189.13"),
Expand Down Expand Up @@ -644,7 +644,7 @@ def test_unmarked_ws_tape_is_bounded_at_the_requested_to(self) -> None:
self.assertIn("rows written", why)

def test_unmarked_browser_metrics_tape_is_bounded_at_the_to_date(self) -> None:
# A browser-export metrics.json (dates only) beside a tape with no
# A metrics.json without a range (dates only) beside a tape with no
# Open rows: bounded at `to` as UTC midnight.
with tempfile.TemporaryDirectory() as tmp:
d = Path(tmp)
Expand Down
8 changes: 4 additions & 4 deletions scripts/test_verify_corpus_open_mark.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""A lot still open at the range's end is marked twice, and the two marks
pair: TradingView's browser export writes its exit row with Signal "Open";
pair: a date-only TradingView tape writes its exit row with Signal "Open";
the engine, fed to TradingView's range end, books the same lot as its
range-end close (open_at_end) and marks the exit row in the trailing
``Engine range-end`` column of engine_trades.csv. The pair is a matched
Expand All @@ -17,8 +17,8 @@
marks' rounding summed into it moved the metric by itself (round 3,
2026-09-02: 0.5536 -> 0.6675 on xlm with 21 of 22 marks exact to the cent).

Why the exit must stay out: a browser export's Open row can carry the quote
at EXPORT time, not a bar's print — all 12 sampled OANDA:EURUSD registry
Why the exit must stay out: a reference Open row can carry the quote
at recording time, not a bar's print — all 12 sampled OANDA:EURUSD registry
tapes end with Open rows at 2026-05-01 08:00 (+8) @ 1.17256 while the
feed's last bar is o 1.17289 h 1.17299 l 1.17282 c 1.1729; the engine's
row is priced at that close, and the 0.029% is not parity information.
Expand All @@ -27,7 +27,7 @@
on an earlier bar keeps its exit/pnl deltas (TV held to the end, the
engine did not); one the engine never opened leaves the coverage
denominator as before. An engine range-end row for a lot the tape closed
is matched by entry and gated like any close. A ws-report-v1 tape marks
is matched by entry and gated like any close. A reference tape marks
nothing and prices the row at the last bar's close — the engine's row —
so it pairs as an ordinary closed trade and the grader is inert.
"""
Expand Down
Loading
Loading