From c78acf8ee522f01199625095b6ca2b73eb394445 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:08:30 +0800 Subject: [PATCH 1/9] feat(gate): pin PYODIDE_TARGET=314.0.0 (target Pyodide for the conformance gate) --- PYODIDE_TARGET | 1 + 1 file changed, 1 insertion(+) create mode 100644 PYODIDE_TARGET diff --git a/PYODIDE_TARGET b/PYODIDE_TARGET new file mode 100644 index 0000000..10017db --- /dev/null +++ b/PYODIDE_TARGET @@ -0,0 +1 @@ +314.0.0 From 9c2dd4bb9918f27204b5c43ea7e9d71319e933c1 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:09:04 +0800 Subject: [PATCH 2/9] build(gate): add Node tooling (pyodide + tar) for the conformance gate --- .gitignore | 5 +- package-lock.json | 132 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++++++ 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index a320cb9..26cc41a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,8 +56,6 @@ pineforge_codegen/**/*.cpp # ─── Node / TypeScript ───────────────────────────────────────────────────── node_modules/ **/node_modules/ -package-lock.json -**/package-lock.json yarn.lock pnpm-lock.yaml npm-debug.log* @@ -120,3 +118,6 @@ temp/ # cloud/mcp-local/ was extracted to a public repo: # https://github.com/fullpass-4pass/pineforge-codegen-mcp # Don't bring it back here. + +# ─── Gate output ─────────────────────────────────────────────────────────── +/release.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8f324da --- /dev/null +++ b/package-lock.json @@ -0,0 +1,132 @@ +{ + "name": "pineforge-codegen-gate", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pineforge-codegen-gate", + "version": "0.0.0", + "devDependencies": { + "pyodide": "314.0.0", + "tar": "^7.4.3" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/pyodide": { + "version": "314.0.0", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-314.0.0.tgz", + "integrity": "sha512-WIofRQehY78IC9n3+p9COjKUZGCCHfrvtR5dXKm0BHIWUun2B8DBf17rXytMF/KYkrsDQRa+BDGhi8UKwzaU3w==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@types/emscripten": "^1.41.4", + "ws": "^8.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d24bacf --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "pineforge-codegen-gate", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Node-side conformance gate tooling for pineforge-codegen (not published).", + "scripts": { + "gate": "node gate/run-gate.mjs", + "gate:full": "GATE_FULL=1 node gate/run-gate.mjs", + "gate:selftest": "node gate/selftest.mjs" + }, + "devDependencies": { + "pyodide": "314.0.0", + "tar": "^7.4.3" + } +} From cbc56f51aae89eb7b3ba7e4759c51e8a695a7507 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:09:29 +0800 Subject: [PATCH 3/9] feat(gate): canonical transpile_json glue (runtime-equivalent to app PY_GLUE) --- gate/glue.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 gate/glue.py diff --git a/gate/glue.py b/gate/glue.py new file mode 100644 index 0000000..d08d453 --- /dev/null +++ b/gate/glue.py @@ -0,0 +1,35 @@ +# CANONICAL GLUE — runtime-equivalent to the body of PY_GLUE in +# pineforge-app/apps/web/lib/pyodide-transpiler/glue.ts (produces identical +# transpile_json output). The browser worker and this gate run the same logic, +# so the gate's parity guarantee reflects shipped behavior. +# (Phase 3: ship this from the npm package so there is one source of truth.) +import json +import sys + +if "/codegen" not in sys.path: + sys.path.insert(0, "/codegen") + +from pineforge_codegen import transpile +from pineforge_codegen.errors import CompileError + + +def transpile_json(source: str) -> str: + try: + cpp = transpile(source) + except CompileError as e: + diags = [] + for d in e.diagnostics: + loc = d.location + message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message + entry = { + "line": loc.line if loc else 1, + "col": loc.col if loc else 1, + "message": message, + "severity": getattr(d.level, "value", "error"), + } + end_col = getattr(loc, "end_col", None) if loc else None + if end_col is not None: + entry["endCol"] = end_col + diags.append(entry) + return json.dumps({"ok": False, "error": str(e), "diagnostics": diags}) + return json.dumps({"ok": True, "cpp": cpp}) From ecf4343860dd6924625ad9b62ae89ab12c1887d1 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:09:53 +0800 Subject: [PATCH 4/9] feat(gate): native CPython oracle (same glue, unexpected-exception capture) --- gate/oracle.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 gate/oracle.py diff --git a/gate/oracle.py b/gate/oracle.py new file mode 100644 index 0000000..3390dde --- /dev/null +++ b/gate/oracle.py @@ -0,0 +1,34 @@ +"""Native-CPython oracle for the conformance gate. + +Reads a JSON array of {"name","src"} from stdin, runs the CANONICAL glue's +transpile_json on each, and writes a JSON object {name: result} to stdout. +Each result is {"json": } on a normal return, or +{"unexpected": ": "} if transpile_json raised something other +than CompileError (CompileError is already encoded inside the json string). +""" + +from __future__ import annotations + +import json +import os +import sys + +GLUE = os.path.join(os.path.dirname(__file__), "glue.py") +exec(compile(open(GLUE).read(), GLUE, "exec")) # defines transpile_json # noqa: S102 + + +def main() -> None: + items = json.load(sys.stdin) + out: dict[str, dict] = {} + for item in items: + name = item["name"] + src = item["src"] + try: + out[name] = {"json": transpile_json(src)} # noqa: F821 — from exec + except Exception as exc: # noqa: BLE001 — capture for parity, don't throw + out[name] = {"unexpected": f"{type(exc).__name__}: {exc}"} + json.dump(out, sys.stdout) + + +if __name__ == "__main__": + main() From df42599eb86bfcf893b6c07b72765471af46a47f Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:10:14 +0800 Subject: [PATCH 5/9] feat(gate): pure comparator module (importable without running the gate) --- gate/compare.mjs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 gate/compare.mjs diff --git a/gate/compare.mjs b/gate/compare.mjs new file mode 100644 index 0000000..e38b93a --- /dev/null +++ b/gate/compare.mjs @@ -0,0 +1,29 @@ +// Pure, side-effect-free comparator for the gate. Both run-gate.mjs (the runner) +// and selftest.mjs (the canary) import this — so importing it never triggers a +// gate run. Each side is {json: ""} (normal return) or +// {unexpected: "Type: msg"} (non-CompileError exception). Returns a mismatch +// string, or null if the two sides agree. +export function compareResults(name, native, browser) { + if (!native) return `${name}: oracle produced no result`; + if (!browser) return `${name}: pyodide produced no result`; + if (native.unexpected || browser.unexpected) { + if (native.unexpected !== browser.unexpected) { + return `${name}: unexpected-exception mismatch\n native : ${native.unexpected ?? ""}\n pyodide: ${browser.unexpected ?? ""}`; + } + return null; + } + if (native.json !== browser.json) { + let detail = ""; + try { + const n = JSON.parse(native.json); + const b = JSON.parse(browser.json); + if (n.ok !== b.ok) detail = `verdict ${b.ok} (pyodide) != ${n.ok} (native)`; + else if (n.ok) detail = "C++ output differs"; + else detail = `error/diagnostics differ\n native : ${native.json}\n pyodide: ${browser.json}`; + } catch { + detail = `raw json differs\n native : ${native.json}\n pyodide: ${browser.json}`; + } + return `${name}: ${detail}`; + } + return null; +} From a2d9c32f2d7d480416c38b500522e3370aa3c7f1 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:10:53 +0800 Subject: [PATCH 6/9] feat(gate): differential parity runner (Pyodide vs native, full-JSON + release.json) --- gate/run-gate.mjs | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 gate/run-gate.mjs diff --git a/gate/run-gate.mjs b/gate/run-gate.mjs new file mode 100644 index 0000000..861e242 --- /dev/null +++ b/gate/run-gate.mjs @@ -0,0 +1,119 @@ +// Differential parity gate: run the transpiler in real wasm32 Pyodide and assert +// it behaves identically to native CPython on EVERY corpus input — success +// (byte-identical cpp) AND failure (same verdict/error/diagnostics + same +// unexpected-exception type+message). Also writes release.json. +// +// node gate/run-gate.mjs # smoke: all err + first N ok fixtures +// GATE_FULL=1 node gate/run-gate.mjs # entire corpus +// +// Exit 0 = parity holds; exit 1 = mismatch(es) or setup failure. +import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { compareResults } from "./compare.mjs"; + +const require = createRequire(import.meta.url); +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, ".."); +const CORPUS = join(ROOT, "tests", "gate-corpus"); +const SCRATCH = join(HERE, ".scratch"); +const GLUE = readFileSync(join(HERE, "glue.py"), "utf8"); +const PYTHON = process.env.GATE_PYTHON ?? "python3"; +const OK_CAP = process.env.GATE_FULL ? Infinity : 60; + +function branchItems(branch) { + const dir = join(CORPUS, branch); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .filter((x) => x.endsWith(".pine")) + .sort() + .map((f) => ({ name: `${branch}/${f}`, src: readFileSync(join(dir, f), "utf8") })); +} + +async function main() { + const okItems = branchItems("ok"); + const errItems = branchItems("err"); + if (okItems.length + errItems.length === 0) { + console.error("gate: empty corpus at tests/gate-corpus/{ok,err}"); + process.exit(1); + } + // ALWAYS include the (small) err branch so the smoke run exercises the failure + // path; cap only the large ok branch. + const items = [ + ...(Number.isFinite(OK_CAP) ? okItems.slice(0, OK_CAP) : okItems), + ...errItems, + ]; + console.log( + `gate: ${items.length} fixtures (ok=${Math.min(okItems.length, OK_CAP)}/${okItems.length}, err=${errItems.length}, GATE_FULL=${process.env.GATE_FULL ? "1" : "0"})`, + ); + + // 1. Pack the in-repo source into an archive (excluding __pycache__). + mkdirSync(SCRATCH, { recursive: true }); + const archive = join(SCRATCH, "pineforge_codegen.tar.gz"); + const tar = require("tar"); + await tar.create( + { gzip: true, file: archive, cwd: ROOT, portable: true, filter: (p) => !p.includes("__pycache__") }, + ["pineforge_codegen"], + ); + const archiveBytes = readFileSync(archive); + + // 2. Load Pyodide, unpack, run glue. + const { loadPyodide } = await import("pyodide"); + const indexURL = dirname(require.resolve("pyodide/package.json")); + const pyodide = await loadPyodide({ indexURL }); + const u8 = new Uint8Array(archiveBytes.buffer, archiveBytes.byteOffset, archiveBytes.byteLength); + pyodide.unpackArchive(u8, "gztar", { extractDir: "/codegen" }); + pyodide.runPython(GLUE); + const transpileJson = pyodide.globals.get("transpile_json"); + + // 3. Native oracle (one process, whole corpus). PYTHONHASHSEED pinned for + // determinism per spec §6.1. + const oracleOut = execFileSync(PYTHON, [join(HERE, "oracle.py")], { + input: JSON.stringify(items), + env: { ...process.env, PYTHONPATH: ROOT, PYTHONHASHSEED: "0" }, + maxBuffer: 256 * 1024 * 1024, + encoding: "utf8", + }); + const native = JSON.parse(oracleOut); + + // 4. Pyodide side + compare. + const mismatches = []; + for (const { name, src } of items) { + let browser; + try { + browser = { json: transpileJson(src) }; + } catch (err) { + browser = { unexpected: `${err?.constructor?.name ?? "Error"}: ${err?.message ?? String(err)}` }; + } + const m = compareResults(name, native[name], browser); + if (m) mismatches.push(m); + } + + // 5. release.json (versions derived from the loaded Pyodide lock). + const lock = require("pyodide/pyodide-lock.json"); + const codegen = readFileSync(join(ROOT, "VERSION"), "utf8").trim(); + const pyodideVer = readFileSync(join(ROOT, "PYODIDE_TARGET"), "utf8").trim(); + const release = { + codegen, + pyodide: pyodideVer, + python: lock.info.python, + emscripten: lock.info.platform, + sha256: createHash("sha256").update(archiveBytes).digest("hex"), + }; + writeFileSync(join(ROOT, "release.json"), JSON.stringify(release, null, 2) + "\n"); + console.log("gate: release.json ->", JSON.stringify(release)); + + if (mismatches.length) { + console.error(`gate: ${mismatches.length} MISMATCH(es):\n` + mismatches.join("\n")); + process.exit(1); + } + console.log(`gate: PARITY OK over ${items.length} fixtures`); +} + +main().catch((e) => { + console.error("gate: fatal", e); + process.exit(1); +}); From 9fdd8b776925c6faae3c6c7c440c3b23c17ef800 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:16:44 +0800 Subject: [PATCH 7/9] test(gate): success (260) + failure (15-family) parity corpus Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/gate-corpus/err/chart_bg_color.pine | 3 + .../err/const_ns_plot_style_free.pine | 3 + .../gate-corpus/err/const_ns_shape_free.pine | 3 + tests/gate-corpus/err/export_func.pine | 3 + tests/gate-corpus/err/footprint_new.pine | 3 + .../err/hard_reject_dividends.pine | 3 + .../gate-corpus/err/matrix_unknown_elem.pine | 3 + .../gate-corpus/err/multi_error_one_line.pine | 3 + tests/gate-corpus/err/sec_tf_invalid.pine | 3 + tests/gate-corpus/err/unknown_color.pine | 3 + tests/gate-corpus/err/unknown_math.pine | 3 + tests/gate-corpus/err/unknown_str.pine | 3 + .../gate-corpus/err/unknown_strategy_fn.pine | 3 + tests/gate-corpus/err/unknown_syminfo.pine | 3 + tests/gate-corpus/err/unknown_ta.pine | 3 + .../AAPL__session-ismarket-nyse-rth-01.pine | 16 ++ ...ime-tradingday-daily-reset-counter-01.pine | 44 ++++ .../QQQ__session-ispremarket-nasdaq-01.pine | 16 ++ .../SPY__session-firstbar-vwap-anchor-01.pine | 29 +++ .../ok/SPY__session-lastbar-flatten-01.pine | 19 ++ ...pto-htf__mtf-htf-monthly-ema-cross-01.pine | 26 ++ ...rage__leverage-margin-call-perp-5x-01.pine | 54 ++++ .../ok/forex__symbol-fx-5dp-eurusd-01.pine | 32 +++ ...ures__symbol-futures-pointvalue-es-01.pine | 36 +++ .../ok/tutorial__macd__strategy.pine | 45 ++++ .../ok/tutorial__mtf__strategy_htf.pine | 49 ++++ .../ok/tutorial__mtf__strategy_ltf.pine | 58 +++++ ...quity__symbol-equity-rth-gaps-aapl-01.pine | 34 +++ ...us-equity-exchange-tz-intraday-cap-01.pine | 30 +++ ...analyzer-parity-choch-bos-isolator-01.pine | 83 ++++++ ...analyzer-parity-edge-margin-50-pct-01.pine | 39 +++ ...er-parity-percent-of-equity-sizing-01.pine | 42 ++++ ...lyzer-parity-small-equity-fraction-01.pine | 44 ++++ ..._analyzer-parity-stop-limit-timing-01.pine | 54 ++++ ...ion__analyzer-self-test-multi-mode-01.pine | 80 ++++++ ...maly-equity-mirror-strategy-equity-01.pine | 56 +++++ ...arstate-isconfirmed-magnifier-off-01b.pine | 41 +++ ...barstate-isconfirmed-magnifier-on-01a.pine | 44 ++++ ...racket-atr-trail-series-int-points-01.pine | 57 +++++ ...n__bracket-atr-trailing-stop-state-01.pine | 60 +++++ ...racket-entry-exit-same-pass-attach-01.pine | 27 ++ ...ket-exit-stop-limit-trail-same-bar-01.pine | 41 +++ ...cket-exit-three-way-set-once-entry-01.pine | 49 ++++ ...lidation__bracket-exit-tp-sl-fixed-01.pine | 27 ++ ...cket-narrow-stop-limit-with-trail8-01.pine | 49 ++++ ...__bracket-partial-exit-qty-percent-01.pine | 28 +++ ...tion__bracket-same-id-exit-replace-01.pine | 27 ++ ...__bracket-tp-sl-oca-reduce-isolate-01.pine | 64 +++++ ...et-trail-points-no-offset-explicit-01.pine | 50 ++++ ...cket-trail-points-with-offset-only-01.pine | 34 +++ ...et-trailing-activation-offset-path-01.pine | 35 +++ ...max-intraday-filled-orders-isolate-01.pine | 45 ++++ ..._cap-risk-gates-allow-max-intraday-01.pine | 36 +++ ...ion__composite-4emarsi-integration-01.pine | 99 ++++++++ ...__composite-4emarsi-quad-ema-stack-01.pine | 59 +++++ ...mposite-4emarsi-rsi-pullback-latch-01.pine | 61 +++++ ...e-4emarsi-session-window-nbar-exit-01.pine | 60 +++++ ...ion__composite-boscurv-integration-01.pine | 80 ++++++ ...osite-boscurv-linreg-slope-channel-01.pine | 64 +++++ ...omposite-boscurv-pivot-bos-trigger-01.pine | 55 ++++ ...ite-bracket-cap-range-pending-stop-01.pine | 98 ++++++++ ..._composite-ies-adx-regime-classify-01.pine | 87 +++++++ ...omposite-ies-bb-kc-squeeze-release-01.pine | 62 +++++ ...__composite-ies-cooldown-daily-cap-01.pine | 72 ++++++ ...mposite-ies-equity-feedback-sizing-01.pine | 95 +++++++ ...idation__composite-ies-integration-01.pine | 236 ++++++++++++++++++ ...omposite-ies-pivot-liquidity-sweep-01.pine | 66 +++++ ...tion__composite-ies-pressure-gauge-01.pine | 79 ++++++ ...n__composite-ies-rsi-macd-momentum-01.pine | 88 +++++++ ...composite-ies-three-ema-bias-score-01.pine | 82 ++++++ ...omposite-kanuck-calc-on-every-tick-01.pine | 55 ++++ ...tion__composite-kanuck-integration-01.pine | 85 +++++++ ...osite-kanuck-kama-state-recurrence-01.pine | 54 ++++ ...composite-kanuck-max-bars-back-500-01.pine | 67 +++++ ...omposite-kkb-ema-atr-breakout-band-01.pine | 48 ++++ ...idation__composite-kkb-integration-01.pine | 68 +++++ ...on__composite-kkb-kalman-filter-1d-01.pine | 58 +++++ ...tion__composite-kkb-margin-100-pct-01.pine | 46 ++++ ...on__composite-liqsweep-integration-01.pine | 89 +++++++ ...on__composite-liqsweep-pivot-hh-ll-01.pine | 58 +++++ ...ite-liqsweep-wait-one-continuation-01.pine | 73 ++++++ ...te-liqsweep-wick-pierce-close-back-01.pine | 55 ++++ ..._composite-marketshift-integration-01.pine | 97 +++++++ ...te-marketshift-pivot-state-machine-01.pine | 64 +++++ ...marketshift-rolling-highest-lowest-01.pine | 51 ++++ ...te-marketshift-state-edge-detector-01.pine | 69 +++++ ...ite-scalping-fast-ma-cross-trigger-01.pine | 46 ++++ ...on__composite-scalping-integration-01.pine | 61 +++++ ...posite-scalping-tight-tp-sl-points-01.pine | 63 +++++ ..._composite-trendmaster-integration-01.pine | 124 +++++++++ ...te-trendmaster-line-new-projection-01.pine | 92 +++++++ ...trendmaster-pivot-anchored-bracket-01.pine | 75 ++++++ ...e-trendmaster-three-tier-ema-state-01.pine | 66 +++++ ...ster-trend-momentum-structure-gate-01.pine | 73 ++++++ ...posite-vcp-cumulative-volume-delta-01.pine | 54 ++++ ...on__composite-vcp-fvg-active-zones-01.pine | 96 +++++++ ...idation__composite-vcp-integration-01.pine | 195 +++++++++++++++ ...n__composite-vcp-manual-adx-regime-01.pine | 73 ++++++ ...on__composite-vcp-pivot-strength-5-01.pine | 55 ++++ ...omposite-vcp-rsi-smooth-divergence-01.pine | 57 +++++ ...__composite-vcp-session-tz-newyork-01.pine | 56 +++++ ...__composite-vcp-vol-zscore-anomaly-01.pine | 52 ++++ ...site-wunderscalper-alert-templates-01.pine | 56 +++++ ...ite-wunderscalper-explicit-reverse-01.pine | 53 ++++ ...omposite-wunderscalper-integration-01.pine | 53 ++++ ...input-source-runtime-override-high-01.pine | 30 +++ ...dation__input-source-subscript-hl2-01.pine | 27 ++ ...tion__ltf-bool-array-bull-majority-01.pine | 44 ++++ ...idation__ltf-numeric-float-ratio15-01.pine | 49 ++++ ...ion__magnifier-tick-dist-endpoints-01.pine | 44 ++++ ...ier-tick-dist-endpoints-rsi-cross-08a.pine | 37 +++ ...ifier-tick-dist-volume-weighted-on-01.pine | 41 +++ ...__matrix-bool-mask-explicit-utc-tz-01.pine | 60 +++++ ...ion__matrix-bool-mask-no-transpose-01.pine | 38 +++ ...trix-bool-mask-transpose-roundtrip-01.pine | 38 +++ ...tion__matrix-bool-regime-mask-24x7-01.pine | 65 +++++ ...ation__matrix-covariance-eigen-pca-01.pine | 43 ++++ ...n__matrix-eigen-rank-deficient-cov-01.pine | 85 +++++++ ...mtf-daily-array-median-percentrank-01.pine | 26 ++ ...validation__mtf-daily-ema26-warmup-01.pine | 41 +++ ...idation__mtf-daily-prev-high-break-01.pine | 32 +++ ...idation__mtf-dual-tf-60-240-rising-01.pine | 28 +++ ...__mtf-htf-60-close-change-baseline-01.pine | 57 +++++ .../validation__mtf-htf-60-close-roll-01.pine | 25 ++ ...alidation__mtf-htf-60-gaps-on-roll-01.pine | 34 +++ ...__mtf-htf-60-rsi14-inside-security-01.pine | 25 ++ ...__mtf-htf-60-sma20-inside-security-01.pine | 27 ++ ...alidation__mtf-htf-60-volume-spike-01.pine | 26 ++ ...n__mtf-htf-confluence-manual-trail-01.pine | 77 ++++++ ..._mtf-htf-confluence-static-bracket-01.pine | 75 ++++++ ...lidation__mtf-htf-weekly-sma-cross-01.pine | 24 ++ ...n__mtf-roll-state-60-240-d-minimal-01.pine | 36 +++ ...on__mtf-triple-tf-close-confluence-01.pine | 39 +++ ...mtf-triple-tf-macd-hist-confluence-01.pine | 47 ++++ ...validation__na-deep-history-int-na-01.pine | 51 ++++ ...dation__na-nz-fixnan-history-chain-01.pine | 29 +++ ...__oca-exit-bracket-internal-cancel-01.pine | 39 +++ ...ation__oca-multi-bracket-isolation-01.pine | 46 ++++ ...ion__oca-raw-strategy-order-reduce-01.pine | 33 +++ ...dation__order-close-all-cancel-all-01.pine | 33 +++ ..._order-close-immediate-vs-next-bar-01.pine | 27 ++ ...order-cross-entry-cancel-same-pass-01.pine | 38 +++ ..._order-cross-entry-close-same-pass-01.pine | 38 +++ ...__order-cross-exit-close-same-pass-01.pine | 32 +++ ...deferred-flip-guaranteed-gap-stops-01.pine | 63 +++++ ...order-deferred-flip-pooc-cross-bar-01.pine | 54 ++++ ..._order-dual-four-bar-stop-no-close-01.pine | 30 +++ ...r-dual-side-same-id-stop-no-cancel-01.pine | 31 +++ ...rder-dual-stop-both-touch-priority-01.pine | 30 +++ ...n__order-dual-stop-cancel-rotation-01.pine | 32 +++ ...lidation__order-dual-stop-far-only-01.pine | 22 ++ ...idation__order-dual-stop-near-only-01.pine | 22 ++ ...der-dual-stop-open-high-first-path-01.pine | 26 ++ ...rder-dual-stop-open-low-first-path-01.pine | 26 ++ ...lidation__order-dual-stop-open-tie-01.pine | 30 +++ ...-dual-stop-source-order-long-first-01.pine | 23 ++ ...dual-stop-source-order-short-first-01.pine | 23 ++ ...order-entry-implicit-reversal-exit-01.pine | 29 +++ ...n__order-flip-stop-no-paired-close-01.pine | 37 +++ ...ion__order-market-close-fill-basis-01.pine | 27 ++ ...der-one-side-four-bar-far-opposite-01.pine | 28 +++ ...der-opposite-entry-close-same-pass-01.pine | 33 +++ ...der-percent-equity-cash-commission-01.pine | 21 ++ ...tion__order-process-on-close-false-01.pine | 35 +++ ...ation__order-process-on-close-true-01.pine | 36 +++ ...order-range-expansion-pending-stop-01.pine | 55 ++++ ...order-same-id-entry-close-same-bar-01.pine | 36 +++ ..._order-same-id-market-entry-repeat-01.pine | 30 +++ ...ion__order-same-id-stop-after-flat-01.pine | 31 +++ ...r-same-id-stop-cross-before-modify-01.pine | 41 +++ ...on__order-same-id-stop-minute-zero-01.pine | 28 +++ ...n__order-same-id-stop-modification-01.pine | 31 +++ ...ion__order-same-id-stop-raise-only-01.pine | 31 +++ ...rder-same-id-stop-window-four-bars-01.pine | 24 ++ ...r-stale-stop-after-close-no-cancel-01.pine | 29 +++ ..._order-stop-cancel-no-regime-close-01.pine | 47 ++++ ...__order-stop-entry-cancel-opposite-01.pine | 35 +++ ...order-stop-entry-reversal-grouping-01.pine | 33 +++ ...n__order-stop-entry-touch-boundary-01.pine | 38 +++ ...pyramid-cash-fractional-commission-01.pine | 37 +++ ...idation__pyramid-close-id-grouping-01.pine | 40 +++ ...n__pyramid-deferred-flip-close-all-01.pine | 44 ++++ ...on__pyramid-flip-stop-pyramiding-2-01.pine | 46 ++++ ..._recompute-alma-sar-corr-magnifier-01.pine | 36 +++ ...idation__recompute-mtf-rsi-macd-bb-01.pine | 51 ++++ ...sk-max-contracts-held-gate-pyramid-01.pine | 32 +++ ...__session-hour-minute-pulse-filter-01.pine | 25 ++ ...ion__session-ny-spring-forward-dst-01.pine | 40 +++ ...n__stats-eventrades-zero-pnl-count-01.pine | 48 ++++ .../validation__ta-accdist-ema-cross-01.pine | 24 ++ ...idation__ta-bb-kc-squeeze-breakout-01.pine | 57 +++++ ...lidation__ta-bb-rsi-mean-reversion-01.pine | 41 +++ ...validation__ta-cci-threshold-cross-01.pine | 39 +++ ...tion__ta-chandelier-exit-direction-01.pine | 45 ++++ ...ta-closedtrades-risk-introspection-01.pine | 37 +++ .../validation__ta-cmo-9-zero-cross-01.pine | 23 ++ ...validation__ta-cog-10-signal-cross-01.pine | 25 ++ .../validation__ta-dmi-adx-di-cross-01.pine | 27 ++ ...tion__ta-donchian-channel-breakout-01.pine | 42 ++++ ...dation__ta-dual-ma-switch-dispatch-01.pine | 42 ++++ ...ta-dual-thrust-open-anchored-range-01.pine | 47 ++++ ...tion__ta-elder-ray-bull-bear-power-01.pine | 45 ++++ ...on__ta-ema-ribbon-stack-transition-01.pine | 40 +++ ...ation__ta-engulfing-candle-pattern-01.pine | 60 +++++ ...ta-highestbars-lowestbars-breakout-01.pine | 47 ++++ .../validation__ta-hma-55-close-cross-01.pine | 26 ++ ...validation__ta-hma-fast-slow-cross-01.pine | 30 +++ ...alidation__ta-inside-bar-engulfing-01.pine | 48 ++++ ...on__ta-kama-style-efficiency-ratio-01.pine | 50 ++++ ...lidation__ta-keltner-channel-break-01.pine | 62 +++++ ...on__ta-linreg-stdev-channel-revert-01.pine | 40 +++ ..._ta-macd-12-26-9-line-signal-cross-01.pine | 26 ++ ...dation__ta-macd-histogram-reversal-01.pine | 32 +++ ...cd-line-gt-signal-continuous-state-01.pine | 23 ++ ...on__ta-map-regime-threshold-lookup-01.pine | 27 ++ ...validation__ta-median-vs-ema-cross-01.pine | 30 +++ .../validation__ta-mfi-14-bands-20-80-01.pine | 24 ++ ...dation__ta-momentum-roc-zero-cross-01.pine | 38 +++ ...ta-multi-indicator-score-composite-01.pine | 62 +++++ .../ok/validation__ta-nvi-pvi-cross-01.pine | 26 ++ .../ok/validation__ta-obv-ema-cross-01.pine | 24 ++ ...ion__ta-percentrank-mean-reversion-01.pine | 43 ++++ ...dation__ta-pivot-array-unshift-pop-01.pine | 62 +++++ ...lidation__ta-pivot-atr-stop-target-01.pine | 46 ++++ ...lidation__ta-pivot-confirmed-break-01.pine | 62 +++++ ...ation__ta-pivot-point-levels-break-01.pine | 28 +++ .../ok/validation__ta-pvt-ema-cross-01.pine | 26 ++ ...lidation__ta-range-filter-var-band-01.pine | 53 ++++ .../validation__ta-rci-14-zero-cross-01.pine | 26 ++ .../validation__ta-rsi-bb-self-bands-01.pine | 44 ++++ ...alidation__ta-rsi-ema-signal-cross-01.pine | 32 +++ ...__ta-rsi-macd-and-continuous-state-01.pine | 33 +++ .../validation__ta-rsi14-bands-30-70-01.pine | 26 ++ .../ok/validation__ta-rsi14-cross-50-01.pine | 27 ++ ...n__ta-rsi14-gt-50-continuous-state-01.pine | 27 ++ ...tion__ta-rsi14-gt60-lt45-no-matrix-01.pine | 27 ++ .../ok/validation__ta-sar-flip-entry-01.pine | 60 +++++ ...validation__ta-sma-152-close-cross-01.pine | 27 ++ .../ok/validation__ta-sma-dual-cross-01.pine | 54 ++++ ...tion__ta-stdev-sma-expansion-break-01.pine | 56 +++++ ...alidation__ta-stoch-slow-k-d-cross-01.pine | 60 +++++ ...alidation__ta-stochastic-rsi-cross-01.pine | 39 +++ ...idation__ta-str-match-regex-filter-01.pine | 34 +++ ...lidation__ta-supertrend-adx-filter-01.pine | 45 ++++ ...tion__ta-supertrend-direction-flip-01.pine | 65 +++++ ...idation__ta-triple-sma-stack-latch-01.pine | 46 ++++ ...idation__ta-tsi-25-13-signal-cross-01.pine | 22 ++ ...tion__ta-volume-spike-atr-breakout-01.pine | 40 +++ ...idation__ta-vwma-vs-sma-divergence-01.pine | 33 +++ .../ok/validation__ta-wpr-14-bands-01.pine | 22 ++ ...imeframe-main-period-self-adaptive-01.pine | 34 +++ ...dt-method-calls-sibling-cumulative-01.pine | 98 ++++++++ ...n__udt-method-default-param-kwargs-01.pine | 64 +++++ ...__udt-method-drives-strategy-entry-01.pine | 62 +++++ ...n__udt-method-extra-primitive-args-01.pine | 42 ++++ ...-method-feeds-strategy-exit-prices-01.pine | 55 ++++ ...validation__udt-method-in-for-loop-01.pine | 55 ++++ ...tion__udt-method-in-if-else-branch-01.pine | 65 +++++ ...idation__udt-method-in-switch-arms-01.pine | 64 +++++ ...lidation__udt-method-in-while-loop-01.pine | 58 +++++ ...tion__udt-method-mutating-self-ref-01.pine | 51 ++++ ...ation__udt-method-on-array-element-01.pine | 98 ++++++++ ...n__udt-method-reads-strategy-state-01.pine | 53 ++++ ...dt-method-receives-ta-series-param-01.pine | 57 +++++ ...lidation__udt-method-scalar-return-01.pine | 51 ++++ ...dt-method-tuple-return-destructure-01.pine | 61 +++++ ...n__udt-method-udt-return-from-func-01.pine | 67 +++++ ...n__udt-method-uses-history-globals-01.pine | 60 +++++ ...dation__udt-method-uses-math-funcs-01.pine | 59 +++++ ...tion__udt-method-uses-na-nz-fixnan-01.pine | 50 ++++ ...on__udt-method-var-instance-streak-01.pine | 51 ++++ ...__udt-method-windowed-method-chain-01.pine | 78 ++++++ ...alidation__udt-regime-stack-stress-01.pine | 186 ++++++++++++++ ...dation__vwap-bands-breakout-1sigma-01.pine | 21 ++ ...__vwap-bands-mean-reversion-2sigma-01.pine | 23 ++ 275 files changed, 12594 insertions(+) create mode 100644 tests/gate-corpus/err/chart_bg_color.pine create mode 100644 tests/gate-corpus/err/const_ns_plot_style_free.pine create mode 100644 tests/gate-corpus/err/const_ns_shape_free.pine create mode 100644 tests/gate-corpus/err/export_func.pine create mode 100644 tests/gate-corpus/err/footprint_new.pine create mode 100644 tests/gate-corpus/err/hard_reject_dividends.pine create mode 100644 tests/gate-corpus/err/matrix_unknown_elem.pine create mode 100644 tests/gate-corpus/err/multi_error_one_line.pine create mode 100644 tests/gate-corpus/err/sec_tf_invalid.pine create mode 100644 tests/gate-corpus/err/unknown_color.pine create mode 100644 tests/gate-corpus/err/unknown_math.pine create mode 100644 tests/gate-corpus/err/unknown_str.pine create mode 100644 tests/gate-corpus/err/unknown_strategy_fn.pine create mode 100644 tests/gate-corpus/err/unknown_syminfo.pine create mode 100644 tests/gate-corpus/err/unknown_ta.pine create mode 100644 tests/gate-corpus/ok/AAPL__session-ismarket-nyse-rth-01.pine create mode 100644 tests/gate-corpus/ok/AAPL__time-tradingday-daily-reset-counter-01.pine create mode 100644 tests/gate-corpus/ok/QQQ__session-ispremarket-nasdaq-01.pine create mode 100644 tests/gate-corpus/ok/SPY__session-firstbar-vwap-anchor-01.pine create mode 100644 tests/gate-corpus/ok/SPY__session-lastbar-flatten-01.pine create mode 100644 tests/gate-corpus/ok/crypto-htf__mtf-htf-monthly-ema-cross-01.pine create mode 100644 tests/gate-corpus/ok/crypto-leverage__leverage-margin-call-perp-5x-01.pine create mode 100644 tests/gate-corpus/ok/forex__symbol-fx-5dp-eurusd-01.pine create mode 100644 tests/gate-corpus/ok/futures__symbol-futures-pointvalue-es-01.pine create mode 100644 tests/gate-corpus/ok/tutorial__macd__strategy.pine create mode 100644 tests/gate-corpus/ok/tutorial__mtf__strategy_htf.pine create mode 100644 tests/gate-corpus/ok/tutorial__mtf__strategy_ltf.pine create mode 100644 tests/gate-corpus/ok/us-equity__symbol-equity-rth-gaps-aapl-01.pine create mode 100644 tests/gate-corpus/ok/us-equity__us-equity-exchange-tz-intraday-cap-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-parity-choch-bos-isolator-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-parity-edge-margin-50-pct-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-parity-percent-of-equity-sizing-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-parity-small-equity-fraction-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-parity-stop-limit-timing-01.pine create mode 100644 tests/gate-corpus/ok/validation__analyzer-self-test-multi-mode-01.pine create mode 100644 tests/gate-corpus/ok/validation__anomaly-equity-mirror-strategy-equity-01.pine create mode 100644 tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-off-01b.pine create mode 100644 tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-on-01a.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-atr-trail-series-int-points-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-atr-trailing-stop-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-entry-exit-same-pass-attach-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-exit-stop-limit-trail-same-bar-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-exit-three-way-set-once-entry-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-exit-tp-sl-fixed-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-narrow-stop-limit-with-trail8-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-partial-exit-qty-percent-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-same-id-exit-replace-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-tp-sl-oca-reduce-isolate-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-trail-points-no-offset-explicit-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-trail-points-with-offset-only-01.pine create mode 100644 tests/gate-corpus/ok/validation__bracket-trailing-activation-offset-path-01.pine create mode 100644 tests/gate-corpus/ok/validation__cap-max-intraday-filled-orders-isolate-01.pine create mode 100644 tests/gate-corpus/ok/validation__cap-risk-gates-allow-max-intraday-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-4emarsi-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-4emarsi-quad-ema-stack-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-4emarsi-rsi-pullback-latch-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-4emarsi-session-window-nbar-exit-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-boscurv-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-boscurv-linreg-slope-channel-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-boscurv-pivot-bos-trigger-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-bracket-cap-range-pending-stop-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-adx-regime-classify-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-bb-kc-squeeze-release-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-cooldown-daily-cap-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-equity-feedback-sizing-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-pivot-liquidity-sweep-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-pressure-gauge-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-rsi-macd-momentum-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-ies-three-ema-bias-score-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kanuck-calc-on-every-tick-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kanuck-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kanuck-kama-state-recurrence-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kanuck-max-bars-back-500-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kkb-ema-atr-breakout-band-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kkb-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kkb-kalman-filter-1d-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-kkb-margin-100-pct-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-liqsweep-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-liqsweep-pivot-hh-ll-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-liqsweep-wait-one-continuation-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-liqsweep-wick-pierce-close-back-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-marketshift-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-marketshift-pivot-state-machine-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-marketshift-rolling-highest-lowest-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-marketshift-state-edge-detector-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-scalping-fast-ma-cross-trigger-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-scalping-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-scalping-tight-tp-sl-points-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-trendmaster-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-trendmaster-line-new-projection-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-trendmaster-pivot-anchored-bracket-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-trendmaster-three-tier-ema-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-trendmaster-trend-momentum-structure-gate-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-cumulative-volume-delta-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-fvg-active-zones-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-manual-adx-regime-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-pivot-strength-5-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-rsi-smooth-divergence-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-session-tz-newyork-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-vcp-vol-zscore-anomaly-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-wunderscalper-alert-templates-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-wunderscalper-explicit-reverse-01.pine create mode 100644 tests/gate-corpus/ok/validation__composite-wunderscalper-integration-01.pine create mode 100644 tests/gate-corpus/ok/validation__input-source-runtime-override-high-01.pine create mode 100644 tests/gate-corpus/ok/validation__input-source-subscript-hl2-01.pine create mode 100644 tests/gate-corpus/ok/validation__ltf-bool-array-bull-majority-01.pine create mode 100644 tests/gate-corpus/ok/validation__ltf-numeric-float-ratio15-01.pine create mode 100644 tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-01.pine create mode 100644 tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-rsi-cross-08a.pine create mode 100644 tests/gate-corpus/ok/validation__magnifier-tick-dist-volume-weighted-on-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-bool-mask-explicit-utc-tz-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-bool-mask-no-transpose-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-bool-mask-transpose-roundtrip-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-bool-regime-mask-24x7-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-covariance-eigen-pca-01.pine create mode 100644 tests/gate-corpus/ok/validation__matrix-eigen-rank-deficient-cov-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-daily-array-median-percentrank-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-daily-ema26-warmup-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-daily-prev-high-break-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-dual-tf-60-240-rising-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-close-change-baseline-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-close-roll-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-gaps-on-roll-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-rsi14-inside-security-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-sma20-inside-security-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-60-volume-spike-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-confluence-manual-trail-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-confluence-static-bracket-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-htf-weekly-sma-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-roll-state-60-240-d-minimal-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-triple-tf-close-confluence-01.pine create mode 100644 tests/gate-corpus/ok/validation__mtf-triple-tf-macd-hist-confluence-01.pine create mode 100644 tests/gate-corpus/ok/validation__na-deep-history-int-na-01.pine create mode 100644 tests/gate-corpus/ok/validation__na-nz-fixnan-history-chain-01.pine create mode 100644 tests/gate-corpus/ok/validation__oca-exit-bracket-internal-cancel-01.pine create mode 100644 tests/gate-corpus/ok/validation__oca-multi-bracket-isolation-01.pine create mode 100644 tests/gate-corpus/ok/validation__oca-raw-strategy-order-reduce-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-close-all-cancel-all-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-close-immediate-vs-next-bar-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-cross-entry-cancel-same-pass-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-cross-entry-close-same-pass-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-cross-exit-close-same-pass-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-deferred-flip-guaranteed-gap-stops-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-deferred-flip-pooc-cross-bar-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-four-bar-stop-no-close-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-side-same-id-stop-no-cancel-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-both-touch-priority-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-cancel-rotation-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-far-only-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-near-only-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-open-high-first-path-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-open-low-first-path-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-open-tie-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-source-order-long-first-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-dual-stop-source-order-short-first-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-entry-implicit-reversal-exit-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-flip-stop-no-paired-close-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-market-close-fill-basis-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-one-side-four-bar-far-opposite-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-opposite-entry-close-same-pass-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-percent-equity-cash-commission-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-process-on-close-false-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-process-on-close-true-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-range-expansion-pending-stop-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-entry-close-same-bar-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-market-entry-repeat-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-after-flat-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-cross-before-modify-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-minute-zero-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-modification-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-raise-only-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-same-id-stop-window-four-bars-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-stale-stop-after-close-no-cancel-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-stop-cancel-no-regime-close-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-stop-entry-cancel-opposite-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-stop-entry-reversal-grouping-01.pine create mode 100644 tests/gate-corpus/ok/validation__order-stop-entry-touch-boundary-01.pine create mode 100644 tests/gate-corpus/ok/validation__pyramid-cash-fractional-commission-01.pine create mode 100644 tests/gate-corpus/ok/validation__pyramid-close-id-grouping-01.pine create mode 100644 tests/gate-corpus/ok/validation__pyramid-deferred-flip-close-all-01.pine create mode 100644 tests/gate-corpus/ok/validation__pyramid-flip-stop-pyramiding-2-01.pine create mode 100644 tests/gate-corpus/ok/validation__recompute-alma-sar-corr-magnifier-01.pine create mode 100644 tests/gate-corpus/ok/validation__recompute-mtf-rsi-macd-bb-01.pine create mode 100644 tests/gate-corpus/ok/validation__risk-max-contracts-held-gate-pyramid-01.pine create mode 100644 tests/gate-corpus/ok/validation__session-hour-minute-pulse-filter-01.pine create mode 100644 tests/gate-corpus/ok/validation__session-ny-spring-forward-dst-01.pine create mode 100644 tests/gate-corpus/ok/validation__stats-eventrades-zero-pnl-count-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-accdist-ema-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-bb-kc-squeeze-breakout-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-bb-rsi-mean-reversion-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-cci-threshold-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-chandelier-exit-direction-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-closedtrades-risk-introspection-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-cmo-9-zero-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-cog-10-signal-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-dmi-adx-di-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-donchian-channel-breakout-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-dual-ma-switch-dispatch-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-dual-thrust-open-anchored-range-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-elder-ray-bull-bear-power-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-ema-ribbon-stack-transition-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-engulfing-candle-pattern-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-highestbars-lowestbars-breakout-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-hma-55-close-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-hma-fast-slow-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-inside-bar-engulfing-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-kama-style-efficiency-ratio-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-keltner-channel-break-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-linreg-stdev-channel-revert-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-macd-12-26-9-line-signal-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-macd-histogram-reversal-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-macd-line-gt-signal-continuous-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-map-regime-threshold-lookup-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-median-vs-ema-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-mfi-14-bands-20-80-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-momentum-roc-zero-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-multi-indicator-score-composite-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-nvi-pvi-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-obv-ema-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-percentrank-mean-reversion-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-pivot-array-unshift-pop-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-pivot-atr-stop-target-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-pivot-confirmed-break-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-pivot-point-levels-break-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-pvt-ema-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-range-filter-var-band-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rci-14-zero-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi-bb-self-bands-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi-ema-signal-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi-macd-and-continuous-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi14-bands-30-70-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi14-cross-50-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi14-gt-50-continuous-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-rsi14-gt60-lt45-no-matrix-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-sar-flip-entry-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-sma-152-close-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-sma-dual-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-stdev-sma-expansion-break-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-stoch-slow-k-d-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-stochastic-rsi-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-str-match-regex-filter-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-supertrend-adx-filter-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-supertrend-direction-flip-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-triple-sma-stack-latch-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-tsi-25-13-signal-cross-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-volume-spike-atr-breakout-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-vwma-vs-sma-divergence-01.pine create mode 100644 tests/gate-corpus/ok/validation__ta-wpr-14-bands-01.pine create mode 100644 tests/gate-corpus/ok/validation__timeframe-main-period-self-adaptive-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-calls-sibling-cumulative-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-default-param-kwargs-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-drives-strategy-entry-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-extra-primitive-args-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-feeds-strategy-exit-prices-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-in-for-loop-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-in-if-else-branch-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-in-switch-arms-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-in-while-loop-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-mutating-self-ref-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-on-array-element-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-reads-strategy-state-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-receives-ta-series-param-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-scalar-return-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-tuple-return-destructure-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-udt-return-from-func-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-uses-history-globals-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-uses-math-funcs-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-uses-na-nz-fixnan-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-var-instance-streak-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-method-windowed-method-chain-01.pine create mode 100644 tests/gate-corpus/ok/validation__udt-regime-stack-stress-01.pine create mode 100644 tests/gate-corpus/ok/validation__vwap-bands-breakout-1sigma-01.pine create mode 100644 tests/gate-corpus/ok/validation__vwap-bands-mean-reversion-2sigma-01.pine diff --git a/tests/gate-corpus/err/chart_bg_color.pine b/tests/gate-corpus/err/chart_bg_color.pine new file mode 100644 index 0000000..7acec4e --- /dev/null +++ b/tests/gate-corpus/err/chart_bg_color.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +c = chart.bg_color diff --git a/tests/gate-corpus/err/const_ns_plot_style_free.pine b/tests/gate-corpus/err/const_ns_plot_style_free.pine new file mode 100644 index 0000000..994736c --- /dev/null +++ b/tests/gate-corpus/err/const_ns_plot_style_free.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +p = plot.style_line diff --git a/tests/gate-corpus/err/const_ns_shape_free.pine b/tests/gate-corpus/err/const_ns_shape_free.pine new file mode 100644 index 0000000..81118bf --- /dev/null +++ b/tests/gate-corpus/err/const_ns_shape_free.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +s = shape.triangleup diff --git a/tests/gate-corpus/err/export_func.pine b/tests/gate-corpus/err/export_func.pine new file mode 100644 index 0000000..292c2b6 --- /dev/null +++ b/tests/gate-corpus/err/export_func.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +export f(x) => x * 2 diff --git a/tests/gate-corpus/err/footprint_new.pine b/tests/gate-corpus/err/footprint_new.pine new file mode 100644 index 0000000..df73bc9 --- /dev/null +++ b/tests/gate-corpus/err/footprint_new.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +fp = footprint.new(10) diff --git a/tests/gate-corpus/err/hard_reject_dividends.pine b/tests/gate-corpus/err/hard_reject_dividends.pine new file mode 100644 index 0000000..b06404e --- /dev/null +++ b/tests/gate-corpus/err/hard_reject_dividends.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = request.dividends(syminfo.tickerid, dividends.gross) diff --git a/tests/gate-corpus/err/matrix_unknown_elem.pine b/tests/gate-corpus/err/matrix_unknown_elem.pine new file mode 100644 index 0000000..f2f5a08 --- /dev/null +++ b/tests/gate-corpus/err/matrix_unknown_elem.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +var m = matrix.new(2, 2) diff --git a/tests/gate-corpus/err/multi_error_one_line.pine b/tests/gate-corpus/err/multi_error_one_line.pine new file mode 100644 index 0000000..7b8c60c --- /dev/null +++ b/tests/gate-corpus/err/multi_error_one_line.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = ta.bogus(math.qux(close)) diff --git a/tests/gate-corpus/err/sec_tf_invalid.pine b/tests/gate-corpus/err/sec_tf_invalid.pine new file mode 100644 index 0000000..a1de599 --- /dev/null +++ b/tests/gate-corpus/err/sec_tf_invalid.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = request.security("ETHUSDT", "abc", close) diff --git a/tests/gate-corpus/err/unknown_color.pine b/tests/gate-corpus/err/unknown_color.pine new file mode 100644 index 0000000..54bea7b --- /dev/null +++ b/tests/gate-corpus/err/unknown_color.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +c = color.not_real(close) diff --git a/tests/gate-corpus/err/unknown_math.pine b/tests/gate-corpus/err/unknown_math.pine new file mode 100644 index 0000000..d80c4da --- /dev/null +++ b/tests/gate-corpus/err/unknown_math.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = math.bogus(close) diff --git a/tests/gate-corpus/err/unknown_str.pine b/tests/gate-corpus/err/unknown_str.pine new file mode 100644 index 0000000..cf7b38e --- /dev/null +++ b/tests/gate-corpus/err/unknown_str.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = str.frobnicate("a") diff --git a/tests/gate-corpus/err/unknown_strategy_fn.pine b/tests/gate-corpus/err/unknown_strategy_fn.pine new file mode 100644 index 0000000..f15edbb --- /dev/null +++ b/tests/gate-corpus/err/unknown_strategy_fn.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +strategy.not_a_real_function(1) diff --git a/tests/gate-corpus/err/unknown_syminfo.pine b/tests/gate-corpus/err/unknown_syminfo.pine new file mode 100644 index 0000000..6e9140a --- /dev/null +++ b/tests/gate-corpus/err/unknown_syminfo.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = syminfo.not_a_real_field diff --git a/tests/gate-corpus/err/unknown_ta.pine b/tests/gate-corpus/err/unknown_ta.pine new file mode 100644 index 0000000..7de142a --- /dev/null +++ b/tests/gate-corpus/err/unknown_ta.pine @@ -0,0 +1,3 @@ +//@version=6 +strategy("T") +x = ta.totally_made_up(close, 14) diff --git a/tests/gate-corpus/ok/AAPL__session-ismarket-nyse-rth-01.pine b/tests/gate-corpus/ok/AAPL__session-ismarket-nyse-rth-01.pine new file mode 100644 index 0000000..ff93ace --- /dev/null +++ b/tests/gate-corpus/ok/AAPL__session-ismarket-nyse-rth-01.pine @@ -0,0 +1,16 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: AAPL TF: 5m Range: 2024-01-02..2024-03-29 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("Session ismarket NYSE RTH gate", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1) + +// Only trade when inside NYSE regular trading hours (09:30–16:00 ET). +// session.ismarket uses syminfo.session which the validator injects via +// syminfo_overrides → session = "0930-1600". +if session.ismarket + if ta.crossover(ta.sma(close, 9), ta.sma(close, 21)) + strategy.entry("Long", strategy.long) + if ta.crossunder(ta.sma(close, 9), ta.sma(close, 21)) + strategy.close("Long") diff --git a/tests/gate-corpus/ok/AAPL__time-tradingday-daily-reset-counter-01.pine b/tests/gate-corpus/ok/AAPL__time-tradingday-daily-reset-counter-01.pine new file mode 100644 index 0000000..94a9c02 --- /dev/null +++ b/tests/gate-corpus/ok/AAPL__time-tradingday-daily-reset-counter-01.pine @@ -0,0 +1,44 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: AAPL TF: 30m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. +// +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe: time_tradingday — daily trade-counter reset +// +// Purpose: Verify that ta.change(time_tradingday) fires exactly once per +// NYSE RTH session open (09:30 ET), resetting a daily trade counter. +// Counter even → go long; counter odd → close position. +// +// Uses AAPL on 30 m bars with the default NYSE session ("0930-1600:23456") +// so that time_tradingday advances at each new RTH open. + +//@version=6 +strategy("time_tradingday daily reset counter", + overlay=true, + default_qty_type=strategy.fixed, + default_qty_value=1, + initial_capital=10000, + commission_type=strategy.commission.percent, + commission_value=0.0) + +// Count how many entries (long+flat signals) we have fired today. +// Reset at each new trading day. +var int daily_counter = 0 + +bool new_trading_day = ta.change(time_tradingday) != 0 + +if new_trading_day + daily_counter := 0 + +// Even counter → long entry; odd counter → close. +if daily_counter % 2 == 0 + if strategy.position_size == 0 + strategy.entry("L", strategy.long) + daily_counter += 1 +else + if strategy.position_size > 0 + strategy.close("L") + daily_counter += 1 diff --git a/tests/gate-corpus/ok/QQQ__session-ispremarket-nasdaq-01.pine b/tests/gate-corpus/ok/QQQ__session-ispremarket-nasdaq-01.pine new file mode 100644 index 0000000..84dad3d --- /dev/null +++ b/tests/gate-corpus/ok/QQQ__session-ispremarket-nasdaq-01.pine @@ -0,0 +1,16 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: QQQ TF: 5m Range: 2024-01-02..2024-03-29 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("Session ispremarket NASDAQ gate", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1) + +// Enter long during premarket (04:00–09:30 ET) on bullish momentum; +// flatten at market open. +if session.ispremarket + if close > open + strategy.entry("PreLong", strategy.long) + +if session.ismarket + strategy.close_all() diff --git a/tests/gate-corpus/ok/SPY__session-firstbar-vwap-anchor-01.pine b/tests/gate-corpus/ok/SPY__session-firstbar-vwap-anchor-01.pine new file mode 100644 index 0000000..270ccf7 --- /dev/null +++ b/tests/gate-corpus/ok/SPY__session-firstbar-vwap-anchor-01.pine @@ -0,0 +1,29 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: SPY TF: 5m Range: 2024-01-02..2024-03-29 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("Session isfirstbar VWAP anchor", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1) + +// Anchor VWAP to the first bar of each trading session using session.isfirstbar. +// Buy when close crosses above session VWAP; sell when it crosses below. + +var float cum_pv = 0.0 +var float cum_vol = 0.0 + +if session.isfirstbar + cum_pv := 0.0 + cum_vol := 0.0 + +if session.ismarket + cum_pv := cum_pv + hlc3 * volume + cum_vol := cum_vol + volume + +session_vwap = cum_vol > 0 ? cum_pv / cum_vol : close + +if session.ismarket + if ta.crossover(close, session_vwap) + strategy.entry("Long", strategy.long) + if ta.crossunder(close, session_vwap) + strategy.close("Long") diff --git a/tests/gate-corpus/ok/SPY__session-lastbar-flatten-01.pine b/tests/gate-corpus/ok/SPY__session-lastbar-flatten-01.pine new file mode 100644 index 0000000..05c868b --- /dev/null +++ b/tests/gate-corpus/ok/SPY__session-lastbar-flatten-01.pine @@ -0,0 +1,19 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: SPY TF: 5m Range: 2024-01-02..2024-03-29 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("Session islastbar flatten", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1) + +// Enter on SMA crossover within session; flatten all positions on the +// last bar of the session (session.islastbar) to avoid overnight holds. + +if session.ismarket + if ta.crossover(ta.sma(close, 9), ta.sma(close, 21)) + strategy.entry("Long", strategy.long) + +// Flatten on last session bar — session.islastbar_regular == session.islastbar +// in engine (single session string; see session_time.hpp limitation comment). +if session.islastbar + strategy.close_all() diff --git a/tests/gate-corpus/ok/crypto-htf__mtf-htf-monthly-ema-cross-01.pine b/tests/gate-corpus/ok/crypto-htf__mtf-htf-monthly-ema-cross-01.pine new file mode 100644 index 0000000..8a76289 --- /dev/null +++ b/tests/gate-corpus/ok/crypto-htf__mtf-htf-monthly-ema-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF MTF-isolate probe — monthly ("M") HTF EMA(6) via request.security. +// Isolates CALENDAR-MONTH aggregation: unlike "60"/"240"/"D" (fixed-width +// HTF buckets), the "M" timeframe rolls on variable-length calendar months +// (28–31 days, DST-agnostic). This exercises the engine's HTF bucket-boundary +// detection for irregular periods end-to-end. The HTF EMA(6) is computed +// INSIDE the request.security context so EMA state advances once per closed +// month, then is held flat across every intraday bar of that month. Trades +// fire when the LTF close crosses the held monthly EMA — any month-boundary +// or roll/seed drift surfaces as extra/early/missing crossings. +//@version=6 +strategy("PF MTF isolate - monthly EMA(6) cross", shorttitle="MTF_MEMA", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float mema = request.security(syminfo.tickerid, "M", ta.ema(close, 6), lookahead=barmerge.lookahead_off) + +if ta.crossover(close, mema) + strategy.entry("L", strategy.long, comment="close x-over monthly EMA") +if ta.crossunder(close, mema) + strategy.entry("S", strategy.short, comment="close x-under monthly EMA") diff --git a/tests/gate-corpus/ok/crypto-leverage__leverage-margin-call-perp-5x-01.pine b/tests/gate-corpus/ok/crypto-leverage__leverage-margin-call-perp-5x-01.pine new file mode 100644 index 0000000..609534e --- /dev/null +++ b/tests/gate-corpus/ok/crypto-leverage__leverage-margin-call-perp-5x-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// leverage-margin-call-perp-5x-01 — forced-liquidation / margin-call probe. +// +// Purpose: isolate TradingView's broker-emulator FORCED LIQUIDATION +// (margin call) behavior for an IN-POSITION long held through an adverse +// move at 5x leverage. The strategy declaration sets +// `margin_long=20, margin_short=20` (20% required margin == 5x leverage). +// A single, oversized long is entered once near the start of the feed and +// then NEVER exited by the script. If price falls far enough that the +// position's losses exhaust the posted margin, TradingView's emulator +// injects a "Margin Call" exit row that the script itself never issued. +// +// The PineForge engine currently has no in-position liquidation path: it +// holds the entry indefinitely and produces no margin-call exit. This +// probe is EXPECTED TO EXPOSE that gap — the divergence (TV emits a +// Margin Call exit, engine does not) is the finding, not a bug to mask. +// When exporting TV's List of Trades, be sure to capture the rows whose +// Type / Signal is "Margin Call" (TV labels the forced exit explicitly). +// +// Logic is intentionally minimal: one large fixed-qty long on the first +// bar after warmup, sized so that a routine adverse 15m perp move at 5x +// can wipe the margin. No exit, no pyramiding beyond the single entry. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P (Binance USDT-M perpetual). +// Set the Strategy Tester margin to 20% long / 20% short to match the +// `margin_long=20, margin_short=20` declaration (TV reads these from the +// script header, but verify the Properties tab shows 20/20). Export +// "List of Trades" — INCLUDING the Margin Call row — to tv_trades.csv. +//@version=6 +strategy("leverage-margin-call-perp-5x-01", shorttitle="lev_mc5x", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + margin_long=20, margin_short=20) + +// ---- one early, oversized long held with NO exit -------------------- +// Enter once: first bar at/after bar_index 50 (post-warmup) while flat. +// qty is sized to ~5x notional of initial_capital so a modest adverse +// move exhausts the 20% margin and triggers TV's forced liquidation. +bool fire = bar_index >= 50 and strategy.position_size == 0 and strategy.closedtrades == 0 + +// Notional target ≈ 5 × initial_capital; qty = target_notional / close. +float target_notional = 5000000.0 +float qty_lev = math.round(target_notional / close * 1000) / 1000 + +if fire and qty_lev > 0 + strategy.entry("LEV", strategy.long, qty=qty_lev, comment="5x held long, no exit") + +// No strategy.close / strategy.exit anywhere: the only way this position +// can leave the book is TradingView's forced-liquidation (Margin Call). diff --git a/tests/gate-corpus/ok/forex__symbol-fx-5dp-eurusd-01.pine b/tests/gate-corpus/ok/forex__symbol-fx-5dp-eurusd-01.pine new file mode 100644 index 0000000..d9a8531 --- /dev/null +++ b/tests/gate-corpus/ok/forex__symbol-fx-5dp-eurusd-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF symbol/FX 5-decimal probe — plain SMA(20) vs SMA(50) crossover on a +// 5-dp forex instrument (OANDA:EURUSD, mintick 0.00001). Isolates two things +// the ETH-USDT feed cannot exercise: +// 1. 5-decimal price scale (prices ~1.0xxxx, not ~1800), so any latent +// assumption about price magnitude / tick granularity surfaces. +// 2. Sub-pip slippage rounding: slippage=2 ticks * mintick 0.00001 = 0.00002 +// added/subtracted at fill, which must round to the 5th decimal exactly. +// strategy.close on the opposite signal keeps the position flat-or-long/flat- +// or-short with no pyramiding, so each fill is a clean single-leg adjudication. +//@version=6 +strategy("PF FX 5dp - SMA20/50 cross", shorttitle="FX5DP", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=2, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +fast = ta.sma(close, 20) +slow = ta.sma(close, 50) + +longCond = ta.crossover(fast, slow) +shortCond = ta.crossunder(fast, slow) + +if longCond + strategy.close("S", comment="close short") + strategy.entry("L", strategy.long, comment="sma cross up") +if shortCond + strategy.close("L", comment="close long") + strategy.entry("S", strategy.short, comment="sma cross dn") diff --git a/tests/gate-corpus/ok/futures__symbol-futures-pointvalue-es-01.pine b/tests/gate-corpus/ok/futures__symbol-futures-pointvalue-es-01.pine new file mode 100644 index 0000000..a691400 --- /dev/null +++ b/tests/gate-corpus/ok/futures__symbol-futures-pointvalue-es-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe: ES futures point-value PnL multiplier + 0.25-tick snap. +// +// Purpose: Isolate the per-instrument PnL scaling on a real CME futures +// contract. CME_MINI:ES1! has syminfo.pointvalue = 50 (USD per index +// point per contract) and syminfo.mintick = 0.25. With 1-contract fixed +// sizing, cash_per_contract commission ($2.10/contract) and slippage=1 +// tick, every closed trade's gross PnL must equal +// (exit_px - entry_px) * 50 and fills must snap to 0.25 boundaries. +// Plain SMA(9)/SMA(21) cross drives flat-to-long/long-to-flat so the +// trade set is deterministic and the PnL multiplier is the only moving +// part vs. the default crypto (pointvalue=1, mintick=0.01) behaviour. + +//@version=6 +strategy("ES point-value 9/21 SMA cross", + initial_capital=1000000, + currency=currency.USD, + commission_type=strategy.commission.cash_per_contract, + commission_value=2.10, + slippage=1, + default_qty_type=strategy.fixed, + default_qty_value=1, + pyramiding=1, + process_orders_on_close=false) + +fast = ta.sma(close, 9) +slow = ta.sma(close, 21) + +if ta.crossover(fast, slow) + strategy.entry("L", strategy.long) + +if ta.crossunder(fast, slow) + strategy.close("L") diff --git a/tests/gate-corpus/ok/tutorial__macd__strategy.pine b/tests/gate-corpus/ok/tutorial__macd__strategy.pine new file mode 100644 index 0000000..2f183fa --- /dev/null +++ b/tests/gate-corpus/ok/tutorial__macd__strategy.pine @@ -0,0 +1,45 @@ +//@version=6 +// +// PineForge tutorial — MACD line/signal crossover. +// +// This is the original Pine v6 source the tutorial is built around. +// It is kept here for reference only; PineForge ships its compiler +// closed-source, so the actual backtest binary is built from the +// sibling generated.cpp (which mirrors the engine bytecode this Pine +// would produce). Read both side-by-side to see how each Pine line +// maps onto the runtime API. +// +strategy("MACD Crossover (tutorial)", + overlay = false, + initial_capital = 1000000, + currency = currency.USD, + process_orders_on_close = false, + pyramiding = 1, + commission_type = strategy.commission.percent, + commission_value = 0, + slippage = 0, + default_qty_type = strategy.fixed, + default_qty_value = 1) + +// === Inputs === +fastLen = input.int(12, "Fast Length", minval=1) +slowLen = input.int(26, "Slow Length", minval=1) +signalLen = input.int(9, "Signal Length", minval=1) +src = input.source(close, "Source") + +// === MACD === +[macdLine, signalLine, histLine] = ta.macd(src, fastLen, slowLen, signalLen) + +// === Signals === +longCond = ta.crossover(macdLine, signalLine) +shortCond = ta.crossunder(macdLine, signalLine) + +// === Entries === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(histLine, "Histogram", style=plot.style_histogram, color=histLine >= 0 ? color.green : color.red) +plot(macdLine, "MACD", color=color.blue) +plot(signalLine, "Signal", color=color.orange) diff --git a/tests/gate-corpus/ok/tutorial__mtf__strategy_htf.pine b/tests/gate-corpus/ok/tutorial__mtf__strategy_htf.pine new file mode 100644 index 0000000..42bd91f --- /dev/null +++ b/tests/gate-corpus/ok/tutorial__mtf__strategy_htf.pine @@ -0,0 +1,49 @@ +//@version=6 +// +// PineForge tutorial — MTF demo (higher-timeframe filter via request.security). +// +// 15m chart cadence, MACD entries gated by an HTF SMA trend filter pulled +// in via `request.security`. The HTF and SMA length are inputs so the +// Python harness can sweep them without rebuilding the .so. +// +// Read alongside generated_htf.cpp to see how each Pine line maps onto +// the runtime API. The .pine here is reference only — the closed-source +// transpiler emits the sibling .cpp. +// +strategy("PineForge MTF tutorial — HTF SMA filter", + overlay = false, + initial_capital = 1000000, + currency = currency.USD, + process_orders_on_close = false, + pyramiding = 1, + commission_type = strategy.commission.percent, + commission_value = 0, + slippage = 0, + default_qty_type = strategy.fixed, + default_qty_value = 1) + +// === Inputs === +fastLen = input.int(12, "Fast Length", minval=1) +slowLen = input.int(26, "Slow Length", minval=1) +signalLen = input.int(9, "Signal Length", minval=1) +smaLen = input.int(20, "SMA Length", minval=1) +htf = input.string("60", "HTF") + +// === HTF trend filter via request.security === +// +// Note: htfSma is computed *inside* the HTF context (the ta.sma call +// runs as part of the per-HTF-bar evaluator the engine schedules +// separately from the chart bar). htfClose is the HTF close. +htfSma = request.security(syminfo.tickerid, htf, ta.sma(close, smaLen), lookahead=barmerge.lookahead_off) +htfClose = request.security(syminfo.tickerid, htf, close, lookahead=barmerge.lookahead_off) +trendUp = htfClose > htfSma + +// === MACD on chart TF === +[macdLine, signalLine, _hist] = ta.macd(close, fastLen, slowLen, signalLen) +longCond = ta.crossover(macdLine, signalLine) and trendUp +shortCond = ta.crossunder(macdLine, signalLine) and not trendUp + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/tutorial__mtf__strategy_ltf.pine b/tests/gate-corpus/ok/tutorial__mtf__strategy_ltf.pine new file mode 100644 index 0000000..264eff2 --- /dev/null +++ b/tests/gate-corpus/ok/tutorial__mtf__strategy_ltf.pine @@ -0,0 +1,58 @@ +//@version=6 +// +// PineForge tutorial — MTF demo (lower-timeframe via request.security_lower_tf). +// +// Chart cadence is the input feed (15m by default). Inside each chart +// bar, request.security_lower_tf("1", close) returns an array of +// synthetic 1-minute closes covering that bar. The engine builds those +// sub-bars from the chart bar's own OHLC path — there is no separate +// finer feed (contrast with TradingView, which downloads a real 1m +// stream when you switch the timeframe). +// +// Entry signal: the realized 1m range exceeds a threshold, i.e. the +// chart bar moved a meaningful distance intra-bar. This is the kind of +// signal you cannot compute from chart-TF OHLC alone — that's the +// point of the lower-TF surface. +// +// Read alongside generated_ltf.cpp for the C++ shape the runtime +// actually loads as strategy_ltf.so. +// +strategy("PineForge MTF tutorial — lower-TF intra-bar range", + overlay = false, + initial_capital = 1000000, + currency = currency.USD, + process_orders_on_close = false, + pyramiding = 1, + commission_type = strategy.commission.percent, + commission_value = 0, + slippage = 0, + default_qty_type = strategy.fixed, + default_qty_value = 1) + +// === Inputs === +rangePct = input.float(0.5, "Range threshold (% of bar close)", minval=0.0) + +// === Lower-TF sub-bar closes for the current chart bar === +// +// PF only supports lookahead=off, gaps=off on this builtin (TV does +// not expose them anyway). Target TF "1" must be a clean integer +// divisor of the chart's input TF. +sub = request.security_lower_tf(syminfo.tickerid, "1", close) + +// === Realized intra-bar range from the sub-bar array === +float subHi = na +float subLo = na +if array.size(sub) > 0 + subHi := array.max(sub) + subLo := array.min(sub) + +float pct = nz((subHi - subLo) / close * 100.0) + +// === Entries: long when intra-bar move was large *and* up === +longCond = pct > rangePct and close > open and strategy.position_size == 0 +exitCond = strategy.position_size > 0 and close < open + +if longCond + strategy.entry("Long", strategy.long) +if exitCond + strategy.close("Long") diff --git a/tests/gate-corpus/ok/us-equity__symbol-equity-rth-gaps-aapl-01.pine b/tests/gate-corpus/ok/us-equity__symbol-equity-rth-gaps-aapl-01.pine new file mode 100644 index 0000000..abf78b1 --- /dev/null +++ b/tests/gate-corpus/ok/us-equity__symbol-equity-rth-gaps-aapl-01.pine @@ -0,0 +1,34 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Isolates: US-equity regular-trading-hours (RTH) session gating combined with +// overnight gap handling on a 5m intraday feed. Plain RSI(14) oversold/overbought +// crosses (30/70) drive entries, but every order is gated by session.ismarket so +// only bars inside NYSE/NASDAQ regular hours (09:30–16:00 ET) can trade. This +// exercises the exchange-tz (America/New_York) vs chart-tz day boundary, the RTH +// session filter, and how the engine treats the overnight gap between the prior +// 16:00 close and the next 09:30 open (RSI series continuity across the gap). + +//@version=6 +strategy("Symbol equity RTH + overnight gaps AAPL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// RSI(14) on close — series spans the overnight gap continuously (no per-day +// reset), so the cross can latch on the first RTH bar after the gap. +rsiVal = ta.rsi(close, 14) + +// Only act on bars inside regular trading hours. session.ismarket is backed by +// syminfo.session, injected by the harness via runtime_overrides.session = +// "0930-1600" with timezone America/New_York. +inRth = session.ismarket + +// Oversold cross up 30 → long; overbought cross down 70 → flat. Gated by RTH. +if inRth and ta.crossover(rsiVal, 30) + strategy.entry("Long", strategy.long) + +if inRth and ta.crossunder(rsiVal, 70) + strategy.close("Long") diff --git a/tests/gate-corpus/ok/us-equity__us-equity-exchange-tz-intraday-cap-01.pine b/tests/gate-corpus/ok/us-equity__us-equity-exchange-tz-intraday-cap-01.pine new file mode 100644 index 0000000..ad08fda --- /dev/null +++ b/tests/gate-corpus/ok/us-equity__us-equity-exchange-tz-intraday-cap-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe (SCAFFOLD) — US-equity intraday-cap day rollover on a REAL session +// (#19 / #26). BLOCKED: needs a real RTH US-equity intraday OHLCV feed in +// corpus/data/ AND a matching TradingView export. See README. +// +// Purpose: validate that max_intraday_filled_orders resets on the EXCHANGE +// trading day for a real-session instrument. The engine keys the gate off +// chart_timezone_ (deliberate — probe-97 crypto evidence); for US equities +// the serving layer pins chart_timezone_ = America/New_York. This probe is +// the missing evidence that chart_tz pinning yields the correct rollover when +// chart_tz != UTC and the symbol has a real 09:30-16:00 session with +// overnight gaps. +// +//@version=6 +strategy("US-equity intraday cap (scaffold)", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// Cap is a strategy.risk.* call in the body, NOT a strategy() parameter. +strategy.risk.max_intraday_filled_orders(5) + +// Plain dual-MA crossover — the cap is the surface under test. +fast = ta.sma(close, 9) +slow = ta.sma(close, 21) + +if ta.crossover(fast, slow) + strategy.entry("Long", strategy.long) +if ta.crossunder(fast, slow) + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__analyzer-parity-choch-bos-isolator-01.pine b/tests/gate-corpus/ok/validation__analyzer-parity-choch-bos-isolator-01.pine new file mode 100644 index 0000000..027397d --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-parity-choch-bos-isolator-01.pine @@ -0,0 +1,83 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 02 — CHoCH / BOS structural-break isolator +// +// Purpose: isolate the VCP integration probe family's bullConfluence / +// _bosLong gating, which depends on market-structure events +// (Break-of-Structure ``bosUp``/``bosDown`` and Change-of-Character +// ``chochUp``/``chochDown``). That family fires one extra long entry on +// 2025-05-12 12:15 UTC because chochUp = TRUE there +// in the engine — its prior-bar bosDown at 11:45 flipped structureDir to +// -1, then close=2542.93 > prevSwingHigh=2526.61 satisfied chochUp. +// vcp-probe-01 already shows ``ta.pivothigh/pivotlow`` match TV +// bit-for-bit, so the disagreement must come from how +// lastSwingHigh/Low and prevSwingHigh/Low evolve through the +// pivot-update side effects, or from the inequality direction in the +// bos/choch booleans. This probe puts the entire VCP-style swing-tracking +// scaffold on its own and trades ONLY on those four boolean events +// (one-bar holding period) so the trade list itself becomes the diff +// against TV. +// +// Trade shape: pyramiding=0, single-contract, exit on the next bar +// after entry so each row in the trade export is exactly one +// bos_up / bos_dn / choch_up / choch_dn firing. No FVG, no MTF, no +// confluence stack — pure swing-state logic. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of Trades" to +// ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 02 - choch/bos isolator", shorttitle="par_p02", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=0, process_orders_on_close=false) + +// ---- inputs (mirror VCP defaults) ---------------------------------- +int i_pivot = input.int(5, "Pivot Strength", minval=2, maxval=20) + +// ---- swing tracking (BYTE-IDENTICAL to the VCP integration probe family) ----- +float pivotHigh = ta.pivothigh(high, i_pivot, i_pivot) +float pivotLow = ta.pivotlow(low, i_pivot, i_pivot) + +var float lastSwingHigh = na +var float lastSwingLow = na +var float prevSwingHigh = na +var float prevSwingLow = na +var int structureDirection = 0 + +if not na(pivotHigh) + prevSwingHigh := lastSwingHigh + lastSwingHigh := pivotHigh + +if not na(pivotLow) + prevSwingLow := lastSwingLow + lastSwingLow := pivotLow + +bool bosUp = not na(lastSwingHigh) and close > lastSwingHigh and structureDirection <= 0 +bool bosDown = not na(lastSwingLow) and close < lastSwingLow and structureDirection >= 0 +bool chochUp = not na(prevSwingHigh) and close > prevSwingHigh and structureDirection < 0 +bool chochDown = not na(prevSwingLow) and close < prevSwingLow and structureDirection > 0 + +if bosUp or chochUp + structureDirection := 1 +if bosDown or chochDown + structureDirection := -1 + +// ---- one-bar trades on every structural event ---------------------- +// We ENTRY-on-event and CLOSE-on-next-bar so each event is bracketed +// into a single trade row in the export. Exporting the event TYPE +// (long entry vs short entry) plus the timestamp lets us diff +// engine vs TV with no other state to confound the comparison. +bool any_bull_event = bosUp or chochUp +bool any_bear_event = bosDown or chochDown + +if any_bull_event and strategy.position_size == 0 + string c_bu = bosUp ? "bosUp" : "chochUp" + strategy.entry("BU", strategy.long, comment=c_bu) + +if any_bear_event and strategy.position_size == 0 + string c_bd = bosDown ? "bosDown" : "chochDown" + strategy.entry("BD", strategy.short, comment=c_bd) + +// Exit at next bar's open (process_orders_on_close=false default) +if strategy.position_size != 0 and bar_index > strategy.opentrades.entry_bar_index(0) + strategy.close_all(comment="event flatten") diff --git a/tests/gate-corpus/ok/validation__analyzer-parity-edge-margin-50-pct-01.pine b/tests/gate-corpus/ok/validation__analyzer-parity-edge-margin-50-pct-01.pine new file mode 100644 index 0000000..42ccafd --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-parity-edge-margin-50-pct-01.pine @@ -0,0 +1,39 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 06 — explicit qty at 50% of equity (moderate margin pressure) +// +// Purpose: bisect the margin-tolerance regime between parity-probe-03 +// (100% equity, hits the boundary every entry, 31 engine extras vs TV) +// and parity-probe-05 (10% equity, no margin pressure expected). +// +// At 50% of equity, qty * fill_price ≈ 0.5 × equity at every entry — +// no slippage-driven margin overshoot is ever possible (next bar's +// open would have to ~double the prior close). If engine and TV +// AGREE on every Monday here too, then TV's "extra rejections" in +// probe-03 are SOLELY a strict-margin behavior at the 1× boundary +// and the engine's tolerance / scale-down policy is the only knob +// that needs adjustment to close the IES gap. If engine and TV +// DISAGREE here, there is some other equity / position-size rule +// in TV's broker emulator that we have not yet modeled, and probe-04 +// (auto-sizing path) will tell us whether that rule is shared by +// the implicit-qty code path or unique to explicit qty. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of Trades" to +// ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 06 - 50% equity sizing", shorttitle="par_p06", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=0, process_orders_on_close=false) + +// ---- engine ------------------------------------------------------- +bool fire = dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size == 0 + +// qty encodes 50% of strategy.equity / close. +float qty_dyn = math.round(strategy.equity * 0.5 / close * 1000) / 1000 + +if fire and qty_dyn > 0 + strategy.entry("E", strategy.long, qty=qty_dyn, comment="qty = 50% equity / close") + +if strategy.position_size > 0 and bar_index > strategy.opentrades.entry_bar_index(0) + strategy.close("E", comment="next-bar flatten") diff --git a/tests/gate-corpus/ok/validation__analyzer-parity-percent-of-equity-sizing-01.pine b/tests/gate-corpus/ok/validation__analyzer-parity-percent-of-equity-sizing-01.pine new file mode 100644 index 0000000..560e0ab --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-parity-percent-of-equity-sizing-01.pine @@ -0,0 +1,42 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 04 — strategy.percent_of_equity sizing +// +// Purpose: parity-probe-03 (explicit qty = strategy.equity / close) shows +// engine takes 31 Mondays that TV silently rejects. The hypothesis is +// that TV's broker emulator applies an additional margin / equity rule +// that engine misses, but it's also possible the explicit-qty + margin +// path differs from TV's auto-sizing. This probe routes the SAME +// equity-feedback contract through Pine's NATIVE +// ``default_qty_type=strategy.percent_of_equity`` path, which lets TV's +// internal sizing engine choose qty itself instead of the user +// computing it. If engine matches TV exactly here, the bug is purely in +// how the engine handles user-provided ``qty=`` arguments. If engine +// STILL diverges, the bug is in the broader margin / equity rule. +// +// Trade shape: long-only, default_qty_type=percent_of_equity, qty +// argument OMITTED so TV's sizing engine takes over. Same Monday +// 00:00 UTC trigger and 1-bar exit as parity-probe-03. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of Trades" to +// ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 04 - percent_of_equity sizing", shorttitle="par_p04", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.percent_of_equity, default_qty_value=99, pyramiding=0, process_orders_on_close=false) + +// ---- engine ------------------------------------------------------- +// Wall-clock entry trigger: 00:00 UTC every Monday. Identical timing to +// parity-probe-03 so the only difference under test is the sizing path +// (auto vs explicit-qty). +bool fire = dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size == 0 + +if fire + // No qty= argument: TV uses default_qty_type=percent_of_equity + // with default_qty_value=99 (i.e., 99% of equity per entry). + strategy.entry("E", strategy.long, comment="auto-sized 99% equity") + +// Exit at next bar's open so each entry maps to a single trade row. +if strategy.position_size > 0 and bar_index > strategy.opentrades.entry_bar_index(0) + strategy.close("E", comment="next-bar flatten") diff --git a/tests/gate-corpus/ok/validation__analyzer-parity-small-equity-fraction-01.pine b/tests/gate-corpus/ok/validation__analyzer-parity-small-equity-fraction-01.pine new file mode 100644 index 0000000..7222a59 --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-parity-small-equity-fraction-01.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 05 — explicit qty at small fraction of equity (no margin pressure) +// +// Purpose: parity-probe-03 has qty = strategy.equity / close (≈100% of +// equity per trade), which puts every entry RIGHT at the 1× margin +// boundary; tiny slippage between signal-bar close and fill-bar open +// then flips margin OK ↔ FAIL. parity-probe-04 routes the same intent +// through TV's auto-sizing path. THIS probe takes the explicit-qty +// path BUT sizes at 10% of equity, so the margin check is satisfied +// by an order of magnitude. Engine and TV should agree on EVERY +// Monday entry — count_delta = 0%, PnL p90 = 0%. Any divergence here +// is therefore NOT margin-related: it's some other rule (pyramiding, +// position-size cap, broker-emulator gating, etc.) that the rest of +// the parity probes have not yet isolated. +// +// Same trigger / exit shape as parity-probe-03 so the only difference +// under test is the qty's relationship to available equity. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of Trades" to +// ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 05 - small equity fraction", shorttitle="par_p05", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=0, process_orders_on_close=false) + +// ---- engine ------------------------------------------------------- +// Wall-clock entry trigger: 00:00 UTC every Monday. +bool fire = dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size == 0 + +// qty encodes 10% of strategy.equity / close — well under the 1× +// margin boundary at all times. Round to 3 decimals so the exported +// CSV is bit-stable across re-runs. +float qty_dyn = math.round(strategy.equity / close * 100) / 1000 + +if fire and qty_dyn > 0 + strategy.entry("E", strategy.long, qty=qty_dyn, comment="qty = 10% equity / close") + +// Exit at next bar's open so each entry maps to a single trade row +// whose Size (qty) reveals 10 × strategy.equity at the entry bar +// (because the divisor is 100, but the rounding is /1000). +if strategy.position_size > 0 and bar_index > strategy.opentrades.entry_bar_index(0) + strategy.close("E", comment="next-bar flatten") diff --git a/tests/gate-corpus/ok/validation__analyzer-parity-stop-limit-timing-01.pine b/tests/gate-corpus/ok/validation__analyzer-parity-stop-limit-timing-01.pine new file mode 100644 index 0000000..7927fa4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-parity-stop-limit-timing-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 01 — strategy.exit stop/limit fill-bar timing +// +// Purpose: isolate the bar-boundary semantics of strategy.exit's stop= +// and limit= orders. A community-style reverse-on-signal scalper shows a +// consistent pattern where the engine fires Exit-{long,short} ONE 15m bar +// earlier than TV across ~9 of its 14 mismatched exits (e.g. engine 18:00 +// vs TV 18:15 on 2025-05-03). The hypothesis is that the engine evaluates +// the stop / limit on the SAME bar as the entry (intra-bar trigger using +// that bar's high/low), while TV defers evaluation to the NEXT bar's open +// onward. Without an isolator, the signal-side cascade in that strategy +// makes it impossible to attribute the drift. +// +// This probe routes a deterministic entry every i_period bars with an +// ATR-derived stop and limit, then lets strategy.exit handle both. No +// MTF, no signals, no regime filter — every exit row in the export is +// either a stop fill, a limit fill, or a forced flatten. Comparing the +// engine's exit timestamps against TV's row-by-row exposes the timing +// semantic directly, with no other moving parts to confound it. +// +// Trade shape: long-only, single contract, pyramiding=0, +// process_orders_on_close=false (TV default), no commission/slippage. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the strategy tester's +// "List of Trades" CSV to ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 01 - stop/limit timing", shorttitle="par_p01", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=0, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_atr_len = input.int(14, "ATR Length") +float i_stop_x = input.float(1.0, "Stop = N x ATR", minval=0.1) +float i_tp_x = input.float(2.0, "TP = N x ATR", minval=0.1) + +// ---- engine ------------------------------------------------------- +float atr_val = ta.atr(i_atr_len) + +// Wall-clock entry trigger: 00:00 and 12:00 UTC every day. We use +// the bar's UTC time (not bar_index) so engine and TV fire on the +// SAME absolute bars regardless of how many warmup bars precede +// each side's chart-bar-0. bar_index-based triggers would shift the +// firing bar by the warmup-prefix length and produce zero matches +// in `verify_corpus.py`'s align-by-time stage. +bool at_entry_window = (hour == 0 or hour == 12) and minute == 0 +bool fire = at_entry_window and strategy.position_size == 0 and not na(atr_val) + +if fire + float stop_lvl = close - atr_val * i_stop_x + float tp_lvl = close + atr_val * i_tp_x + strategy.entry("L", strategy.long, comment="periodic entry") + strategy.exit("X", from_entry="L", stop=stop_lvl, limit=tp_lvl, comment_loss="stop fill", comment_profit="tp fill") diff --git a/tests/gate-corpus/ok/validation__analyzer-self-test-multi-mode-01.pine b/tests/gate-corpus/ok/validation__analyzer-self-test-multi-mode-01.pine new file mode 100644 index 0000000..0c4af60 --- /dev/null +++ b/tests/gate-corpus/ok/validation__analyzer-self-test-multi-mode-01.pine @@ -0,0 +1,80 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe pineforge-analyzer — multi-mode parity exporter +// Purpose: parity test for a single strategy that switches among four entry +// shapes (clock_pulse / htf_60_roll / htf_d_high1 / bar_stair) via an +// input.string mode selector, used to spot-check engine timing per-shape. +// +// PineForge analyzer — run on the SAME symbol + timeframe + date range as repo OHLCV. +// Export List of trades → compare with scripts/diff_tv_vs_engine.py or validate_detailed_report.py +// +// Multiple mode exports: name files trades-.csv and set inputs.json "Analysis mode" + +// "tv_trades_csv" to match, OR run: uv run python scripts/diff_pineforge_analyzer_modes.py +// +// Modes (input): +// clock_pulse — periodic 1-bar long trades (bar index + timestamp ruler vs engine) +// htf_60_roll — ta.change(D-60 close) entries (HTF close alignment) +// htf_d_high1 — daily high[1] break (request.security + series[1] gate; same class as mtf-probe-04) +// bar_stair — close > close[1] > close[2] (Series [k] chain smoke test) +// +// Chart: match PineForge input_tf (e.g. 15m for ETHUSDT 15m CSV). Commission/slippage 0. +// TV “List of trades” naive times: if you export with chart/sessions in UTC+8, set inputs.json +// tv_trades_csv_tz to "utc_plus_8" (repo default for parity tooling). +// +// clock_pulse: UTC 00:15 entry / 00:30 exit (15m) — aligns with TV labels 08:15/08:30 when +// tv_trades_csv_tz is utc_plus_8. PineForge engine still defers bare MARKET fills one bar when +// process_orders_on_close=false, so trade timestamps/prices vs TV CSV need same-bar fill fix in +// runtime for full pairwise parity. +//@version=6 +strategy("PF Analyzer (PineForge)", shorttitle="PF_analyze", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +mode = input.string("clock_pulse", "Analysis mode", + options = ["clock_pulse", "htf_60_roll", "htf_d_high1", "bar_stair"]) +// Legacy input kept for saved layouts; clock_pulse ignores it (was bar_index % stepBars). +stepBars = input.int(96, "clock_pulse: unused (legacy bar step)", minval = 2) + +// --- clock_pulse: long 00:15 UTC bar, exit 00:30 UTC (15m chart); skip first bar --- +inPulse = bar_index > 0 and hour == 0 and minute == 15 +exitPulse = bar_index > 0 and hour == 0 and minute == 30 + +// --- htf_60_roll --- +h60 = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) +roll60 = ta.change(h60) != 0 + +// --- htf_d_high1 (same logic family as validation/mtf-probe-04-daily-prev-high) --- +dH1 = request.security(syminfo.tickerid, "D", high[1], lookahead=barmerge.lookahead_off) +crossUp = close > dH1 and close[1] <= dH1[1] +exitD = close < dH1 * 0.995 and strategy.position_size > 0 + +// --- bar_stair --- +stair = close > close[1] and close[1] > close[2] + +if mode == "clock_pulse" + if exitPulse + strategy.close("C") + if inPulse + strategy.entry("C", strategy.long, qty=1) + +if mode == "htf_60_roll" + if roll60 and strategy.position_size == 0 + strategy.entry("R", strategy.long, qty=1) + if roll60[1] and strategy.position_size != 0 + strategy.close("R") + +if mode == "htf_d_high1" + if crossUp and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1) + if exitD + strategy.close("L") + +if mode == "bar_stair" + if stair and strategy.position_size == 0 + strategy.entry("S", strategy.long, qty=1) + if not stair and strategy.position_size > 0 + strategy.close("S") diff --git a/tests/gate-corpus/ok/validation__anomaly-equity-mirror-strategy-equity-01.pine b/tests/gate-corpus/ok/validation__anomaly-equity-mirror-strategy-equity-01.pine new file mode 100644 index 0000000..013cb9d --- /dev/null +++ b/tests/gate-corpus/ok/validation__anomaly-equity-mirror-strategy-equity-01.pine @@ -0,0 +1,56 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe 03 — strategy.equity per-bar mirror +// +// Purpose: isolate the IES integration probe family's PnL drift (2.54% on +// the strict thresholds, sole strategy below the strong tier). The drift +// occurs because every entry sizes via ``strategy.equity * i_risk_pct * +// regime_size_mult * quality_mult / risk_per_unit``, and tiny IIR-warmup +// deltas in ATR/ADX (engine has 5 months of warmup OHLCV vs TV's full +// chart history) compound across thousands of trades into a $29k equity +// gap by 2026-04-21 — at which point per-bar regime and risk match TV +// bit-for-bit but the qty is locked at engine_equity / TV_equity ≈ +// 0.9731. The disagreement therefore lives in strategy.equity itself. +// +// This probe sets ``qty = strategy.equity / close`` at every periodic +// entry and immediately flattens on the next bar, so the exported +// ``Size (qty)`` column reads back ``strategy.equity / close`` at each +// trade's bar. Comparing engine vs TV row-by-row pinpoints the FIRST +// trade where strategy.equity diverges (i.e., where the cascade +// starts) and quantifies the divergence rate at every point afterward. +// +// Trade shape: long-only, pyramiding=0, no commission/slippage so the +// only mover of strategy.equity between trades is the prior trade's +// realised P&L plus the open position's mark-to-market. The 100-bar +// period spaces trades far enough apart that the prior trade always +// closes before the next entry computes its qty. +// +// TV setup: 15m chart on BINANCE:ETHUSDT.P, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of Trades" to +// ``tv_trades.csv`` in this folder. +//@version=6 +strategy("Parity probe 03 - equity mirror", shorttitle="par_p03", overlay=true, initial_capital=1000000, currency=currency.USD, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=0, process_orders_on_close=false) + +// ---- engine ------------------------------------------------------- +// Wall-clock entry trigger: 00:00 UTC every Monday. Using a +// timestamp-based rule (not bar_index) so engine and TV fire on the +// SAME absolute bars; bar_index-based triggers shift by the warmup +// prefix and break verify_corpus.py's align-by-time stage. Pine's +// `dayofweek.monday` constant is integer 2 (Sun=1, Mon=2, ..., Sat=7); +// the engine's codegen does not yet expose `dayofweek.*` namespace +// constants, so the literal is inlined here. +bool fire = dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size == 0 + +// qty encodes strategy.equity at this bar; round to 3 decimals so +// the exported CSV is bit-stable across re-runs. +float qty_dyn = math.round(strategy.equity / close * 1000) / 1000 + +if fire and qty_dyn > 0 + strategy.entry("E", strategy.long, qty=qty_dyn, comment="qty = equity/close") + +// Exit at next bar's open so each entry maps to a single trade row +// whose Size (qty) reveals strategy.equity at the entry bar. +if strategy.position_size > 0 and bar_index > strategy.opentrades.entry_bar_index(0) + strategy.close("E", comment="next-bar flatten") diff --git a/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-off-01b.pine b/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-off-01b.pine new file mode 100644 index 0000000..c1aaf23 --- /dev/null +++ b/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-off-01b.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Barstate-magnifier probe 01b — barstate.isconfirmed, magnifier OFF +// +// Purpose: baseline counterpart to 01a. Same Pine logic, magnifier +// OFF. The fill bar list must match 01a exactly; any divergence +// implicates magnifier sub-bar handling of barstate.isconfirmed. +// +// Trade shape: long-only. EMA(9/21) crossover signal latches when +// detected; entry executes only on the bar whose close is confirmed +// (barstate.isconfirmed). Exit on opposite cross also confirmed. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier OFF. Export trades to +// trades.csv. Compare against 01a/trades.csv for bar-identical fills. +//@version=6 +strategy("PF barstate-magnifier probe 01b - isconfirmed OFF", shorttitle="BSM_01b_OFF", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=false, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +var bool pendingLong = false +var bool pendingExit = false + +if ta.crossover(emaFast, emaSlow) + pendingLong := true +if ta.crossunder(emaFast, emaSlow) + pendingExit := true + +if barstate.isconfirmed and pendingLong and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="confirmed long") + pendingLong := false + +if barstate.isconfirmed and pendingExit and strategy.position_size > 0 + strategy.close("L", comment="confirmed exit") + pendingExit := false diff --git a/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-on-01a.pine b/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-on-01a.pine new file mode 100644 index 0000000..6829268 --- /dev/null +++ b/tests/gate-corpus/ok/validation__barstate-isconfirmed-magnifier-on-01a.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Barstate-magnifier probe 01a — barstate.isconfirmed, magnifier ON +// +// Purpose: barstate.isconfirmed must fire exactly once per chart bar +// regardless of magnifier sub-bar ticks. With magnifier ON the +// runtime risk is is_first_tick_/is_last_tick_ getting flipped on the +// last sample of the last sub-bar and the gated branch firing more +// than once or on the wrong bar (Pine #4). Pair with 01b (magnifier +// OFF) — the fill bar must be byte-identical between the two runs. +// +// Trade shape: long-only. EMA(9/21) crossover signal latches when +// detected; entry executes only on the bar whose close is confirmed +// (barstate.isconfirmed). Exit on opposite cross also confirmed. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier ON. Export trades to +// trades.csv. Compare against 01b/trades.csv for bar-identical fills. +//@version=6 +strategy("PF barstate-magnifier probe 01a - isconfirmed ON", shorttitle="BSM_01a_ON", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=true, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +var bool pendingLong = false +var bool pendingExit = false + +if ta.crossover(emaFast, emaSlow) + pendingLong := true +if ta.crossunder(emaFast, emaSlow) + pendingExit := true + +if barstate.isconfirmed and pendingLong and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="confirmed long") + pendingLong := false + +if barstate.isconfirmed and pendingExit and strategy.position_size > 0 + strategy.close("L", comment="confirmed exit") + pendingExit := false diff --git a/tests/gate-corpus/ok/validation__bracket-atr-trail-series-int-points-01.pine b/tests/gate-corpus/ok/validation__bracket-atr-trail-series-int-points-01.pine new file mode 100644 index 0000000..0507cf1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-atr-trail-series-int-points-01.pine @@ -0,0 +1,57 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 91 - atr-based trail_points with fixed-time entries +// +// Purpose: isolate whether the residual ~80 drifts from a community-style +// scalping strategy (after the trail_points-ceil fix in commit 9e838c0) +// come from the atr-based variable trail_points, or from that strategy's +// specific momentum-based entry pattern. +// +// Probes 88/89/90 all passed 100% with CONSTANT trail_points (8 or 20). +// This probe keeps everything else the same as probe 90 but switches +// trail_points to `atr` (series, varies per entry) — exactly like the +// scalping shape. Stop and limit also become atr-based (atrMult=1.2 short +// stop, *2 limit) to fully mirror the scalping shape. +// +// If probe 91 matches TV: the atr-based trail is fine, and trade-23-style +// drifts are tied to the scalping shape's entry timing +// (post-strong-bull-candle). +// If probe 91 diverges: there's a bug in how engine handles a series-int +// trail_points compared to a constant — most likely the timing of the +// atr capture inside strategy.exit. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 91 - atr trail fixed entry", + shorttitle="PF_P91_ATRTRAIL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +atrLen = 14 +atrMult = 1.2 +atr = ta.atr(atrLen) + +if hour == 8 and minute == 0 and strategy.position_size == 0 + longStop = close - atr * atrMult + longLimit = close + (close - longStop) * 2 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=longStop, + limit=longLimit, + trail_points=atr, + comment="atr trail long") + +if hour == 20 and minute == 0 and strategy.position_size == 0 + shortStop = close + atr * atrMult + shortLimit = close - (shortStop - close) * 2 + strategy.entry("S", strategy.short, qty=1, comment="entry short") + strategy.exit("SX", "S", + stop=shortStop, + limit=shortLimit, + trail_points=atr, + comment="atr trail short") diff --git a/tests/gate-corpus/ok/validation__bracket-atr-trailing-stop-state-01.pine b/tests/gate-corpus/ok/validation__bracket-atr-trailing-stop-state-01.pine new file mode 100644 index 0000000..345b717 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-atr-trailing-stop-state-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 13 — ATR-trailing-stop with var-state direction +// Purpose: parity test for ta.atr() driven trailing stop and `var float trailStop` +// / `var bool isLong` state-machine progression on EMA crossovers. +// +//@version=6 +strategy("ATR Trailing Stop", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +atrLen = input.int(14, "ATR Length", minval=1) +atrMult = input.float(2.0, "ATR Multiplier", step=0.1) +maLen = input.int(20, "MA Length", minval=5) + +// === Indicators === +atrVal = ta.atr(atrLen) +maVal = ta.ema(close, maLen) + +// === Trailing stop logic with var === +var float trailStop = na +var bool isLong = false + +// === Entry signals === +longEntry = ta.crossover(close, maVal) +shortEntry = ta.crossunder(close, maVal) + +if longEntry + strategy.entry("Long", strategy.long) + isLong := true + trailStop := close - atrVal * atrMult + +if shortEntry + strategy.entry("Short", strategy.short) + isLong := false + trailStop := close + atrVal * atrMult + +// === Update trailing stop === +if isLong and strategy.position_size > 0 + newStop = close - atrVal * atrMult + if not na(trailStop) + trailStop := math.max(trailStop, newStop) + else + trailStop := newStop + if close < trailStop + strategy.close("Long") + isLong := false + +if not isLong and strategy.position_size < 0 + newStop = close + atrVal * atrMult + if not na(trailStop) + trailStop := math.min(trailStop, newStop) + else + trailStop := newStop + if close > trailStop + strategy.close("Short") + +plot(trailStop, "Trail Stop", color=isLong ? color.green : color.red, style=plot.style_stepline) +plot(maVal, "MA", color=color.blue) diff --git a/tests/gate-corpus/ok/validation__bracket-entry-exit-same-pass-attach-01.pine b/tests/gate-corpus/ok/validation__bracket-entry-exit-same-pass-attach-01.pine new file mode 100644 index 0000000..a024c54 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-entry-exit-same-pass-attach-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 71 - entry and exit same pass +// +// Purpose: determine whether a price-based strategy.exit can attach to a +// strategy.entry created earlier in the same Pine execution pass. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 71 - entry exit same pass", shorttitle="PF_P71_ENTEXIT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 8 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry with bracket") + strategy.exit("X", "L", limit=close * 1.005, stop=close * 0.995, comment="same-pass bracket") + +if hour == 16 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="short entry with bracket") + strategy.exit("XS", "S", limit=close * 0.995, stop=close * 1.005, comment="same-pass short bracket") + +if strategy.position_size != 0 and hour == 23 and minute == 45 + strategy.close_all(comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-exit-stop-limit-trail-same-bar-01.pine b/tests/gate-corpus/ok/validation__bracket-exit-stop-limit-trail-same-bar-01.pine new file mode 100644 index 0000000..df43632 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-exit-stop-limit-trail-same-bar-01.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 50 - stop/limit/trail same-bar exit +// +// Purpose: isolate one strategy.exit carrying stop, limit, and trail_points at +// the same time. This is the exit surface used by the scalping integration +// probe family. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 50 - stop limit trail", shorttitle="PF_P50_EXIT3", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +atr = ta.atr(14) + +enterLong = hour == 8 and minute == 15 and strategy.position_size == 0 +enterShort = hour == 16 and minute == 15 and strategy.position_size == 0 + +if enterLong + strategy.entry("L", strategy.long, qty=1, comment="probe long") + +if enterShort + strategy.entry("S", strategy.short, qty=1, comment="probe short") + +if strategy.position_size > 0 + longStop = strategy.position_avg_price - atr * 0.8 + longLimit = strategy.position_avg_price + atr * 1.6 + strategy.exit("LX", "L", stop=longStop, limit=longLimit, trail_points=atr, comment="triple exit long") + +if strategy.position_size < 0 + shortStop = strategy.position_avg_price + atr * 0.8 + shortLimit = strategy.position_avg_price - atr * 1.6 + strategy.exit("SX", "S", stop=shortStop, limit=shortLimit, trail_points=atr, comment="triple exit short") + +if strategy.position_size != 0 and hour == 23 and minute == 45 + strategy.close_all(comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-exit-three-way-set-once-entry-01.pine b/tests/gate-corpus/ok/validation__bracket-exit-three-way-set-once-entry-01.pine new file mode 100644 index 0000000..9288216 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-exit-three-way-set-once-entry-01.pine @@ -0,0 +1,49 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 88 - three-way exit set once at entry (scalping-like) +// +// Purpose: isolate the exit-side divergence in a community-style scalping +// strategy. Probe 50 already covers same-bar stop+limit+trail_points fills, +// but it RE-ISSUES strategy.exit on every bar while in position +// (`if strategy.position_size > 0`). The scalping shape instead calls +// strategy.exit ONCE next to strategy.entry — the exit's stop, limit, and +// trail_points values are captured from the entry-bar atr/close and never +// updated. This probe mirrors that shape exactly so any divergence between +// TV and the engine maps cleanly to the "exit set once + carried across +// multiple bars" path rather than the "exit reset each bar" path that +// probe 50 covers. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 88 - 3-way exit set once", shorttitle="PF_P88_EX3SET", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// Tick-sized trail (~$0.20 for ETH at mintick 0.01) matches scalping's +// trail_points=atr semantics — atr is implicitly cast to int and ends up +// as a small tick count rather than the human-readable atr in price units. +trailTicks = 20 + +// Long entry once a day, with the 3-way exit captured at entry close. +if hour == 8 and minute == 0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=close * 0.99, + limit=close * 1.02, + trail_points=trailTicks, + comment="3-way set once") + +// Short entry once a day, far enough from the long window to avoid bracket +// interference. Same 3-way exit captured at entry close. +if hour == 20 and minute == 0 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="entry short") + strategy.exit("SX", "S", + stop=close * 1.01, + limit=close * 0.98, + trail_points=trailTicks, + comment="3-way set once") diff --git a/tests/gate-corpus/ok/validation__bracket-exit-tp-sl-fixed-01.pine b/tests/gate-corpus/ok/validation__bracket-exit-tp-sl-fixed-01.pine new file mode 100644 index 0000000..1f93573 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-exit-tp-sl-fixed-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 40 - bracket exit TP/SL +// +// Purpose: isolate strategy.exit(limit, stop) on a simple periodic long entry. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 40 - bracket TP/SL", shorttitle="PF_G40_BRACKET", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +enterLong = hour == 0 and minute == 15 and strategy.position_size == 0 + +if enterLong + strategy.entry("L", strategy.long, qty=1, comment="periodic long") + +if strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.exit("LX", "L", limit=entry * 1.004, stop=entry * 0.996, comment="bracket") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("L", comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-narrow-stop-limit-with-trail8-01.pine b/tests/gate-corpus/ok/validation__bracket-narrow-stop-limit-with-trail8-01.pine new file mode 100644 index 0000000..d9306a2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-narrow-stop-limit-with-trail8-01.pine @@ -0,0 +1,49 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 90 - narrow stop+limit alongside trail (scalping-distances) +// +// Purpose: investigate the 80 trades from a community-style scalping +// strategy that still drift by $0.50+ even after the trail_points-ceil fix +// (e.g. trade 23 at 2025-04-26 00:15 UTC, where engine fires trail at +// activation 1791.12 and TV fires at 1792.00 — Δ=$0.88). +// +// Probe 88 (trailTicks=20, far stop/limit at ±1-2%) and probe 89 +// (trailTicks=8, far stop/limit at ±5%) both match TV 100%. That scalping +// shape's stop and limit are MUCH narrower — atr-based, so roughly +// entry±$5..$15. This probe pins trailTicks=8 (matching probe 89) but +// moves the stop and limit close to entry like that scalping shape +// (±$10/$20) to see whether the path-priority resolution on the entry bar +// changes when stop/limit are reachable on the same bar's path even though +// only the trail's activation level is actually crossed. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 90 - narrow stop/limit + trail8", + shorttitle="PF_P90_NARROW", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +trailTicks = 8 +stopDist = 10.0 +limitDist = 20.0 + +if hour == 8 and minute == 0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=close - stopDist, + limit=close + limitDist, + trail_points=trailTicks, + comment="narrow stop/limit + trail8") + +if hour == 20 and minute == 0 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="entry short") + strategy.exit("SX", "S", + stop=close + stopDist, + limit=close - limitDist, + trail_points=trailTicks, + comment="narrow stop/limit + trail8") diff --git a/tests/gate-corpus/ok/validation__bracket-partial-exit-qty-percent-01.pine b/tests/gate-corpus/ok/validation__bracket-partial-exit-qty-percent-01.pine new file mode 100644 index 0000000..82fd2be --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-partial-exit-qty-percent-01.pine @@ -0,0 +1,28 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 41 - partial exit with qty_percent +// +// Purpose: isolate strategy.exit(..., qty_percent=50) and remaining-position handling. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 41 - partial qty percent", shorttitle="PF_G41_PARTIAL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=2, pyramiding=1, + process_orders_on_close=false) + +enterLong = hour == 1 and minute == 15 and strategy.position_size == 0 + +if enterLong + strategy.entry("L", strategy.long, qty=2, comment="two lots") + +if strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.exit("HALF_TP", "L", limit=entry * 1.003, qty_percent=50, comment="half tp") + strategy.exit("REST_SL", "L", stop=entry * 0.994, comment="rest stop") + +if strategy.position_size > 0 and hour == 9 and minute == 15 + strategy.close("L", comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-same-id-exit-replace-01.pine b/tests/gate-corpus/ok/validation__bracket-same-id-exit-replace-01.pine new file mode 100644 index 0000000..1ad0344 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-same-id-exit-replace-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 67 - same-id exit replacement +// +// Purpose: determine whether repeated strategy.exit calls with the same id +// replace the pending bracket or create multiple exits. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 67 - same id exit replace", shorttitle="PF_P67_EXITREPL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 8 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="long for exit replace") + +if strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.exit("X", "L", limit=entry * 1.01, stop=entry * 0.99, comment="first exit") + strategy.exit("X", "L", limit=entry * 1.003, stop=entry * 0.997, comment="replacement exit") + +if strategy.position_size > 0 and hour == 16 and minute == 15 + strategy.close("L", comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-tp-sl-oca-reduce-isolate-01.pine b/tests/gate-corpus/ok/validation__bracket-tp-sl-oca-reduce-isolate-01.pine new file mode 100644 index 0000000..3f497fa --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-tp-sl-oca-reduce-isolate-01.pine @@ -0,0 +1,64 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 97a — TP/SL OCA-reduce bracket (isolated from probe 97) +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolate the OCA-reduce bracket surface from probe 97. Drops +// the gap/range-expansion gate, drops the pending stop entry, drops the +// max_intraday_filled_orders cap. Uses a plain dual-MA crossover to +// trigger entries so the bracket is the only non-trivial surface under +// test. If TV vs engine diverge here, the OCA-reduce bracket math +// (TP+SL share an OCA group; when one fills, the other auto-reduces by +// filled qty) is the bug locus. +// +// Validation goal: bit-exact trade list. Pass = identical exits because +// OCA-reduce arithmetic at the mintick boundary matches TV. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 97a - tp/sl bracket isolate", shorttitle="PF_p97a_BRK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, calc_on_order_fills=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(9, "Fast MA", minval=2) +int i_slow = input.int(21, "Slow MA", minval=3) +int i_tp_ticks = input.int(10, "Take profit (ticks)", minval=1) +int i_sl_ticks = input.int(10, "Stop loss (ticks)", minval=1) + +// ---- entry signal: dual MA cross ---------------------------------- +float fast = ta.sma(close, i_fast) +float slow = ta.sma(close, i_slow) +bool go_long = ta.crossover(fast, slow) +bool go_short = ta.crossunder(fast, slow) + +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, comment="ma cross up") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, comment="ma cross dn") + +// ---- bracket: TP + SL sharing an OCA reduce group ------------------ +float pos_qty = math.abs(strategy.position_size) +int pos_dir = strategy.position_size > 0 ? 1 : (strategy.position_size < 0 ? -1 : 0) +float entry_px = strategy.position_avg_price +float tp_px = entry_px + pos_dir * i_tp_ticks * syminfo.mintick +float sl_px = entry_px - pos_dir * i_sl_ticks * syminfo.mintick + +bool in_position = pos_qty > 0 +exit_dir = pos_dir > 0 ? strategy.short : strategy.long + +if in_position + strategy.order("BracketTP", exit_dir, qty=pos_qty, limit=tp_px, + oca_name="bracket97a", oca_type=strategy.oca.reduce, comment="TP") + strategy.order("BracketSL", exit_dir, qty=pos_qty, stop=sl_px, + oca_name="bracket97a", oca_type=strategy.oca.reduce, comment="SL") +else + strategy.cancel("BracketTP") + strategy.cancel("BracketSL") diff --git a/tests/gate-corpus/ok/validation__bracket-trail-points-no-offset-explicit-01.pine b/tests/gate-corpus/ok/validation__bracket-trail-points-no-offset-explicit-01.pine new file mode 100644 index 0000000..2f0df58 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-trail-points-no-offset-explicit-01.pine @@ -0,0 +1,50 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 89 - trail_points without trail_offset, alongside stop+limit +// +// Purpose: figure out what TradingView's broker actually does with +// `trail_points` when `trail_offset` is missing. TV's parser rejects +// strategy.exit if it has ONLY `trail_points`, so the legal way to use +// `trail_points` without an explicit `trail_offset` is alongside `stop` +// and/or `limit` — exactly the exit shape used by a community-style +// scalping strategy. This probe mirrors that shape with controlled, +// fixed-percent stop and limit far from entry so the trail (or TV's +// interpretation of it) is the only thing that can fire on the entry +// bar's intra-bar path. +// +// trail_points=8 matches the int(atr)≈8 captured at the scalping shape's +// failing entry bar (trade 1); engine fires at entry+$0.08 (activation +// level) and TV fires at entry+$0.97. probe 88 already proved engine + TV +// agree 100% with trailTicks=20 in the same shape, so this probe +// specifically dials trailTicks down to 8 to see whether the gap is a +// small-trail edge case or something deeper about activation handling. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 89 - trail_points 8 + far stop/limit", + shorttitle="PF_P89_TRAIL8", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +trailTicks = 8 + +if hour == 8 and minute == 0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=close * 0.95, + limit=close * 1.05, + trail_points=trailTicks, + comment="trail8 + far stop/limit") + +if hour == 20 and minute == 0 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="entry short") + strategy.exit("SX", "S", + stop=close * 1.05, + limit=close * 0.95, + trail_points=trailTicks, + comment="trail8 + far stop/limit") diff --git a/tests/gate-corpus/ok/validation__bracket-trail-points-with-offset-only-01.pine b/tests/gate-corpus/ok/validation__bracket-trail-points-with-offset-only-01.pine new file mode 100644 index 0000000..4249cc4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-trail-points-with-offset-only-01.pine @@ -0,0 +1,34 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 51 - trail_points with trail_offset +// +// Purpose: isolate TradingView semantics for the legal trailing-stop pair +// trail_points + trail_offset without stop/limit siblings. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 51 - trail points offset", shorttitle="PF_P51_TRAIL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +trailPts = input.float(80.0, "Trail Points", minval=1.0) +trailOff = input.float(40.0, "Trail Offset", minval=1.0) + +if hour == 9 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="trail long") + +if hour == 15 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="trail short") + +if strategy.position_size > 0 + strategy.exit("LX", "L", trail_points=trailPts, trail_offset=trailOff, comment="trail long") + +if strategy.position_size < 0 + strategy.exit("SX", "S", trail_points=trailPts, trail_offset=trailOff, comment="trail short") + +if strategy.position_size != 0 and hour == 23 and minute == 45 + strategy.close_all(comment="timeout") diff --git a/tests/gate-corpus/ok/validation__bracket-trailing-activation-offset-path-01.pine b/tests/gate-corpus/ok/validation__bracket-trailing-activation-offset-path-01.pine new file mode 100644 index 0000000..e4e433b --- /dev/null +++ b/tests/gate-corpus/ok/validation__bracket-trailing-activation-offset-path-01.pine @@ -0,0 +1,35 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 56 - trailing activation and offset path +// +// Purpose: isolate legal trailing-stop behavior with trail_points + trail_offset +// and no stop/limit siblings. Uses larger tick distances than probe 51 to avoid +// tiny activation noise and focus on the activation/offset path. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 56 - trailing activation path", shorttitle="PF_P56_TRAILPATH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +trailPts = input.float(800.0, "Trail Points", minval=1.0) +trailOff = input.float(400.0, "Trail Offset", minval=1.0) + +if hour == 9 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="trail path long") + +if hour == 15 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="trail path short") + +if strategy.position_size > 0 + strategy.exit("LX", "L", trail_points=trailPts, trail_offset=trailOff, comment="trail path long") + +if strategy.position_size < 0 + strategy.exit("SX", "S", trail_points=trailPts, trail_offset=trailOff, comment="trail path short") + +if strategy.position_size != 0 and hour == 23 and minute == 45 + strategy.close_all(comment="timeout") diff --git a/tests/gate-corpus/ok/validation__cap-max-intraday-filled-orders-isolate-01.pine b/tests/gate-corpus/ok/validation__cap-max-intraday-filled-orders-isolate-01.pine new file mode 100644 index 0000000..9b8ab1c --- /dev/null +++ b/tests/gate-corpus/ok/validation__cap-max-intraday-filled-orders-isolate-01.pine @@ -0,0 +1,45 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 97b — max_intraday_filled_orders cap (isolated from probe 97) +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolate `strategy.risk.max_intraday_filled_orders(N)` from +// probe 97. Drops bracket, drops gap/range gate, drops pending stop +// entries. Uses a plain dual-MA crossover with market entries; the cap +// is the only non-trivial surface under test. If TV vs engine diverge +// here, the cap counter (when it increments, when it resets, what it +// counts as a "filled order") is the bug locus. +// +// Validation goal: bit-exact trade list. Pass = identical entries +// because the cap counter increments + day-rollover boundary match TV. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 97b - intraday cap isolate", shorttitle="PF_p97b_CAP", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, calc_on_order_fills=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast MA", minval=2) +int i_slow = input.int(13, "Slow MA", minval=3) +int i_max_fills = input.int(5, "Max intraday filled orders", minval=1) + +strategy.risk.max_intraday_filled_orders(i_max_fills) + +// ---- entry signal: dual MA cross (no bracket, no gap, no cap-aware logic) +float fast = ta.sma(close, i_fast) +float slow = ta.sma(close, i_slow) +bool go_long = ta.crossover(fast, slow) +bool go_short = ta.crossunder(fast, slow) + +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, comment="ma cross up") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, comment="ma cross dn") diff --git a/tests/gate-corpus/ok/validation__cap-risk-gates-allow-max-intraday-01.pine b/tests/gate-corpus/ok/validation__cap-risk-gates-allow-max-intraday-01.pine new file mode 100644 index 0000000..107bdd6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__cap-risk-gates-allow-max-intraday-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 46 - strategy.risk gates +// +// Purpose: isolate allow_entry_in, max_position_size, and max_intraday_filled_orders. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 46 - risk gates", shorttitle="PF_G46_RISK", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=10, + process_orders_on_close=false) + +strategy.risk.allow_entry_in(strategy.direction.long) +strategy.risk.max_position_size(2) +strategy.risk.max_intraday_filled_orders(3) + +// Use fixed UTC-style clock pulses on the crypto 24x7 feed. The short attempt +// should be blocked by allow_entry_in; the third long should be constrained by risk. +if hour == 0 and minute == 15 + strategy.entry("L1", strategy.long, qty=1, comment="first long") + +if hour == 0 and minute == 30 + strategy.entry("L2", strategy.long, qty=1, comment="second long") + +if hour == 0 and minute == 45 + strategy.entry("L3", strategy.long, qty=1, comment="third long") + +if hour == 1 and minute == 0 + strategy.entry("S_BLOCKED", strategy.short, qty=1, comment="blocked short") + +if hour == 3 and minute == 0 and strategy.position_size != 0 + strategy.close_all(comment="daily reset") diff --git a/tests/gate-corpus/ok/validation__composite-4emarsi-integration-01.pine b/tests/gate-corpus/ok/validation__composite-4emarsi-integration-01.pine new file mode 100644 index 0000000..b8cd517 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-4emarsi-integration-01.pine @@ -0,0 +1,99 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 4ema-rsi-probe-integration — quad-EMA + RSI pullback + bar window +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the cross-surface combination of the four-EMA stack +// (probe 01) and the RSI shallow-pullback latch (probe 02) plus the +// fixed-bar forced-exit counter from probe 03. The session-window gate +// from probe 03 is intentionally NOT composed in (binary session test +// stays standalone in probe 03). Original deep RSI bands (dip < 40, +// recover > 50) almost never co-occur with a same-bar bullish four-EMA +// stack on 15m crypto — by the time RSI dips that deep the trend has +// flipped — so this integration uses a shallow pullback band (dip +// below ~48, recover above ~52) that triggers far more frequently +// inside an existing stack regime. The engine surfaces under test are +// the stacking predicate, the var-bool RSI arm-and-fire latch, and +// the var-int bars-in-trade counter that closes the trade at exactly N. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. Long when +// stack is bullish AND RSI long-pullback fires; short symmetric; +// forced exit after N bars regardless of state. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 4ema-rsi-probe-integration", shorttitle="PF_4emaINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_xs = input.int(8, "EMA xs length", minval=2) +int i_s = input.int(21, "EMA s length", minval=3) +int i_m = input.int(55, "EMA m length", minval=5) +int i_l = input.int(200, "EMA l length", minval=10) +int i_rsi_len = input.int(14, "RSI length", minval=2) +float i_dip_lo = input.float(48, "Long pullback band", minval=10, maxval=50) +float i_pop_lo = input.float(52, "Long recovery line", minval=40, maxval=70) +float i_dip_hi = input.float(52, "Short pullback band", minval=50, maxval=90) +float i_pop_hi = input.float(48, "Short recovery line", minval=30, maxval=60) +int i_expiry_bars = input.int(8, "Forced exit after N bars", minval=1) + +// ---- four-EMA fan + stack-order predicates ------------------------- +float ema_xs = ta.ema(close, i_xs) +float ema_s = ta.ema(close, i_s) +float ema_m = ta.ema(close, i_m) +float ema_l = ta.ema(close, i_l) + +bool stack_bull = ema_xs > ema_s and ema_s > ema_m and ema_m > ema_l +bool stack_bear = ema_xs < ema_s and ema_s < ema_m and ema_m < ema_l + +// ---- RSI arm-and-fire latches ------------------------------------- +float r = ta.rsi(close, i_rsi_len) + +var bool long_armed = false +var bool short_armed = false + +if r < i_dip_lo + long_armed := true +if r > i_dip_hi + short_armed := true + +bool long_fire = long_armed and ta.crossover(r, i_pop_lo) +bool short_fire = short_armed and ta.crossunder(r, i_pop_hi) + +if long_fire + long_armed := false +if short_fire + short_armed := false + +// ---- composed entry signals --------------------------------------- +bool go_long = stack_bull and long_fire +bool go_short = stack_bear and short_fire + +// ---- bars-in-trade counter ---------------------------------------- +var int bars_in_trade = 0 +if strategy.position_size != 0 + bars_in_trade += 1 +else + bars_in_trade := 0 + +bool expiry_due = strategy.position_size != 0 and bars_in_trade >= i_expiry_bars + +// ---- order routing ------------------------------------------------ +// Allow direction flips (long when flat or short, short when flat or long) +// so the integration trade list stays dense enough for parity comparison. +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ short") + +if expiry_due + strategy.close_all(comment="integ bar expiry") diff --git a/tests/gate-corpus/ok/validation__composite-4emarsi-quad-ema-stack-01.pine b/tests/gate-corpus/ok/validation__composite-4emarsi-quad-ema-stack-01.pine new file mode 100644 index 0000000..d23849a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-4emarsi-quad-ema-stack-01.pine @@ -0,0 +1,59 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 4ema-rsi-probe-01-quad-ema-stack — four-EMA stacking order check +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a four-tier EMA fan and the strict-ordering predicate +// derived from it. The engine surface under test is the parallel evaluation +// of four `ta.ema` chains at different lengths and the boolean-chain +// composition (`a > b and b > c and c > d`) used to gate entries. Multi-EMA +// stacks are common in trend-following strategies; if the engine's EMA +// recurrence drifts even fractionally on the longest length, the stack +// predicate flips on a different bar and the entry list desyncs. +// +// Validation goal: bit-exact entries on the rising edge of a fully bullish +// stack (`ema_xs > ema_s > ema_m > ema_l`) and exits on the rising edge of +// a fully bearish stack. Pass = identical trade list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 4ema-rsi-probe-01-quad-ema-stack", shorttitle="PF_4ema01_STK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_xs = input.int(8, "EMA xs length", minval=2) +int i_s = input.int(21, "EMA s length", minval=3) +int i_m = input.int(55, "EMA m length", minval=5) +int i_l = input.int(200, "EMA l length", minval=10) + +// ---- four-EMA fan -------------------------------------------------- +float ema_xs = ta.ema(close, i_xs) +float ema_s = ta.ema(close, i_s) +float ema_m = ta.ema(close, i_m) +float ema_l = ta.ema(close, i_l) + +// ---- stack-order predicates ---------------------------------------- +bool stack_bull = ema_xs > ema_s + and ema_s > ema_m + and ema_m > ema_l +bool stack_bear = ema_xs < ema_s + and ema_s < ema_m + and ema_m < ema_l + +// ---- rising-edge gates --------------------------------------------- +bool bull_edge = stack_bull and not stack_bull[1] +bool bear_edge = stack_bear and not stack_bear[1] + +// ---- trivial entries on the stack-edge transitions ----------------- +if bull_edge and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="stack bull edge") + +if bear_edge and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="stack bear edge") diff --git a/tests/gate-corpus/ok/validation__composite-4emarsi-rsi-pullback-latch-01.pine b/tests/gate-corpus/ok/validation__composite-4emarsi-rsi-pullback-latch-01.pine new file mode 100644 index 0000000..80840e7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-4emarsi-rsi-pullback-latch-01.pine @@ -0,0 +1,61 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 4ema-rsi-probe-02-rsi-pullback — RSI dip-and-recover trigger +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a two-state RSI pullback gate. The engine surfaces under +// test are `ta.rsi` (Wilder smoothing via `ta.rma`) and a cross-bar `var +// bool` latch that arms on a deep pullback and fires when RSI recovers +// through the midline. The latch is the parity-relevant piece — it depends +// on bar-by-bar evaluation order and on `ta.rsi`'s recursive smoothing +// matching the TV reference exactly. +// +// Validation goal: bit-exact long-entry list when RSI dips below the lower +// band and then crosses back above the midline; symmetric for shorts. Pass +// = identical trade list vs TradingView's broker emulator. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 4ema-rsi-probe-02-rsi-pullback", shorttitle="PF_4ema02_RSI", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_rsi_len = input.int(14, "RSI length", minval=2) +float i_dip_lo = input.float(40, "Long pullback band", minval=10, maxval=50) +float i_pop_lo = input.float(50, "Long recovery line", minval=40, maxval=60) +float i_dip_hi = input.float(60, "Short pullback band", minval=50, maxval=90) +float i_pop_hi = input.float(50, "Short recovery line", minval=40, maxval=60) + +// ---- RSI base ------------------------------------------------------ +float r = ta.rsi(close, i_rsi_len) + +// ---- arm-and-fire latches (cross-bar) ------------------------------ +var bool long_armed = false +var bool short_armed = false + +if r < i_dip_lo + long_armed := true +if r > i_dip_hi + short_armed := true + +bool long_fire = long_armed and ta.crossover(r, i_pop_lo) +bool short_fire = short_armed and ta.crossunder(r, i_pop_hi) + +if long_fire + long_armed := false +if short_fire + short_armed := false + +// ---- trivial entries on fire events -------------------------------- +if long_fire and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="rsi pullback long") + +if short_fire and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="rsi pullback short") diff --git a/tests/gate-corpus/ok/validation__composite-4emarsi-session-window-nbar-exit-01.pine b/tests/gate-corpus/ok/validation__composite-4emarsi-session-window-nbar-exit-01.pine new file mode 100644 index 0000000..2acd45e --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-4emarsi-session-window-nbar-exit-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 4ema-rsi-probe-03-binary-bar-window — session window + N-bar expiry +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a binary-options-style fixed-bar exit combined with an +// intraday session-window gate. The engine surfaces under test are +// `time(timeframe.period, sessionSpec)` for the entry-gating window and a +// `var int` bars-in-trade counter that forces an exit after N bars. This +// validates that the engine's session-time evaluation and per-bar trade-age +// counter both align with TradingView's broker emulator at the boundary +// bars (open of session, last bar of trade). +// +// Validation goal: bit-exact entries that occur ONLY inside the session +// window, and bit-exact forced exits exactly N bars after entry regardless +// of any other condition. Pass = identical trade list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 4ema-rsi-probe-03-binary-bar-window", shorttitle="PF_4ema03_WIN", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_expiry_bars = input.int(8, "Forced exit after N bars", minval=1) +string i_session = input.session("1000-2200", "Entry session (UTC)") + +// ---- session gate (UTC reference; chart timezone overrides display) - +bool in_window = not na(time(timeframe.period, i_session, "UTC")) + +// ---- trivial momentum trigger inside the window -------------------- +float fast = ta.ema(close, 5) +float slow = ta.ema(close, 20) +bool long_trigger = ta.crossover(fast, slow) and in_window +bool short_trigger = ta.crossunder(fast, slow) and in_window + +// ---- bars-in-trade counter ---------------------------------------- +var int bars_in_trade = 0 +if strategy.position_size != 0 + bars_in_trade += 1 +else + bars_in_trade := 0 + +bool expiry_due = strategy.position_size != 0 and bars_in_trade >= i_expiry_bars + +// ---- order routing ------------------------------------------------- +if long_trigger and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="window long") + +if short_trigger and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="window short") + +if expiry_due + strategy.close_all(comment="bar-window expiry") diff --git a/tests/gate-corpus/ok/validation__composite-boscurv-integration-01.pine b/tests/gate-corpus/ok/validation__composite-boscurv-integration-01.pine new file mode 100644 index 0000000..40746ef --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-boscurv-integration-01.pine @@ -0,0 +1,80 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe bos-curv-probe-integration — BOS trigger gated by curved channel +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the two surfaces isolated by `bos-curv-probe-01..02` +// (pivot-based break-of-structure + curved linreg/ATR channel) into one +// strategy. Long entry requires both a BOS-up confirmation AND `close` +// above the curved upper band's lower envelope; short symmetric. The +// integrated probe verifies that the engine evaluates pivot-state and +// linreg/ATR math in agreement on the same bars TradingView does. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe bos-curv-probe-integration", shorttitle="PF_bosINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) +int i_lr_len = input.int(50, "Linreg length", minval=10) +int i_atr_len = input.int(14, "ATR length", minval=2) +float i_atr_mult = input.float(2.0, "ATR multiplier", minval=0.1, step=0.1) +float i_curve_offset = input.float(5.0, "Slope curve offset", step=0.5) + +// ---- pivot + last-confirmed state --------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +bool bos_long = not na(last_ph) and close > last_ph and close[1] <= nz(last_ph[1], last_ph) +bool bos_short = not na(last_pl) and close < last_pl and close[1] >= nz(last_pl[1], last_pl) + +// ---- curved channel ----------------------------------------------- +float mid = ta.linreg(close, i_lr_len, 0) +float lr_lag = ta.linreg(close, i_lr_len, 1) +float slope = mid - lr_lag +float width = ta.atr(i_atr_len) * i_atr_mult +float upper = mid + width + slope * i_curve_offset +float lower = mid - width + slope * i_curve_offset + +// ---- composite gates ---------------------------------------------- +bool channel_bull = close > mid + slope * i_curve_offset +bool channel_bear = close < mid + slope * i_curve_offset + +bool go_long = bos_long and channel_bull +bool go_short = bos_short and channel_bear + +// ---- exit when price re-enters opposite half of channel ----------- +bool exit_long = strategy.position_size > 0 and ta.crossunder(close, lower) +bool exit_short = strategy.position_size < 0 and ta.crossover(close, upper) + +// ---- order routing ------------------------------------------------ +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ short") + +if exit_long + strategy.close("L", comment="integ exit long") +if exit_short + strategy.close("S", comment="integ exit short") diff --git a/tests/gate-corpus/ok/validation__composite-boscurv-linreg-slope-channel-01.pine b/tests/gate-corpus/ok/validation__composite-boscurv-linreg-slope-channel-01.pine new file mode 100644 index 0000000..b0f07ca --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-boscurv-linreg-slope-channel-01.pine @@ -0,0 +1,64 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe bos-curv-probe-02-curved-channel — slope-warped linreg+ATR channel +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a non-linear channel computed from `ta.linreg` plus +// per-bar slope and `ta.atr` width. The engine surfaces under test are +// `ta.linreg` (least-squares regression over a rolling window evaluated at +// two offsets to derive a slope), `ta.atr` (RMA chain), and the curved +// upper/lower band: +// +// mid = ta.linreg(close, len, 0) +// slope = ta.linreg(close, len, 0) - ta.linreg(close, len, 1) +// width = ta.atr(atr_len) * mult +// upper = mid + width + slope * curve_offset +// lower = mid - width + slope * curve_offset +// +// The slope term curves the band asymmetrically vs a flat linreg envelope. +// Verifies that engine vs TV agree on the regression coefficients at every +// bar and on the slope-difference numerics. +// +// Validation goal: bit-exact entries when `close` crosses above the +// curved upper band (long) or below the curved lower band (short). +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe bos-curv-probe-02-curved-channel", shorttitle="PF_bos02_CRV", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_lr_len = input.int(50, "Linreg length", minval=10) +int i_atr_len = input.int(14, "ATR length", minval=2) +float i_atr_mult = input.float(2.0, "ATR multiplier", minval=0.1, step=0.1) +float i_curve_offset = input.float(5.0, "Slope curve offset", step=0.5) + +// ---- linreg + slope ------------------------------------------------ +float mid = ta.linreg(close, i_lr_len, 0) +float lr_lag = ta.linreg(close, i_lr_len, 1) +float slope = mid - lr_lag + +// ---- ATR width ----------------------------------------------------- +float width = ta.atr(i_atr_len) * i_atr_mult + +// ---- curved bands -------------------------------------------------- +float upper = mid + width + slope * i_curve_offset +float lower = mid - width + slope * i_curve_offset + +// ---- crosses ------------------------------------------------------- +bool cross_up = ta.crossover(close, upper) +bool cross_down = ta.crossunder(close, lower) + +// ---- order routing ------------------------------------------------- +if cross_up and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="curve cross up") + +if cross_down and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="curve cross down") diff --git a/tests/gate-corpus/ok/validation__composite-boscurv-pivot-bos-trigger-01.pine b/tests/gate-corpus/ok/validation__composite-boscurv-pivot-bos-trigger-01.pine new file mode 100644 index 0000000..da4f90b --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-boscurv-pivot-bos-trigger-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe bos-curv-probe-01-swing-bos-trigger — pivot-based break-of-structure +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a break-of-structure detector built on `ta.pivothigh` / +// `ta.pivotlow`. The engine surfaces under test are the pivot left/right +// confirmation logic and the cross-bar `var float` last-confirmed-pivot +// state used to detect a close-through-pivot break. BOS triggers are a +// staple of structural strategies; small differences in WHEN a pivot +// confirms (right-bar count) cascade into different break bars. +// +// Validation goal: bit-exact entries on the bar that closes above the most +// recently confirmed pivot high (long) or below the most recently confirmed +// pivot low (short). Pass = identical trade list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe bos-curv-probe-01-swing-bos-trigger", shorttitle="PF_bos01_BOS", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) + +// ---- pivot detection ---------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +// ---- last-confirmed pivot state ----------------------------------- +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- break detection (close through last pivot) ------------------- +bool bos_long = not na(last_ph) and close > last_ph and close[1] <= nz(last_ph[1], last_ph) +bool bos_short = not na(last_pl) and close < last_pl and close[1] >= nz(last_pl[1], last_pl) + +// ---- order routing ------------------------------------------------ +if bos_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="bos long") + +if bos_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="bos short") diff --git a/tests/gate-corpus/ok/validation__composite-bracket-cap-range-pending-stop-01.pine b/tests/gate-corpus/ok/validation__composite-bracket-cap-range-pending-stop-01.pine new file mode 100644 index 0000000..9153d13 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-bracket-cap-range-pending-stop-01.pine @@ -0,0 +1,98 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 97 — TP/SL bracket on gap-driven stop entries with OCA reduce +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises four engine surfaces in one cycle: +// 1. `strategy.entry(..., stop=)` placing a pending stop entry, then +// `strategy.cancel` clearing it on the next bar if the trigger +// condition no longer holds (tests pending-order lifecycle across bars). +// 2. `strategy.order` placing a TP + SL pair sharing an OCA group with +// `strategy.oca.reduce` — when one fills, the other should be reduced +// by the filled qty automatically. +// 3. Range-expansion detection: previous bar's range exceeds an +// ATR-multiple threshold. We use ATR-relative range expansion instead +// of a literal price gap (`open > high[1]`) because true gaps are +// vanishingly rare on 24/7 crypto data — the surface under test is +// pending stop placement gated on a per-bar predicate, not the +// semantics of the predicate itself. +// 4. `strategy.risk.max_intraday_filled_orders` caps activity per +// intraday session. +// +// Validation goal: bit-exact trade-list match against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. Verifies pending +// stop entries that survive a single bar (filled when price breaks through +// `high[1]`/`low[1]`), the OCA reduce-mode bracket arithmetic at the +// minimum-tick boundary, and the intraday-cap gate. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// "Properties" → leave defaults from the script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 97 - tp/sl gap reversal oca", shorttitle="PF_p97_TPSL_OCA", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, calc_on_order_fills=false) + +// ---- inputs -------------------------------------------------------- +int i_tp_ticks = input.int(10, "Take profit (ticks)", minval=1) +int i_sl_ticks = input.int(10, "Stop loss (ticks)", minval=1) +int i_max_fills = input.int(5, "Max intraday filled orders", minval=1) +int i_atr_len = input.int(14, "ATR length", minval=1) +float i_atr_mult = input.float(1.0, "Range/ATR threshold", minval=0.1) + +strategy.risk.max_intraday_filled_orders(i_max_fills) + +// ---- range-expansion detection ------------------------------------- +// Previous bar's range vs prior ATR. When the previous bar was wider +// than the average bar, place pending stop entries above/below it. This +// preserves the pending-stop + cancel-on-next-bar lifecycle that the +// engine surface requires while firing far more often than literal +// price gaps on 24/7 crypto data. +float prev_range = high[1] - low[1] +float atr_now = ta.atr(i_atr_len) +float atr_prev = atr_now[1] +bool expansion = not na(atr_prev) and prev_range > atr_prev * i_atr_mult + +// Direction of the previous bar drives the side: previous up-bar → +// pending long stop above its high; previous down-bar → pending short +// stop below its low. Symmetric to the original gap-up/gap-down split. +bool prev_up = close[1] > open[1] +bool prev_down = close[1] < open[1] + +bool arm_long = expansion and prev_up +bool arm_short = expansion and prev_down + +// ---- pending stop entries, cancelled when trigger lifts ------------ +if arm_long + strategy.entry("LongOnGap", strategy.long, stop=high[1]) +else + strategy.cancel("LongOnGap") + +if arm_short + strategy.entry("ShortOnGap", strategy.short, stop=low[1]) +else + strategy.cancel("ShortOnGap") + +// ---- bracket: TP + SL sharing an OCA reduce group ------------------ +float pos_qty = math.abs(strategy.position_size) +int pos_dir = strategy.position_size > 0 ? 1 : (strategy.position_size < 0 ? -1 : 0) +float entry_px = strategy.position_avg_price +float tp_px = entry_px + pos_dir * i_tp_ticks * syminfo.mintick +float sl_px = entry_px - pos_dir * i_sl_ticks * syminfo.mintick + +bool in_position = pos_qty > 0 +exit_dir = pos_dir > 0 ? strategy.short : strategy.long + +if in_position + strategy.order("BracketTP", exit_dir, qty=pos_qty, limit=tp_px, + oca_name="bracket97", oca_type=strategy.oca.reduce, comment="TP") + strategy.order("BracketSL", exit_dir, qty=pos_qty, stop=sl_px, + oca_name="bracket97", oca_type=strategy.oca.reduce, comment="SL") +else + strategy.cancel("BracketTP") + strategy.cancel("BracketSL") diff --git a/tests/gate-corpus/ok/validation__composite-ies-adx-regime-classify-01.pine b/tests/gate-corpus/ok/validation__composite-ies-adx-regime-classify-01.pine new file mode 100644 index 0000000..0c66bb6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-adx-regime-classify-01.pine @@ -0,0 +1,87 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 01 — manual ADX/DI chain + ATR-ratio regime classification +// +// Purpose: isolate the IES integration probe family's bespoke regime detector. +// That family does NOT use the built-in `ta.dmi`; instead it rolls its own +// DI flow through `ta.rma`: +// +// plus_dm = max(high - high[1], 0); minus_dm = max(low[1] - low, 0) +// if plus_dm > minus_dm: minus_dm := 0 else: plus_dm := 0 +// smooth_tr/plus/minus = ta.rma(...,14) +// plus_di / minus_di / dx (DI-sum normalisation) → adx = ta.rma(dx, 14) +// +// It then classifies regime ∈ {0,1,2,3} from `adx >= 25` and an ATR-ratio +// gate (`atr(14) / sma(atr(14),42)` against 1.4 / 0.6). That ratio compares +// current ATR to a 42-bar SMA of ATR — a TA-on-TA chain that often diverges +// across engines on the early bars when the inner ATR is still warming up. +// +// This probe keeps ONLY that calculation surface and entries on the rising +// edge of `regime == 1` (trending). Exits when `regime != 1`. If the engine +// disagrees here, the IES regime gate (which feeds bias_score, regime_size_mult, +// adaptive stop multiplier, and the +2 trending bonus to bull_total) is the +// numerical edge to chase. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 01 - adx regime", shorttitle="IES_p01_REG", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_adx_len = input.int(14, "ADX Period", minval=5, maxval=30) +float i_adx_trend = input.float(25, "Trend Threshold", minval=15, maxval=40) +int i_atr_len = input.int(14, "ATR Period", minval=5, maxval=30) +float i_vol_exp = input.float(1.4,"Volatility Expansion",minval=1.1,maxval=2.0, step=0.1) +float i_vol_con = input.float(0.6,"Volatility Contraction",minval=0.3,maxval=0.9, step=0.1) + +// ---- IES's exact f_adx_calc UDF ------------------------------------ +f_adx_calc(int len) => + float tr_val = ta.tr(true) + float plus_dm = math.max(high - high[1], 0) + float minus_dm = math.max(low[1] - low, 0) + if plus_dm > minus_dm + minus_dm := 0 + else + plus_dm := 0 + float smooth_tr = ta.rma(tr_val, len) + float smooth_plus = ta.rma(plus_dm, len) + float smooth_minus = ta.rma(minus_dm, len) + float plus_di = smooth_tr > 0 ? 100 * smooth_plus / smooth_tr : 0 + float minus_di = smooth_tr > 0 ? 100 * smooth_minus / smooth_tr : 0 + float di_sum = plus_di + minus_di + float dx = di_sum > 0 ? 100 * math.abs(plus_di - minus_di) / di_sum : 0 + float adx_val = ta.rma(dx, len) + [adx_val, plus_di, minus_di] + +[adx, plus_di, minus_di] = f_adx_calc(i_adx_len) + +// ---- ATR-ratio volatility gate ------------------------------------- +float atr_val = ta.atr(i_atr_len) +float atr_ma = ta.sma(atr_val, i_atr_len * 3) +float vol_ratio = atr_ma > 0 ? atr_val / atr_ma : 1.0 + +// ---- regime classification (IES exact ladder) ---------------------- +int regime = 0 +if vol_ratio >= i_vol_exp and adx < i_adx_trend + regime := 3 +else if adx >= i_adx_trend + regime := 1 +else if vol_ratio <= i_vol_con + regime := 2 + +bool trending_regime = regime == 1 + +// ---- trivial entry on rising edge of trending regime --------------- +bool long_entry = trending_regime and not trending_regime[1] and strategy.position_size == 0 +bool long_exit = not trending_regime and strategy.position_size > 0 + +if long_entry + strategy.entry("L", strategy.long, qty=1, comment="trending ON") +if long_exit + strategy.close("L", comment="trending OFF") diff --git a/tests/gate-corpus/ok/validation__composite-ies-bb-kc-squeeze-release-01.pine b/tests/gate-corpus/ok/validation__composite-ies-bb-kc-squeeze-release-01.pine new file mode 100644 index 0000000..152a853 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-bb-kc-squeeze-release-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 05 — BB / KC squeeze release detection +// +// Purpose: isolate the IES integration probe family's volatility-squeeze +// gate. That family computes Bollinger Bands (sma+stdev) and Keltner +// Channels (ema+atr) at length 20 in parallel, then tracks +// `squeeze_on = bb_lower > kc_lower and bb_upper < kc_upper`. The release +// event is a one-bar pulse: +// +// squeeze_release = squeeze_on[1] and not squeeze_on +// +// That `squeeze_on[1]` reference is exactly the kind of derived-bool +// history reference where engine vs TV can drift — the boolean is a +// derived series produced from four parallel TA chains (sma, stdev, ema, +// atr) and any one of those warming up differently in the early bars +// flips the boundary value of `squeeze_on` for one or two bars. +// +// Trade shape: long-only — enter on `squeeze_release` (one-bar pulse); +// exit when close crosses below the KC basis (ema(close,20)). This keeps +// the entry trigger laser-focused on the squeeze_release boundary while +// leaving exit logic trivial. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 05 - bb/kc squeeze", shorttitle="IES_p05_SQZ", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_bb_len = input.int(20, "BB Length", minval=10, maxval=50) +float i_bb_mult = input.float(2.0, "BB Mult", minval=1.0, maxval=3.0, step=0.1) +int i_kc_len = input.int(20, "KC Length", minval=10, maxval=50) +float i_kc_mult = input.float(1.5, "KC Mult", minval=1.0, maxval=3.0, step=0.1) + +// ---- Bollinger Bands (sma + stdev*mult) ---------------------------- +float bb_basis = ta.sma(close, i_bb_len) +float bb_dev = ta.stdev(close, i_bb_len) * i_bb_mult +float bb_upper = bb_basis + bb_dev +float bb_lower = bb_basis - bb_dev + +// ---- Keltner Channels (ema + atr*mult) ----------------------------- +float kc_basis = ta.ema(close, i_kc_len) +float kc_range = ta.atr(i_kc_len) * i_kc_mult +float kc_upper = kc_basis + kc_range +float kc_lower = kc_basis - kc_range + +// ---- squeeze + release boundary ------------------------------------ +bool squeeze_on = bb_lower > kc_lower and bb_upper < kc_upper +bool squeeze_release = squeeze_on[1] and not squeeze_on + +// ---- enter on the release pulse, exit on KC-basis cross-under ------ +if squeeze_release and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="squeeze release") + +if ta.crossunder(close, kc_basis) and strategy.position_size > 0 + strategy.close("L", comment="kc basis lost") diff --git a/tests/gate-corpus/ok/validation__composite-ies-cooldown-daily-cap-01.pine b/tests/gate-corpus/ok/validation__composite-ies-cooldown-daily-cap-01.pine new file mode 100644 index 0000000..2cff4d1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-cooldown-daily-cap-01.pine @@ -0,0 +1,72 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 07 — `var int` cooldown counter + daily-trade-limit reset +// +// Purpose: isolate the IES integration probe family's two trade-frequency +// state machines. That family uses a `var int bars_since_trade = 999` +// counter that increments every bar and resets to 0 on entry, gated +// against `i_cooldown`. It also keeps a `var int daily_trades = 0` cap +// reset on every new day: +// +// var int bars_since_trade = 999 +// bars_since_trade := bars_since_trade + 1 +// +// var int daily_trades = 0 +// bool new_day = ta.change(time("D")) != 0 +// if new_day +// daily_trades := 0 +// +// The cooldown read happens BEFORE the entry decision; the reset and +// `daily_trades += 1` happen on the same bar AFTER the entry fires. +// That ordering plus the `ta.change(time("D"))` boundary detection +// (which depends on how the engine groups 15m bars into UTC days vs +// how TV does) are the two numerical edges this probe pins down. +// +// Trade shape: long-only EMA crossover, gated by both counters. Exit +// on cross-under. The cooldown is set high enough (15 bars ≈ 3.75 h) +// to actively gate signals; the daily cap is set low enough (3) to +// truncate at least some signal-rich days. Either gate misbehaving +// will show up as extra/missing trades vs TV. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 07 - cooldown + daily cap", shorttitle="IES_p07_COOL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast_ema = input.int(9, "Fast EMA", minval=2, maxval=50) +int i_slow_ema = input.int(21, "Slow EMA", minval=3, maxval=100) +int i_cooldown = input.int(15, "Cooldown bars", minval=0, maxval=200) +int i_max_trades = input.int(3, "Max trades / day",minval=1, maxval=50) + +// ---- cooldown counter (IES exact pattern) -------------------------- +var int bars_since_trade = 999 +bars_since_trade := bars_since_trade + 1 + +// ---- daily trade cap with UTC-day boundary ------------------------- +var int daily_trades = 0 +bool new_day = ta.change(time("D")) != 0 +if new_day + daily_trades := 0 + +// ---- trivial entry signal (EMA crossover) -------------------------- +float emaFast = ta.ema(close, i_fast_ema) +float emaSlow = ta.ema(close, i_slow_ema) + +bool raw_long_signal = ta.crossover(emaFast, emaSlow) and strategy.position_size == 0 +bool cooldown_ok = bars_since_trade >= i_cooldown +bool daily_cap_ok = daily_trades < i_max_trades + +if raw_long_signal and cooldown_ok and daily_cap_ok + strategy.entry("L", strategy.long, qty=1, comment="long w/ cooldown") + bars_since_trade := 0 + daily_trades := daily_trades + 1 + +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__composite-ies-equity-feedback-sizing-01.pine b/tests/gate-corpus/ok/validation__composite-ies-equity-feedback-sizing-01.pine new file mode 100644 index 0000000..53e4163 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-equity-feedback-sizing-01.pine @@ -0,0 +1,95 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 08 — equity-feedback dynamic position sizing (rev 2) +// +// Purpose: isolate the IES integration probe family's equity-feedback +// sizing loop, which is architecturally distinct from every other IES +// component because the `qty=` argument depends on `strategy.equity` — +// itself a function of every prior trade's PnL. Any single drift earlier +// in the trade tape permanently changes the sequence of qty values from +// that bar forward, even if the entry/exit boundary decisions agree: +// +// account_risk = strategy.equity * (i_risk_pct/100) * regime_size_mult * quality_mult +// long_risk = atr_val * adaptive_stop_mult +// qty = account_risk / long_risk +// +// Design note (rev 2): the previous revision used +// ``ta.crossover(close, ema(close, 20))`` as the entry trigger, which made +// the trade list sensitive to TV's chart-history-driven EMA seed +// (TV's chart had bars before our OHLCV CSV starts, so its EMA was +// already-converged at bar 0 of our reference; the engine seeded fresh +// from OHLCV bar 0). That gave a 6-hour shift on the very first +// crossover and cascaded into wildly different qty/equity tapes — +// conflating warmup mismatch with the actual equity-feedback contract +// under test. This rev replaces the trigger with a TIME-OF-DAY gate +// (`hour == 0 and minute == 15`, fires once per UTC day) so engine and +// TV ALWAYS agree on which bars trigger entries. Any remaining +// engine↔TV drift is then attributable solely to the equity-feedback +// arithmetic. +// +// Trade shape: enter long at chart 00:15 each day; exit via static +// ATR-multiple stop+target. `pyramiding=1`, `default_qty_type= +// strategy.fixed`, and the `qty=` argument override the default +// per-entry. If TV and engine agree on entry timestamps but disagree +// on PnL or final equity, the equity-feedback compounding is the gap. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 08 - equity sizing", shorttitle="IES_p08_SIZE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES sizing block) ----------------------- +float i_risk_pct = input.float(1.0, "Risk Per Trade %", minval=0.1, maxval=5.0, step=0.1) +float i_atr_stop_mult = input.float(2.0, "ATR Stop Multiplier", minval=1.0, maxval=5.0, step=0.5) +float i_atr_tp_mult = input.float(4.0, "ATR Target Multiplier",minval=1.0, maxval=10.0,step=0.5) +float i_adx_trend = input.float(25, "ADX Trend Threshold", minval=15, maxval=40) +float i_trend_mult = input.float(1.2, "Trending Size Mult", minval=0.5, maxval=2.0, step=0.1) +float i_neutral_mult = input.float(1.0, "Neutral Size Mult", minval=0.5, maxval=2.0, step=0.1) +float i_quality_mult = input.float(1.15,"Quality Mult (fixed)", minval=1.0, maxval=1.5, step=0.05) + +// ---- regime gate (single boundary; full ADX is in ies-probe-01) ---- +[plus_di, minus_di, adx_val] = ta.dmi(14, 14) +bool trending_regime = adx_val >= i_adx_trend + +// ---- adaptive multiplier (collapsed to two branches) --------------- +float regime_size_mult = trending_regime ? i_trend_mult : i_neutral_mult + +// ---- ATR + stop/target distances ----------------------------------- +float atr_val = ta.atr(14) +float long_risk = atr_val * i_atr_stop_mult + +// ---- equity-feedback qty (THE thing under test) -------------------- +float account_risk = strategy.equity * (i_risk_pct / 100.0) * regime_size_mult * i_quality_mult +float long_position_size = long_risk > 0 ? account_risk / long_risk : 0.0 + +// ---- TIME-OF-DAY entry gate (deterministic across engines) --------- +// Once-per-UTC-day at 00:15. The same bar fires in TV and engine, so any +// trade-list drift is the equity-feedback arithmetic itself, not the +// triggering indicator's warmup-driven seed mismatch. +bool long_entry = hour == 0 and minute == 15 + and strategy.position_size == 0 + and not na(atr_val) + and not na(adx_val) + +var float entryStop = na +var float entryTP = na + +if long_entry + entryStop := close - atr_val * i_atr_stop_mult + entryTP := close + atr_val * i_atr_tp_mult + strategy.entry("L", strategy.long, qty=long_position_size, comment="dyn qty long") + strategy.exit("LX", "L", stop=entryStop, limit=entryTP, comment="bracket") + +// @pf-trace ies_equity=strategy.equity +// @pf-trace ies_accountRisk=account_risk +// @pf-trace ies_longRisk=long_risk +// @pf-trace ies_qty=long_position_size +// @pf-trace ies_longEntry=long_entry +// @pf-trace ies_atr=atr_val +// @pf-trace ies_adx=adx_val diff --git a/tests/gate-corpus/ok/validation__composite-ies-integration-01.pine b/tests/gate-corpus/ok/validation__composite-ies-integration-01.pine new file mode 100644 index 0000000..76760f2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-integration-01.pine @@ -0,0 +1,236 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 09 — integration probe (regime + bias + momentum + pressure +// + cooldown, composed in a clean-room layered design) +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the surfaces already isolated in ies-probe-01..08 in +// a single strategy so that engine/TV agreement on the COMPONENT probes +// can be cross-checked against agreement on a representative composition. +// The four numerical layers are: +// (1) regime — manual ADX / DI / ATR-ratio chain (probe 01). +// (2) bias — 21/55/200 EMA stack with a 0–70 bias score (probe 02). +// (3) momentum — RSI(14) + RSI[3] direction + MACD histogram + memory +// gives a 0–3 score per side (probe 03). +// (4) pressure — candle-position with double-EMA smoothing + state +// ladder + momentum threshold (probe 04). +// Each layer contributes points to a `bull_total` / `bear_total` int. +// Long entry fires when bull_total >= 5 AND regime == 1 (trending) AND +// the cooldown counter (probe 07) has elapsed; short symmetric. The +// composition is the author's; the per-layer math mirrors the surfaces +// isolated by the component probes (NOT the original IES source). +// +// Validation goal: integration parity. If probes 01–07 each pass +// individually, but this composition disagrees with TradingView, the +// gap is attributable to interaction between the layers (bar-resolution +// ordering, ta-on-ta seeding under stress) rather than any single layer. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF IES probe 09 - integration", shorttitle="IES_p09_INT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ===================================================================== +// inputs (one block per layer; defaults match component probes) +// ===================================================================== +// regime layer +int i_adx_len = input.int(14, "ADX period", minval=5, maxval=30) +float i_adx_trend = input.float(25, "ADX trend threshold", minval=15, maxval=40) +int i_atr_len = input.int(14, "ATR period", minval=5, maxval=30) +float i_vol_exp = input.float(1.4,"Vol expansion", minval=1.1,maxval=2.0, step=0.1) +float i_vol_con = input.float(0.6,"Vol contraction", minval=0.3,maxval=0.9, step=0.1) +// bias layer +int i_ma_fast = input.int(21, "Bias fast EMA", minval=5, maxval=50) +int i_ma_slow = input.int(55, "Bias slow EMA", minval=20, maxval=200) +int i_ma_trend = input.int(200, "Bias trend EMA", minval=50, maxval=500) +float i_bias_thresh = input.float(30, "Bias score threshold", minval=10, maxval=70) +// momentum layer +int i_rsi_len = input.int(14, "RSI period", minval=5, maxval=30) +float i_rsi_bull = input.float(55, "RSI bullish level", minval=50, maxval=70) +float i_rsi_bear = input.float(45, "RSI bearish level", minval=30, maxval=50) +int i_macd_fast = input.int(12, "MACD fast", minval=5, maxval=20) +int i_macd_slow = input.int(26, "MACD slow", minval=15, maxval=50) +int i_macd_sig = input.int(9, "MACD signal", minval=3, maxval=15) +// pressure layer +int i_press_len = input.int(14, "Pressure period", minval=5, maxval=50) +int i_press_smo = input.int(5, "Pressure smoothing", minval=1, maxval=20) +int i_press_mom = input.int(10, "Pressure momentum", minval=3, maxval=30) +float i_press_thr = input.float(0.05,"Pressure mom thresh", minval=0.01,maxval=0.2, step=0.01) +// cooldown layer +int i_cooldown = input.int(8, "Cooldown bars", minval=0, maxval=200) + +// ===================================================================== +// layer 1 — regime (manual ADX + ATR-ratio classification) +// ===================================================================== +f_adx_chain(int len) => + float tr_v = ta.tr(true) + float p_dm = math.max(high - high[1], 0) + float m_dm = math.max(low[1] - low, 0) + if p_dm > m_dm + m_dm := 0 + else + p_dm := 0 + float s_tr = ta.rma(tr_v, len) + float s_p = ta.rma(p_dm, len) + float s_m = ta.rma(m_dm, len) + float p_di_v = s_tr > 0 ? 100 * s_p / s_tr : 0 + float m_di_v = s_tr > 0 ? 100 * s_m / s_tr : 0 + float di_sum = p_di_v + m_di_v + float dx_v = di_sum > 0 ? 100 * math.abs(p_di_v - m_di_v) / di_sum : 0 + float adx_v = ta.rma(dx_v, len) + [adx_v, p_di_v, m_di_v] + +[adx_v, p_di_v, m_di_v] = f_adx_chain(i_adx_len) +float atr_v = ta.atr(i_atr_len) +float atr_avg = ta.sma(atr_v, i_atr_len * 3) +float vol_ratio = atr_avg > 0 ? atr_v / atr_avg : 1.0 + +int regime = 0 +if vol_ratio >= i_vol_exp and adx_v < i_adx_trend + regime := 3 +else if adx_v >= i_adx_trend + regime := 1 +else if vol_ratio <= i_vol_con + regime := 2 + +bool trending = regime == 1 +bool di_bull = p_di_v > m_di_v +bool di_bear = m_di_v > p_di_v + +// ===================================================================== +// layer 2 — bias (triple-EMA stack score) +// ===================================================================== +float ma_fast = ta.ema(close, i_ma_fast) +float ma_slow = ta.ema(close, i_ma_slow) +float ma_trend = ta.ema(close, i_ma_trend) + +bool stack_up = ma_fast > ma_slow and ma_slow > ma_trend +bool stack_dn = ma_fast < ma_slow and ma_slow < ma_trend +bool above_str = close > ma_fast and close > ma_slow +bool below_str = close < ma_fast and close < ma_slow + +float bull_bias = 0.0 +if stack_up + bull_bias += 30 +if above_str + bull_bias += 20 +if close > ma_trend + bull_bias += 20 + +float bear_bias = 0.0 +if stack_dn + bear_bias += 30 +if below_str + bear_bias += 20 +if close < ma_trend + bear_bias += 20 + +bool bias_bull = bull_bias >= i_bias_thresh +bool bias_bear = bear_bias >= i_bias_thresh + +// ===================================================================== +// layer 3 — momentum (RSI + RSI[3] direction + MACD histogram memory) +// ===================================================================== +float rsi_v = ta.rsi(close, i_rsi_len) +bool rsi_bull = rsi_v > i_rsi_bull +bool rsi_bear = rsi_v < i_rsi_bear +bool rsi_mom_up = rsi_v > rsi_v[3] +bool rsi_mom_dn = rsi_v < rsi_v[3] + +float macd_line = ta.ema(close, i_macd_fast) - ta.ema(close, i_macd_slow) +float macd_sig = ta.ema(macd_line, i_macd_sig) +float macd_hist = macd_line - macd_sig +bool macd_bull = macd_hist > 0 and macd_hist > macd_hist[1] +bool macd_bear = macd_hist < 0 and macd_hist < macd_hist[1] + +int mom_bull = 0 +if rsi_bull + mom_bull += 1 +if rsi_mom_up + mom_bull += 1 +if macd_bull + mom_bull += 1 + +int mom_bear = 0 +if rsi_bear + mom_bear += 1 +if rsi_mom_dn + mom_bear += 1 +if macd_bear + mom_bear += 1 + +bool mom_bull_ok = mom_bull >= 2 +bool mom_bear_ok = mom_bear >= 2 + +// ===================================================================== +// layer 4 — pressure gauge (candle position + double-EMA smoothing) +// ===================================================================== +float bar_range = high - low +float raw_press = bar_range > 0 ? (close - low) / bar_range : 0.5 +float press_r = ta.ema(raw_press, i_press_len) +float press_s = ta.ema(press_r, i_press_smo) +float press_mom = press_s - press_s[i_press_mom] + +bool press_bull = press_s > 0.5 + i_press_thr or press_mom > i_press_thr +bool press_bear = press_s < 0.5 - i_press_thr or press_mom < -i_press_thr + +// ===================================================================== +// composite scoring (bull_total / bear_total) — author's design +// each layer contributes one or two points; cooldown gates fires +// ===================================================================== +int bull_total = 0 +if trending and di_bull + bull_total += 2 +if bias_bull + bull_total += 1 +if mom_bull_ok + bull_total += 1 +if press_bull + bull_total += 1 + +int bear_total = 0 +if trending and di_bear + bear_total += 2 +if bias_bear + bear_total += 1 +if mom_bear_ok + bear_total += 1 +if press_bear + bear_total += 1 + +// ===================================================================== +// cooldown counter (var-int across-bar carry, identical to probe 07) +// ===================================================================== +var int bars_since_trade = 999 +bars_since_trade := bars_since_trade + 1 +bool cooldown_ok = bars_since_trade >= i_cooldown + +// ===================================================================== +// entries — long requires bull_total >= 5 AND trending; short symmetric +// ===================================================================== +bool long_entry = bull_total >= 5 and trending and cooldown_ok and strategy.position_size <= 0 +bool short_entry = bear_total >= 5 and trending and cooldown_ok and strategy.position_size >= 0 + +if long_entry + if strategy.position_size < 0 + strategy.close("S", comment="flip") + strategy.entry("L", strategy.long, comment="composite long") + bars_since_trade := 0 + +if short_entry + if strategy.position_size > 0 + strategy.close("L", comment="flip") + strategy.entry("S", strategy.short, comment="composite short") + bars_since_trade := 0 + +// exit when regime falls out of trending +if not trending and strategy.position_size != 0 + strategy.close_all(comment="regime exit") diff --git a/tests/gate-corpus/ok/validation__composite-ies-pivot-liquidity-sweep-01.pine b/tests/gate-corpus/ok/validation__composite-ies-pivot-liquidity-sweep-01.pine new file mode 100644 index 0000000..6d59a9f --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-pivot-liquidity-sweep-01.pine @@ -0,0 +1,66 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 06 — pivot-tracked liquidity sweep detection +// +// Purpose: isolate the IES integration probe family's structure / sweep +// layer. That family detects pivots with `ta.pivothigh(high, 10, 5)` and +// `ta.pivotlow(low, 10, 5)` (left=10, right=5), tracks the most-recent +// confirmed pivot in `var float last_swing_high / last_swing_low`, then +// flags a "sweep" when price pierces that pivot intra-bar but closes back +// the other side: +// +// sweep_high = high > last_swing_high and close < last_swing_high +// sweep_low = low < last_swing_low and close > last_swing_low +// +// Two numerical edges live here. (1) `ta.pivothigh/low` confirms a pivot +// `right` bars *after* the candidate, so the pivot stamp is delayed — +// engine vs TV must agree on exactly which bar receives the non-na +// pivothigh value. (2) The `var float` hand-off is a cumulative state +// machine that depends on every prior pivot in the dataset; any single +// missed/extra pivot permanently shifts the sweep boundary going forward. +// +// Trade shape: enter long on `sweep_low` (a stop-hunt reversal up); flip +// to short on `sweep_high`. No exit logic beyond the flip — keeps the +// number of trades equal to the number of detected sweeps. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 06 - pivot sweep", shorttitle="IES_p06_SWP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_swing_left = input.int(10, "Swing Left Bars", minval=3, maxval=30) +int i_swing_right = input.int(5, "Swing Right Bars", minval=2, maxval=15) + +// ---- pivots (left/right asymmetric to match IES) ------------------- +float swing_high = ta.pivothigh(high, i_swing_left, i_swing_right) +float swing_low = ta.pivotlow(low, i_swing_left, i_swing_right) + +// ---- var-state pivot memory (IES exact pattern) -------------------- +var float last_swing_high = na +var float last_swing_low = na + +if not na(swing_high) + last_swing_high := swing_high +if not na(swing_low) + last_swing_low := swing_low + +// ---- sweep detection: pierce intra-bar, close back through --------- +bool sweep_high = not na(last_swing_high) and high > last_swing_high and close < last_swing_high +bool sweep_low = not na(last_swing_low) and low < last_swing_low and close > last_swing_low + +if sweep_low and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip") + strategy.entry("L", strategy.long, qty=1, comment="sweep low → long") + +if sweep_high and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip") + strategy.entry("S", strategy.short, qty=1, comment="sweep high → short") diff --git a/tests/gate-corpus/ok/validation__composite-ies-pressure-gauge-01.pine b/tests/gate-corpus/ok/validation__composite-ies-pressure-gauge-01.pine new file mode 100644 index 0000000..0fc3255 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-pressure-gauge-01.pine @@ -0,0 +1,79 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 04 — candle-position pressure with double-EMA smoothing +// +// Purpose: isolate the IES integration probe family's "pressure gauge" — a +// flow proxy built from the candle's close-position-in-range, then +// double-smoothed: +// +// raw_buy = (high - low) > 0 ? (close - low) / (high - low) : 0.5 +// pressure_ratio = ta.ema(raw_buy, 14) +// pressure_smooth = ta.ema(pressure_ratio, 5) +// pressure_momentum = pressure_smooth - pressure_smooth[10] +// +// Two failure modes can drift here. (1) Zero-range bars in the OHLCV CSV: +// the ternary returns 0.5, but a tiny float-rounding gap (e.g. high == low +// in the CSV but high > low post-magnifier) will quietly switch branches. +// (2) The ema-of-ema chain seeds differently: the inner ta.ema(raw_buy,14) +// has its own warmup, and the outer ta.ema(...,5) re-smoothes its output +// — that compounds early-bar disagreement between TV and the engine. +// +// IES's pressure_state ladder fires `pressure_bull` when state >= 1 OR +// momentum > 0.05. This probe replicates that exactly and enters long on +// the rising edge of pressure_bull, exits on rising edge of pressure_bear. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 04 - pressure gauge", shorttitle="IES_p04_PRESS", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_pressure_len = input.int(14, "Pressure Period", minval=5, maxval=50) +int i_pressure_smooth = input.int(5, "Pressure Smoothing", minval=1, maxval=20) +int i_pressure_mom = input.int(10, "Pressure Momentum", minval=3, maxval=30) +float i_pressure_high = input.float(0.7, "Extreme Buy Level", minval=0.5,maxval=0.9, step=0.05) +float i_pressure_low = input.float(0.3, "Extreme Sell Level", minval=0.1,maxval=0.5, step=0.05) +float i_pressure_thresh = input.float(0.05,"Momentum Threshold", minval=0.01,maxval=0.2, step=0.01) + +// ---- candle-position pressure with zero-range fallback ------------- +float range_val = high - low +float raw_buy = range_val > 0 ? (close - low) / range_val : 0.5 + +// ---- double-EMA smoothing chain ------------------------------------ +float pressure_ratio = ta.ema(raw_buy, i_pressure_len) +float pressure_smooth = ta.ema(pressure_ratio, i_pressure_smooth) +float pressure_momentum = pressure_smooth - pressure_smooth[i_pressure_mom] + +// ---- IES's exact pressure_state ladder ----------------------------- +int pressure_state = 0 +if pressure_smooth >= i_pressure_high + pressure_state := 2 +else if pressure_smooth > 0.5 + i_pressure_thresh + pressure_state := 1 +else if pressure_smooth <= i_pressure_low + pressure_state := -2 +else if pressure_smooth < 0.5 - i_pressure_thresh + pressure_state := -1 + +bool pressure_bull = pressure_state >= 1 or pressure_momentum > i_pressure_thresh +bool pressure_bear = pressure_state <= -1 or pressure_momentum < -i_pressure_thresh + +// ---- enter on rising edge of bull/bear pressure -------------------- +bool long_entry = pressure_bull and not pressure_bull[1] and strategy.position_size <= 0 +bool short_entry = pressure_bear and not pressure_bear[1] and strategy.position_size >= 0 + +if long_entry + if strategy.position_size < 0 + strategy.close("S", comment="flip") + strategy.entry("L", strategy.long, qty=1, comment="pressure bull") + +if short_entry + if strategy.position_size > 0 + strategy.close("L", comment="flip") + strategy.entry("S", strategy.short, qty=1, comment="pressure bear") diff --git a/tests/gate-corpus/ok/validation__composite-ies-rsi-macd-momentum-01.pine b/tests/gate-corpus/ok/validation__composite-ies-rsi-macd-momentum-01.pine new file mode 100644 index 0000000..adf3043 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-rsi-macd-momentum-01.pine @@ -0,0 +1,88 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 03 — RSI(14) + rsi[3] direction + MACD(12,26,9) histogram +// +// Purpose: isolate the IES integration probe family's momentum layer. +// That family synthesises a 0-3 "momentum score" per side from three terms: +// +// rsi_bullish = rsi > 55 (RSI threshold) +// rsi_momentum_up = rsi > rsi[3] (RSI 3-bar memory) +// macd_bullish = macd_hist > 0 and > macd_hist[1] (MACD hist memory) +// +// Both `rsi[3]` and `macd_hist[1]` are series-history references whose value +// on the boundary bar can disagree between the engine and TV if the inner +// ta.* chain warms up differently or if the [N] indexer is computed before +// vs after the current-bar value is materialised. MACD histogram is a +// triple-EMA chain (ema(12) − ema(26) → ema(9) → diff) so any 1-tick early +// drift propagates into the [1] memory. +// +// Trade shape: long when `momentum_bull_score >= 2` rises through 2 (i.e. +// the boundary crossing into bullish-2-of-3); short when bear score crosses +// into 2. Entry on rising-edge through threshold, flip on opposite. This +// preserves IES's "2-of-3 rule" exactly while shedding the rest of the +// strategy. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 03 - rsi+macd momentum", shorttitle="IES_p03_MOM", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_rsi_len = input.int(14, "RSI Period", minval=5, maxval=30) +float i_rsi_bull = input.float(55, "RSI Bullish Level",minval=50, maxval=70) +float i_rsi_bear = input.float(45, "RSI Bearish Level",minval=30, maxval=50) +int i_macd_fast = input.int(12, "MACD Fast", minval=5, maxval=20) +int i_macd_slow = input.int(26, "MACD Slow", minval=15, maxval=50) +int i_macd_sig = input.int(9, "MACD Signal", minval=3, maxval=15) + +// ---- RSI + 3-bar direction memory ---------------------------------- +float rsi = ta.rsi(close, i_rsi_len) +bool rsi_bullish = rsi > i_rsi_bull +bool rsi_bearish = rsi < i_rsi_bear +bool rsi_momentum_up = rsi > rsi[3] +bool rsi_momentum_dn = rsi < rsi[3] + +// ---- MACD histogram (IES's exact pattern, inline rather than UDF) -- +float macd_line = ta.ema(close, i_macd_fast) - ta.ema(close, i_macd_slow) +float macd_signal = ta.ema(macd_line, i_macd_sig) +float macd_hist = macd_line - macd_signal + +bool macd_bullish = macd_hist > 0 and macd_hist > macd_hist[1] +bool macd_bearish = macd_hist < 0 and macd_hist < macd_hist[1] + +// ---- 2-of-3 momentum scores --------------------------------------- +int momentum_bull_score = 0 +if rsi_bullish + momentum_bull_score += 1 +if rsi_momentum_up + momentum_bull_score += 1 +if macd_bullish + momentum_bull_score += 1 + +int momentum_bear_score = 0 +if rsi_bearish + momentum_bear_score += 1 +if rsi_momentum_dn + momentum_bear_score += 1 +if macd_bearish + momentum_bear_score += 1 + +// ---- enter on rising-edge through 2-of-3 --------------------------- +bool long_entry = momentum_bull_score >= 2 and momentum_bull_score[1] < 2 and strategy.position_size <= 0 +bool short_entry = momentum_bear_score >= 2 and momentum_bear_score[1] < 2 and strategy.position_size >= 0 + +if long_entry + if strategy.position_size < 0 + strategy.close("S", comment="flip") + strategy.entry("L", strategy.long, qty=1, comment="mom 2of3 long") + +if short_entry + if strategy.position_size > 0 + strategy.close("L", comment="flip") + strategy.entry("S", strategy.short, qty=1, comment="mom 2of3 short") diff --git a/tests/gate-corpus/ok/validation__composite-ies-three-ema-bias-score-01.pine b/tests/gate-corpus/ok/validation__composite-ies-three-ema-bias-score-01.pine new file mode 100644 index 0000000..111623e --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-ies-three-ema-bias-score-01.pine @@ -0,0 +1,82 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// IES probe 02 — 21/55/200 EMA stack + composite bias score +// +// Purpose: isolate the IES integration probe family's "bias_score" layer. +// That family stacks three EMAs +// (21/55/200) and synthesises a 0-to-100-ish bias score from four binary +// gates: +// +// ma_bullish = ma_fast > ma_slow and ma_slow > ma_trend (+30) +// price_above_structure = close > ma_fast and close > ma_slow (+20) +// close_above_trend = close > ma_trend (+20) +// plus_di > minus_di (+30) +// +// `bullish_bias = bias_score >= 30` is then a major contributor to bull_total +// (3 of the 14 max points). This probe drops the DI half (covered by +// ies-probe-01) and tests ONLY the EMA-stack arithmetic plus the bool [1] +// history reference used to find rising-edges of `bullish_bias`. The 200-EMA +// is the longest TA chain in IES; its early-bar warmup behaviour and +// 21/55/200 ordering are the most likely numerical edges in this layer. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export "List of trades" → trades.csv. +//@version=6 +strategy("PF IES probe 02 - three-ema bias", shorttitle="IES_p02_BIAS", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// ---- inputs (mirrored from IES) ------------------------------------ +int i_ma_fast = input.int(21, "Fast MA", minval=5, maxval=50) +int i_ma_slow = input.int(55, "Slow MA", minval=20, maxval=200) +int i_ma_trend = input.int(200, "Trend MA", minval=50, maxval=500) +float i_bias_thresh = input.float(30, "Bias Threshold", minval=10, maxval=60) + +// ---- the three EMAs IES stacks ------------------------------------- +float ma_fast = ta.ema(close, i_ma_fast) +float ma_slow = ta.ema(close, i_ma_slow) +float ma_trend = ta.ema(close, i_ma_trend) + +// ---- bias gates (DI half intentionally dropped — see ies-probe-01) - +bool ma_bullish = ma_fast > ma_slow and ma_slow > ma_trend +bool ma_bearish = ma_fast < ma_slow and ma_slow < ma_trend +bool price_above_structure = close > ma_fast and close > ma_slow +bool price_below_structure = close < ma_fast and close < ma_slow + +// ---- composite scores (no DI; max is 70 instead of IES's 100) ------ +float bias_score = 0.0 +if ma_bullish + bias_score += 30 +if price_above_structure + bias_score += 20 +if close > ma_trend + bias_score += 20 + +float bear_bias_score = 0.0 +if ma_bearish + bear_bias_score += 30 +if price_below_structure + bear_bias_score += 20 +if close < ma_trend + bear_bias_score += 20 + +bool bullish_bias = bias_score >= i_bias_thresh +bool bearish_bias = bear_bias_score >= i_bias_thresh + +// ---- entries on rising edge of bias (tests bool [1] history) ------- +bool long_entry = bullish_bias and not bullish_bias[1] and strategy.position_size <= 0 +bool short_entry = bearish_bias and not bearish_bias[1] and strategy.position_size >= 0 + +if long_entry + if strategy.position_size < 0 + strategy.close("S", comment="flip") + strategy.entry("L", strategy.long, qty=1, comment="bias rising long") + +if short_entry + if strategy.position_size > 0 + strategy.close("L", comment="flip") + strategy.entry("S", strategy.short, qty=1, comment="bias rising short") diff --git a/tests/gate-corpus/ok/validation__composite-kanuck-calc-on-every-tick-01.pine b/tests/gate-corpus/ok/validation__composite-kanuck-calc-on-every-tick-01.pine new file mode 100644 index 0000000..faf017f --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kanuck-calc-on-every-tick-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kanuck-probe-02-calc-on-every-tick — calc_on_every_tick semantics +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates `calc_on_every_tick=true` evaluation. The engine surface +// under test is the per-tick re-evaluation pass: a `var int` counter +// incremented every script run, plus a modulo-based entry trigger. On +// historical (replay) data both engine and TV evaluate once per bar; under +// `calc_on_every_tick` semantics the engine should still produce a trade +// list that matches TV's broker-emulator output for historical bars. +// +// Validation goal: bit-exact entries when `tick_counter % step == 0` after +// a momentum trigger fires. Note: engine vs TV may legitimately diverge on +// intra-bar order if `calc_on_every_tick` semantics differ on real-time +// bars; the historical replay used by the corpus should still match. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kanuck-probe-02-calc-on-every-tick", shorttitle="PF_kan02_TICK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + calc_on_every_tick=true) + +// ---- inputs -------------------------------------------------------- +int i_step = input.int(3, "Tick-counter modulo step", minval=1) +int i_fast = input.int(8, "EMA fast length", minval=2) +int i_slow = input.int(21, "EMA slow length", minval=3) + +// ---- per-evaluation tick counter ---------------------------------- +var int tick_counter = 0 +tick_counter += 1 + +// ---- momentum trigger --------------------------------------------- +float fast = ta.ema(close, i_fast) +float slow = ta.ema(close, i_slow) + +bool cross_up = ta.crossover(fast, slow) +bool cross_down = ta.crossunder(fast, slow) + +// ---- modulo gate on the counter ----------------------------------- +bool gate = (tick_counter % i_step) == 0 + +// ---- order routing ------------------------------------------------- +if cross_up and gate and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="tick gated long") + +if cross_down and gate and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="tick gated short") diff --git a/tests/gate-corpus/ok/validation__composite-kanuck-integration-01.pine b/tests/gate-corpus/ok/validation__composite-kanuck-integration-01.pine new file mode 100644 index 0000000..a487853 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kanuck-integration-01.pine @@ -0,0 +1,85 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kanuck-probe-integration — KAMA + tick + deep-history composition +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the three surfaces isolated by `kanuck-probe-01..03` +// (KAMA stateful smoothing, `calc_on_every_tick=true` per-tick counter, +// `max_bars_back=500` deep-history reference chain) into one strategy. +// Verifies the runtime handles all three runtime modes simultaneously +// without state corruption between them. The engine surfaces under test +// are: KAMA recurrence, per-tick counter under `calc_on_every_tick=true`, +// and deep-history retention under `max_bars_back=500`. +// +// Entries fire on `ta.crossover(close, kama)` gated by the tick-counter +// modulo (probes 01 + 02). The deep-history terms (`ta.sma(close, 400)`, +// `ta.rsi(close[200], 14)`, `close[450]`) are computed on every bar so +// the runtime has to retain the 500-bar buffer (probe 03 surface) but +// they no longer ALSO gate entries — the earlier four-AND composition +// fired far too rarely on 15m crypto for parity to be meaningful. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kanuck-probe-integration", shorttitle="PF_kanINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + calc_on_every_tick=true, max_bars_back=500) + +// ---- inputs -------------------------------------------------------- +int i_kama_len = input.int(14, "KAMA length", minval=2) +int i_kama_fast = input.int(2, "KAMA fast end", minval=1) +int i_kama_slow = input.int(30, "KAMA slow end", minval=2) +int i_step = input.int(2, "Tick-counter modulo", minval=1) +int i_sma_len = input.int(400, "Long SMA length", minval=50) +int i_rsi_offset = input.int(200, "RSI source offset", minval=1) +int i_deep_lag = input.int(450, "Close-reference lag", minval=1) + +// ---- KAMA (manual recurrence; v6 has no ta.kama built-in) ---------- +float change_n = math.abs(close - close[i_kama_len]) +float vol_sum = math.sum(math.abs(close - close[1]), i_kama_len) +float er = vol_sum > 0 ? change_n / vol_sum : 0.0 +float fast_sc = 2.0 / (i_kama_fast + 1) +float slow_sc = 2.0 / (i_kama_slow + 1) +float sc = math.pow(er * (fast_sc - slow_sc) + slow_sc, 2) +var float kama = na +kama := na(kama[1]) ? close : kama[1] + sc * (close - kama[1]) + +// ---- per-evaluation tick counter ---------------------------------- +var int tick_counter = 0 +tick_counter += 1 +bool gate = (tick_counter % i_step) == 0 + +// ---- deep-history terms (computed every bar; force 500-bar buffer) - +float long_sma = ta.sma(close, i_sma_len) +float deep_rsi = ta.rsi(close[i_rsi_offset], 14) +float deep_close = close[i_deep_lag] +bool deep_ready = not na(long_sma) and not na(deep_rsi) and not na(deep_close) + +// Plot the corroborating deep-history terms so the engine has to keep +// them resolved even though they no longer gate entries. +plot(long_sma, "deep SMA", color=color.new(color.blue, 0)) +plot(deep_rsi, "deep RSI", color=color.new(color.purple, 0), display=display.data_window) +plot(deep_close, "deep ref", color=color.new(color.gray, 0), display=display.data_window) + +// ---- composed signals --------------------------------------------- +bool kama_up = ta.crossover(close, kama) +bool kama_down = ta.crossunder(close, kama) + +bool go_long = deep_ready and kama_up and gate +bool go_short = deep_ready and kama_down and gate + +// ---- order routing ------------------------------------------------- +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ short") diff --git a/tests/gate-corpus/ok/validation__composite-kanuck-kama-state-recurrence-01.pine b/tests/gate-corpus/ok/validation__composite-kanuck-kama-state-recurrence-01.pine new file mode 100644 index 0000000..bec519a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kanuck-kama-state-recurrence-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kanuck-probe-01-kama-state — KAMA stateful adaptive smoothing +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a hand-rolled KAMA (Kaufman Adaptive MA) recurrence +// (v6 ships no `ta.kama` built-in, so the math runs explicitly through +// `math.abs`, `math.sum`, and a `var float` state carrier). The smoothing +// constant changes per-bar based on an efficiency ratio derived from +// `length` price changes; engine surface under test is the bar-by-bar +// `kama := kama[1] + sc * (close - kama[1])` update through regime +// transitions where the SC swings fastest. +// +// Validation goal: bit-exact entries on `ta.crossover(close, kama)` and +// exits on `ta.crossunder(close, kama)`. Pass = identical trade list vs +// TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kanuck-probe-01-kama-state", shorttitle="PF_kan01_KAMA", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_kama_len = input.int(14, "KAMA length", minval=2) +int i_kama_fast = input.int(2, "KAMA fast end", minval=1) +int i_kama_slow = input.int(30, "KAMA slow end", minval=2) + +// ---- KAMA (manual recurrence; v6 has no ta.kama built-in) ---------- +float change_n = math.abs(close - close[i_kama_len]) +float vol_sum = math.sum(math.abs(close - close[1]), i_kama_len) +float er = vol_sum > 0 ? change_n / vol_sum : 0.0 +float fast_sc = 2.0 / (i_kama_fast + 1) +float slow_sc = 2.0 / (i_kama_slow + 1) +float sc = math.pow(er * (fast_sc - slow_sc) + slow_sc, 2) +var float kama = na +kama := na(kama[1]) ? close : kama[1] + sc * (close - kama[1]) + +// ---- crosses ------------------------------------------------------- +bool cross_up = ta.crossover(close, kama) +bool cross_down = ta.crossunder(close, kama) + +// ---- order routing ------------------------------------------------- +if cross_up and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="kama cross up") + +if cross_down and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="kama cross down") diff --git a/tests/gate-corpus/ok/validation__composite-kanuck-max-bars-back-500-01.pine b/tests/gate-corpus/ok/validation__composite-kanuck-max-bars-back-500-01.pine new file mode 100644 index 0000000..ca3d487 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kanuck-max-bars-back-500-01.pine @@ -0,0 +1,67 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kanuck-probe-03-max-bars-back-500 — deep-history reference chain +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates `max_bars_back=500`. The engine surface under test is +// the runtime's deep-history buffer: a chain that combines `ta.sma(close, +// 400)`, `ta.rsi(close[200], 14)`, and a raw `close[450]` reference forces +// the runtime to retain at least 500 bars of every series touched. Verifies +// that the engine handles the deep buffer without truncation artefacts and +// matches TradingView's broker emulator on the bars where these terms first +// become non-na. +// +// Validation goal: bit-exact entries on `close > close[450]` (long) and +// `close < close[450]` (short) — a state-flipping condition off the +// deep-history buffer that fires many times across the 36k-bar window. +// `ta.sma(close, 400)` and `ta.rsi(close[200], 14)` are computed on +// every bar (and would also be plotted in TV) so the engine still has +// to retain the 500-bar buffer for those terms — they are the deep- +// history terms under test even though they no longer gate entries. +// (The earlier triple-AND form is statistically far too rare on 15m +// crypto to produce a meaningful trade-list parity check.) +// Pass = identical trade list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kanuck-probe-03-max-bars-back-500", shorttitle="PF_kan03_MBB", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + max_bars_back=500) + +// ---- inputs -------------------------------------------------------- +int i_sma_len = input.int(400, "Long SMA length", minval=50) +int i_rsi_offset = input.int(200, "RSI source offset", minval=1) +int i_deep_lag = input.int(450, "Close-reference lookback", minval=1) + +// ---- deep-history terms ------------------------------------------- +// All three are computed every bar and force the runtime to retain +// the 500-bar buffer. `long_sma` and `deep_rsi` are the corroborating +// deep-buffer terms; `deep_close` is the entry trigger. +float long_sma = ta.sma(close, i_sma_len) +float deep_rsi = ta.rsi(close[i_rsi_offset], 14) +float deep_close = close[i_deep_lag] + +// Plot the corroborating deep-history terms so the engine has to keep +// them resolved even though they no longer gate entries. +plot(long_sma, "deep SMA", color=color.new(color.blue, 0)) +plot(deep_rsi, "deep RSI", color=color.new(color.purple, 0), display=display.data_window) + +// ---- composite condition ------------------------------------------ +bool deep_ready = not na(long_sma) and not na(deep_rsi) and not na(deep_close) + +bool long_trigger = deep_ready and close > deep_close +bool short_trigger = deep_ready and close < deep_close + +// ---- order routing ------------------------------------------------- +if long_trigger and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="deep-history long") + +if short_trigger and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="deep-history short") diff --git a/tests/gate-corpus/ok/validation__composite-kkb-ema-atr-breakout-band-01.pine b/tests/gate-corpus/ok/validation__composite-kkb-ema-atr-breakout-band-01.pine new file mode 100644 index 0000000..1cef766 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kkb-ema-atr-breakout-band-01.pine @@ -0,0 +1,48 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kkb-probe-02-breakout-trigger — EMA mid + ATR breakout band +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a smoothed-band breakout. The engine surfaces under +// test are `ta.ema(close, len)` for the central band, `ta.atr(len)` for +// volatility, and the upper/lower band arithmetic. Long entries fire on +// `ta.crossover(close, upper)`, short on `ta.crossunder(close, lower)`. +// Verifies that engine vs TV agree on the EMA/ATR numerics at the +// breakout boundary. +// +// Validation goal: bit-exact entries on close-vs-band breakouts. Pass = +// identical trade list vs TradingView's broker emulator. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kkb-probe-02-breakout-trigger", shorttitle="PF_kkb02_BRK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_band_len = input.int(20, "Band EMA length", minval=2) +int i_atr_len = input.int(14, "ATR length", minval=2) +float i_atr_mult = input.float(1.5, "ATR multiplier", minval=0.1, step=0.1) + +// ---- band --------------------------------------------------------- +float band = ta.ema(close, i_band_len) +float vol = ta.atr(i_atr_len) +float upper = band + vol * i_atr_mult +float lower = band - vol * i_atr_mult + +// ---- crosses ------------------------------------------------------ +bool cross_up = ta.crossover(close, upper) +bool cross_down = ta.crossunder(close, lower) + +// ---- order routing ------------------------------------------------- +if cross_up and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="band breakout long") + +if cross_down and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="band breakdown short") diff --git a/tests/gate-corpus/ok/validation__composite-kkb-integration-01.pine b/tests/gate-corpus/ok/validation__composite-kkb-integration-01.pine new file mode 100644 index 0000000..97eeade --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kkb-integration-01.pine @@ -0,0 +1,68 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kkb-probe-integration — Kalman + breakout + margin-100% +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the three surfaces isolated by `kkb-probe-01..03` +// (1-D Kalman filter, EMA-mid + ATR breakout band, `margin_long=100, +// margin_short=100` broker-emulator semantics) into one strategy. Long +// when Kalman trends up AND price breaks the upper band; short when +// Kalman trends down AND price breaks the lower band. Exit when the +// opposite breakout fires. Verifies that the runtime carries Kalman +// state correctly while the broker emulator enforces full-margin sizing. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kkb-probe-integration", shorttitle="PF_kkbINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + margin_long=100, margin_short=100) + +// ---- inputs -------------------------------------------------------- +float i_q = input.float(0.001, "Process variance Q", minval=0.0, step=0.0005) +float i_r = input.float(0.1, "Measurement variance R", minval=0.0001, step=0.01) +int i_band_len = input.int(20, "Band EMA length", minval=2) +int i_atr_len = input.int(14, "ATR length", minval=2) +float i_atr_mult = input.float(1.5, "ATR multiplier", minval=0.1, step=0.1) + +// ---- Kalman state ------------------------------------------------- +var float x = na +var float p = 1.0 + +float x_pred = nz(x[1], close) +float p_pred = nz(p[1], 1.0) + i_q +float k_gain = p_pred / (p_pred + i_r) +x := x_pred + k_gain * (close - x_pred) +p := (1 - k_gain) * p_pred + +bool kalman_bull = x > nz(x[1], x) +bool kalman_bear = x < nz(x[1], x) + +// ---- breakout band ------------------------------------------------ +float band = ta.ema(close, i_band_len) +float vol = ta.atr(i_atr_len) +float upper = band + vol * i_atr_mult +float lower = band - vol * i_atr_mult + +bool break_up = ta.crossover(close, upper) +bool break_down = ta.crossunder(close, lower) + +// ---- composed gates ----------------------------------------------- +bool go_long = kalman_bull and break_up +bool go_short = kalman_bear and break_down + +// ---- order routing ------------------------------------------------- +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ short") diff --git a/tests/gate-corpus/ok/validation__composite-kkb-kalman-filter-1d-01.pine b/tests/gate-corpus/ok/validation__composite-kkb-kalman-filter-1d-01.pine new file mode 100644 index 0000000..bf9bef2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kkb-kalman-filter-1d-01.pine @@ -0,0 +1,58 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kkb-probe-01-kalman-filter — per-bar 1-D Kalman update +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a 1-dimensional Kalman filter state recurrence written +// directly in PineScript. The engine surface under test is the cross-bar +// `var float` state pair (state estimate + error covariance) and the per-bar +// predict / update arithmetic: +// +// x_pred = x[1] +// p_pred = p[1] + q +// k_gain = p_pred / (p_pred + r) +// x = x_pred + k_gain * (close - x_pred) +// p = (1 - k_gain) * p_pred +// +// Verifies that engine vs TV agree on the gain numerics, especially after +// many recursive updates where small per-bar drift compounds. +// +// Validation goal: bit-exact entries on `ta.crossover(close, x)` and exits +// on `ta.crossunder(close, x)`. Pass = identical trade list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kkb-probe-01-kalman-filter", shorttitle="PF_kkb01_KAL", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +float i_q = input.float(0.001, "Process variance Q", minval=0.0, step=0.0005) +float i_r = input.float(0.1, "Measurement variance R", minval=0.0001, step=0.01) + +// ---- Kalman state (cross-bar) ------------------------------------- +var float x = na +var float p = 1.0 + +float x_pred = nz(x[1], close) +float p_pred = nz(p[1], 1.0) + i_q +float k_gain = p_pred / (p_pred + i_r) +x := x_pred + k_gain * (close - x_pred) +p := (1 - k_gain) * p_pred + +// ---- crosses ------------------------------------------------------- +bool cross_up = ta.crossover(close, x) +bool cross_down = ta.crossunder(close, x) + +// ---- order routing ------------------------------------------------- +if cross_up and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="kalman cross up") + +if cross_down and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="kalman cross down") diff --git a/tests/gate-corpus/ok/validation__composite-kkb-margin-100-pct-01.pine b/tests/gate-corpus/ok/validation__composite-kkb-margin-100-pct-01.pine new file mode 100644 index 0000000..90a6b6e --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-kkb-margin-100-pct-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe kkb-probe-03-margin-100-pct — margin_long/margin_short=100% +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the broker-emulator's margin/sizing semantics under +// `margin_long=100, margin_short=100`. The engine surface under test is +// strategy declaration argument propagation through to per-bar position +// sizing — at 100% margin the broker should require full notional cover +// for every fill. The strategy itself is a trivial dual-SMA cross so the +// only differentiator vs other probes is the margin declaration. +// +// Validation goal: bit-exact entries on `ta.crossover(ta.sma(close,5), +// ta.sma(close,20))` and exits on the symmetric cross-under, against +// TradingView's broker emulator with the same margin settings. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe kkb-probe-03-margin-100-pct", shorttitle="PF_kkb03_MGN", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + margin_long=100, margin_short=100) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast SMA length", minval=2) +int i_slow = input.int(20, "Slow SMA length", minval=3) + +// ---- dual SMA ------------------------------------------------------ +float fast = ta.sma(close, i_fast) +float slow = ta.sma(close, i_slow) + +bool cross_up = ta.crossover(fast, slow) +bool cross_down = ta.crossunder(fast, slow) + +// ---- order routing ------------------------------------------------- +if cross_up and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="ma cross up") + +if cross_down and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="ma cross down") diff --git a/tests/gate-corpus/ok/validation__composite-liqsweep-integration-01.pine b/tests/gate-corpus/ok/validation__composite-liqsweep-integration-01.pine new file mode 100644 index 0000000..ac2c8f5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-liqsweep-integration-01.pine @@ -0,0 +1,89 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe liquidity-sweep-probe-integration — swing + sweep + reentry +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the three surfaces isolated by +// `liquidity-sweep-probe-01..03` (pivot-confirmed HH/LL swing structure, +// sweep-bar wick-pierce-and-close-back, one-bar-wait reentry on +// continuation) into one strategy. Long entries require a recent +// higher-high context AND a sweep-of-lows AND a one-bar-delayed +// continuation candle; short symmetric. Verifies that the engine carries +// pivot, sweep, and latch state correctly across many bars. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe liquidity-sweep-probe-integration", shorttitle="PF_lspINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) + +// ---- pivot detection ---------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +// ---- swing state (last + previous) -------------------------------- +var float last_ph = na +var float prev_ph = na +var float last_pl = na +var float prev_pl = na + +if not na(ph) + prev_ph := last_ph + last_ph := ph +if not na(pl) + prev_pl := last_pl + last_pl := pl + +// ---- structural-direction flags (latched until next confirmed pivot) +var bool struct_bull = false +var bool struct_bear = false + +if not na(ph) and not na(prev_ph) + if ph > prev_ph + struct_bull := true + struct_bear := false +if not na(pl) and not na(prev_pl) + if pl < prev_pl + struct_bear := true + struct_bull := false + +// ---- sweep detection ---------------------------------------------- +bool sweep_high = not na(last_ph) and high > last_ph and close < last_ph +bool sweep_low = not na(last_pl) and low < last_pl and close > last_pl + +// ---- one-bar wait latches ----------------------------------------- +var int wait_long = 0 +var int wait_short = 0 + +if sweep_low and struct_bull + wait_long := 1 +else if wait_long > 0 + wait_long := wait_long - 1 + +if sweep_high and struct_bear + wait_short := 1 +else if wait_short > 0 + wait_short := wait_short - 1 + +bool fire_long = wait_long == 0 and wait_long[1] == 1 and close > open +bool fire_short = wait_short == 0 and wait_short[1] == 1 and close < open + +// ---- order routing ------------------------------------------------- +if fire_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ long") + +if fire_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ short") diff --git a/tests/gate-corpus/ok/validation__composite-liqsweep-pivot-hh-ll-01.pine b/tests/gate-corpus/ok/validation__composite-liqsweep-pivot-hh-ll-01.pine new file mode 100644 index 0000000..ec2be06 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-liqsweep-pivot-hh-ll-01.pine @@ -0,0 +1,58 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe liquidity-sweep-probe-01-pivot-swing — directional swing-structure +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates pivot-high/low detection plus a higher-high / lower-low +// directional confirm. The engine surfaces under test are `ta.pivothigh` / +// `ta.pivotlow` and the cross-bar `var float` previous-swing tracking used +// to classify each newly confirmed pivot as a higher-high, lower-high, +// higher-low, or lower-low. Entries fire on the rising edge of a confirmed +// higher-high (long) or lower-low (short). +// +// Validation goal: bit-exact entries on confirmed HH/LL events. Pass = +// identical trade list vs TradingView's broker emulator. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe liquidity-sweep-probe-01-pivot-swing", shorttitle="PF_lsp01_SWG", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) + +// ---- pivot detection ---------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +// ---- last-confirmed swings ---------------------------------------- +var float last_ph = na +var float prev_ph = na +var float last_pl = na +var float prev_pl = na + +if not na(ph) + prev_ph := last_ph + last_ph := ph +if not na(pl) + prev_pl := last_pl + last_pl := pl + +// ---- HH / LL events ----------------------------------------------- +bool higher_high = not na(ph) and not na(prev_ph) and ph > prev_ph +bool lower_low = not na(pl) and not na(prev_pl) and pl < prev_pl + +// ---- order routing ------------------------------------------------- +if higher_high and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="hh long") + +if lower_low and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="ll short") diff --git a/tests/gate-corpus/ok/validation__composite-liqsweep-wait-one-continuation-01.pine b/tests/gate-corpus/ok/validation__composite-liqsweep-wait-one-continuation-01.pine new file mode 100644 index 0000000..f1cccd2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-liqsweep-wait-one-continuation-01.pine @@ -0,0 +1,73 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe liquidity-sweep-probe-03-reentry-on-sweep — wait-one + continuation +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates a delayed entry after a sweep confirms — detect the +// sweep on bar N, wait one bar, enter on the continuation bar (N+2) only +// if its close confirms the sweep direction. The engine surfaces under +// test are pivot retention plus a small `var int` countdown latch that +// expires after one bar; this validates both `var` state semantics and +// the engine's handling of multi-bar entry preconditions. +// +// Validation goal: bit-exact entries placed exactly two bars after a +// sweep (sweep at N, wait one bar at N+1, continuation entry at N+2 if +// the continuation candle confirms direction). Pass = identical trade +// list vs TradingView. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe liquidity-sweep-probe-03-reentry-on-sweep", shorttitle="PF_lsp03_REE", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) + +// ---- pivot tracking ----------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- sweep detection ---------------------------------------------- +bool sweep_high = not na(last_ph) and high > last_ph and close < last_ph +bool sweep_low = not na(last_pl) and low < last_pl and close > last_pl + +// ---- one-bar wait latches ----------------------------------------- +var int wait_long = 0 +var int wait_short = 0 + +if sweep_low + wait_long := 1 +else if wait_long > 0 + wait_long := wait_long - 1 + +if sweep_high + wait_short := 1 +else if wait_short > 0 + wait_short := wait_short - 1 + +// ---- continuation gates (fire when latch was just decremented to 0) +bool fire_long = wait_long == 0 and wait_long[1] == 1 and close > open +bool fire_short = wait_short == 0 and wait_short[1] == 1 and close < open + +// ---- order routing ------------------------------------------------- +if fire_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="reentry long") + +if fire_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="reentry short") diff --git a/tests/gate-corpus/ok/validation__composite-liqsweep-wick-pierce-close-back-01.pine b/tests/gate-corpus/ok/validation__composite-liqsweep-wick-pierce-close-back-01.pine new file mode 100644 index 0000000..eb986f7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-liqsweep-wick-pierce-close-back-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe liquidity-sweep-probe-02-sweep-bar — wick-pierce + close-back-inside +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the sweep-bar pattern: a candle whose wick pierces the +// most recently confirmed swing high (or low) but whose close returns +// inside the prior range. The engine surfaces under test are pivot-state +// retention (`var float last_ph`/`last_pl`) and the per-bar boolean +// composition (`high > last_ph and close < last_ph` for a bearish sweep +// of highs; symmetric for bullish sweep of lows). The bullish sweep-of- +// lows triggers a long entry; the bearish sweep-of-highs triggers a +// short entry — the standard liquidity-grab interpretation. +// +// Validation goal: bit-exact entries on the bar that completes a sweep +// pattern. Pass = identical trade list vs TradingView's broker emulator. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe liquidity-sweep-probe-02-sweep-bar", shorttitle="PF_lsp02_SWP", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(5, "Pivot left bars", minval=1) +int i_right = input.int(5, "Pivot right bars", minval=1) + +// ---- pivot tracking ----------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- sweep patterns ------------------------------------------------ +bool sweep_high = not na(last_ph) and high > last_ph and close < last_ph +bool sweep_low = not na(last_pl) and low < last_pl and close > last_pl + +// ---- order routing ------------------------------------------------- +if sweep_low and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="bullish sweep long") + +if sweep_high and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="bearish sweep short") diff --git a/tests/gate-corpus/ok/validation__composite-marketshift-integration-01.pine b/tests/gate-corpus/ok/validation__composite-marketshift-integration-01.pine new file mode 100644 index 0000000..c71b64a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-marketshift-integration-01.pine @@ -0,0 +1,97 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe market-shift-probe-integration — pivot state + rolling extreme + edge entries +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the three surfaces isolated by `market-shift-probe-01..03` +// into a single end-to-end strategy. The engine surface under test is the +// cross-surface composition: pivot-driven `market_state` flips on price +// breaking the most-recent confirmed pivot (probe 01); the rolling-window +// extreme (probe 02) provides an alternative rebound entry; the edge +// detector (probe 03) ensures entries fire once per state transition. +// +// Entries use OR-of-AND composition rather than strict AND-of-all: a +// long fires either on a fresh bull state edge OR on a rolling-low +// rebound while in a bull state. Strict AND-composition requires pivot +// break and rolling-extreme cross to land on the same bar, which is +// vanishingly rare on 15m crypto and produces no trades at all. The +// OR-of-AND form still routes EVERY entry through all three surfaces: +// the state machine must be in the right regime, the rolling extreme +// must agree (or the pivot edge must fire), and the edge detector +// stops same-bar duplicates. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe market-shift-probe-integration", shorttitle="PF_MSINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength", minval=2, maxval=20) +int i_window = input.int(50, "Rolling window length", minval=10, maxval=200) + +// ---- pivot tracking ------------------------------------------------ +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- rolling extreme gate ------------------------------------------ +float roll_hi = ta.highest(high, i_window) +float roll_lo = ta.lowest(low, i_window) + +float ref_hi = roll_hi[1] +float ref_lo = roll_lo[1] + +bool extreme_up = ta.crossover(close, ref_lo) +bool extreme_down = ta.crossunder(close, ref_hi) + +// ---- state machine (probe 01 surface) ------------------------------ +var int market_state = 0 +var int prev_state = 0 + +bool pivot_break_up = not na(last_ph) and close > last_ph +bool pivot_break_down = not na(last_pl) and close < last_pl + +if pivot_break_up + market_state := 1 +else if pivot_break_down + market_state := -1 + +// ---- edge detector (probe 03 surface) ------------------------------ +bool state_long_edge = market_state == 1 and prev_state != 1 +bool state_short_edge = market_state == -1 and prev_state != -1 + +// ---- composed entry (OR of two AND-gated surfaces) ----------------- +// Fire long either: +// (a) on a fresh bull state transition (probe 01 → 03 path), OR +// (b) on a rolling-low rebound while in a bull state +// (probe 02 ∧ 01 path). +// Same for short, mirrored. Every entry still passes through the +// state machine + at least one of {edge detector, rolling extreme}. +bool go_long = (state_long_edge) or (market_state == 1 and extreme_up) +bool go_short = (state_short_edge) or (market_state == -1 and extreme_down) + +// ---- routing ------------------------------------------------------- +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ bull") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ bear") + +prev_state := market_state diff --git a/tests/gate-corpus/ok/validation__composite-marketshift-pivot-state-machine-01.pine b/tests/gate-corpus/ok/validation__composite-marketshift-pivot-state-machine-01.pine new file mode 100644 index 0000000..4da4504 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-marketshift-pivot-state-machine-01.pine @@ -0,0 +1,64 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe market-shift-probe-01-shift-state — bull/bear regime state machine on pivot breaks +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the regime state machine surface used by structural-shift +// strategies. A `var int market_state` holds three values (0 neutral, 1 bull, +// -1 bear). Transitions happen only when price closes through the most recent +// confirmed pivot high (→ bull) or pivot low (→ bear). The engine surface +// under test is cross-bar `var` state mutation gated by `ta.pivothigh` / +// `ta.pivotlow` confirmation plus simple price-vs-level breakthrough. No +// orders depend on bracket arithmetic here — this probe routes a trivial +// flat-flip entry directly off `market_state` so any TV/engine drift is +// attributable to the pivot tracking + state mutation, nothing else. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Long when state flips to 1, +// short when state flips to -1. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe market-shift-probe-01-shift-state", shorttitle="PF_MS01_STATE", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength (left=right)", minval=2, maxval=20) + +// ---- pivot tracking ------------------------------------------------ +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +// Hold the most-recently confirmed pivot levels across bars. +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- regime state machine ------------------------------------------ +var int market_state = 0 + +bool break_up = not na(last_ph) and close > last_ph +bool break_down = not na(last_pl) and close < last_pl + +if break_up + market_state := 1 +else if break_down + market_state := -1 + +// ---- trivial flat/flip routing off the state ----------------------- +if market_state == 1 and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="state bull") + +if market_state == -1 and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="state bear") diff --git a/tests/gate-corpus/ok/validation__composite-marketshift-rolling-highest-lowest-01.pine b/tests/gate-corpus/ok/validation__composite-marketshift-rolling-highest-lowest-01.pine new file mode 100644 index 0000000..bb6b8e1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-marketshift-rolling-highest-lowest-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe market-shift-probe-02-rolling-extreme — rolling highest/lowest tracking +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the rolling-window extreme surface (`ta.highest(high, N)` +// and `ta.lowest(low, N)`) used by shift detectors to anchor their +// rebound/breakout reference levels. Entry triggers on `ta.crossover(close, +// ta.lowest(low, N)[1])` (close pierces the prior bar's rolling low → +// rebound long); short is symmetric on `ta.crossunder(close, +// ta.highest(high, N)[1])`. Using the [1]-shifted reference avoids the +// trivial "close == lowest" same-bar tautology and exercises history-access +// alignment in TV vs the engine. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. The window-length surface +// is the only meaningful drift candidate — `ta.highest`/`ta.lowest` warmup +// and series-history precision should agree perfectly through bar 50+. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe market-shift-probe-02-rolling-extreme", shorttitle="PF_MS02_ROLL", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_window = input.int(50, "Rolling window length", minval=10, maxval=200) + +// ---- rolling extremes --------------------------------------------- +float roll_hi = ta.highest(high, i_window) +float roll_lo = ta.lowest(low, i_window) + +// Reference the prior-bar value so the cross is not a self-tautology. +float ref_hi = roll_hi[1] +float ref_lo = roll_lo[1] + +bool long_signal = ta.crossover(close, ref_lo) +bool short_signal = ta.crossunder(close, ref_hi) + +// ---- routing ------------------------------------------------------- +if long_signal and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="rebound long") + +if short_signal and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="rebound short") diff --git a/tests/gate-corpus/ok/validation__composite-marketshift-state-edge-detector-01.pine b/tests/gate-corpus/ok/validation__composite-marketshift-state-edge-detector-01.pine new file mode 100644 index 0000000..84b2dd1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-marketshift-state-edge-detector-01.pine @@ -0,0 +1,69 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe market-shift-probe-03-shift-driven-entry — entries on state-change edges +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the rising-edge detector surface used to fire entries +// only on `market_state` transitions (not on every bar where the state is +// "true"). Two cross-bar `var int` slots — `market_state` and `prev_state` — +// produce a single-bar long signal when `market_state` flips to +1 from +// anything else, and a single-bar short signal on the symmetric -1 flip. +// The engine surface under test is the bar-end `prev_state := market_state` +// assignment ordering: the comparison MUST happen before the assignment, or +// the edge collapses and entries fire every bar instead of once. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Long fires once per bull +// transition, short fires once per bear transition. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe market-shift-probe-03-shift-driven-entry", shorttitle="PF_MS03_EDGE", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength (left=right)", minval=2, maxval=20) + +// ---- pivot tracking ------------------------------------------------ +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- regime state machine + edge detector -------------------------- +var int market_state = 0 +var int prev_state = 0 + +bool break_up = not na(last_ph) and close > last_ph +bool break_down = not na(last_pl) and close < last_pl + +if break_up + market_state := 1 +else if break_down + market_state := -1 + +bool long_edge = market_state == 1 and prev_state != 1 +bool short_edge = market_state == -1 and prev_state != -1 + +// ---- routing ------------------------------------------------------- +if long_edge and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="bull edge") + +if short_edge and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="bear edge") + +// Update prev_state AFTER all comparisons + entries — last statement. +prev_state := market_state diff --git a/tests/gate-corpus/ok/validation__composite-scalping-fast-ma-cross-trigger-01.pine b/tests/gate-corpus/ok/validation__composite-scalping-fast-ma-cross-trigger-01.pine new file mode 100644 index 0000000..d9552a8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-scalping-fast-ma-cross-trigger-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe scalping-probe-02-fast-ma-trigger — short-period EMA cross flip-on-cross +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the short-period EMA cross trigger surface that drives +// most scalp strategies' direction signal. Two EMAs (5 and 13) generate +// flat-flip entries on `ta.crossover` / `ta.crossunder` with no bracket, +// no filters — pure cross-on-cross. The engine surface under test is the +// EMA recursion warmup precision and the `ta.crossover`/`ta.crossunder` +// strict-inequality semantics: TV uses `now > prev_other and prev <= +// prev_other` so the engine's edge detector must follow exactly the same +// rule, especially around equal-EMA touches. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe scalping-probe-02-fast-ma-trigger", shorttitle="PF_SC02_MA", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) + +// ---- EMA pair + cross --------------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- flip-on-cross entries ---------------------------------------- +if long_signal + strategy.entry("L", strategy.long, qty=1, comment="ma flip long") + +if short_signal + strategy.entry("S", strategy.short, qty=1, comment="ma flip short") diff --git a/tests/gate-corpus/ok/validation__composite-scalping-integration-01.pine b/tests/gate-corpus/ok/validation__composite-scalping-integration-01.pine new file mode 100644 index 0000000..3f2415b --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-scalping-integration-01.pine @@ -0,0 +1,61 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe scalping-probe-integration — fast EMA cross entries with tight TP/SL bracket +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the two surfaces isolated by `scalping-probe-01..02` into +// a single end-to-end scalp strategy. The engine surface under test is the +// cross-surface composition: fast EMA cross fires the entry, then the tight +// bracket TP (+15 ticks) / SL (-7 ticks) governs the exit. Bracket arithmetic +// must be computed off the entry fill, and intra-bar TP-vs-SL resolution must +// match TV's open→high/low/close traversal rule. A new opposite cross can +// arrive while the bracket is still active — the engine must handle the +// `strategy.entry` reverse-position semantics correctly in that case. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe scalping-probe-integration", shorttitle="PF_SCINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) +int i_tp_tk = input.int(15, "Take profit (ticks)", minval=1) +int i_sl_tk = input.int(7, "Stop loss (ticks)", minval=1) + +// ---- EMA pair + cross trigger ------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- entries ------------------------------------------------------- +if long_signal + strategy.entry("L", strategy.long, qty=1, comment="integ scalp long") + +if short_signal + strategy.entry("S", strategy.short, qty=1, comment="integ scalp short") + +// ---- bracket exits anchored on entry fill -------------------------- +float entry_px = strategy.position_avg_price + +if strategy.position_size > 0 + float tp_px = entry_px + i_tp_tk * syminfo.mintick + float sl_px = entry_px - i_sl_tk * syminfo.mintick + strategy.exit("Brk", from_entry="L", stop=sl_px, limit=tp_px) + +if strategy.position_size < 0 + float tp_px = entry_px - i_tp_tk * syminfo.mintick + float sl_px = entry_px + i_sl_tk * syminfo.mintick + strategy.exit("Brk", from_entry="S", stop=sl_px, limit=tp_px) diff --git a/tests/gate-corpus/ok/validation__composite-scalping-tight-tp-sl-points-01.pine b/tests/gate-corpus/ok/validation__composite-scalping-tight-tp-sl-points-01.pine new file mode 100644 index 0000000..73fba8a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-scalping-tight-tp-sl-points-01.pine @@ -0,0 +1,63 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe scalping-probe-01-tight-tp-sl-points — small-points TP/SL via strategy.exit +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the tight-bracket exit surface used by scalp strategies. +// Entry is a trivial fast EMA cross (5 vs 13). The bracket is placed via +// `strategy.exit("Brk", from_entry=..., stop=sl_px, limit=tp_px)` with +// absolute prices computed from the entry fill (TP = +15 ticks, SL = -7 +// ticks via `syminfo.mintick`). The engine surface under test is the +// `strategy.exit` mintick-aligned price arithmetic and TP-vs-SL fill +// priority within a single bar — when both TP and SL straddle the bar's +// range, TV resolves with a fixed open→high/low/close path that the engine +// must reproduce. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Every bracket fill must +// match TV in side, price, and timing. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe scalping-probe-01-tight-tp-sl-points", shorttitle="PF_SC01_BRK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) +int i_tp_tk = input.int(15, "Take profit (ticks)", minval=1) +int i_sl_tk = input.int(7, "Stop loss (ticks)", minval=1) + +// ---- fast EMA cross trigger ---------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- entries ------------------------------------------------------- +if long_signal + strategy.entry("L", strategy.long, qty=1, comment="scalp long") + +if short_signal + strategy.entry("S", strategy.short, qty=1, comment="scalp short") + +// ---- bracket exits anchored on entry fill -------------------------- +float entry_px = strategy.position_avg_price + +if strategy.position_size > 0 + float tp_px = entry_px + i_tp_tk * syminfo.mintick + float sl_px = entry_px - i_sl_tk * syminfo.mintick + strategy.exit("Brk", from_entry="L", stop=sl_px, limit=tp_px) + +if strategy.position_size < 0 + float tp_px = entry_px - i_tp_tk * syminfo.mintick + float sl_px = entry_px + i_sl_tk * syminfo.mintick + strategy.exit("Brk", from_entry="S", stop=sl_px, limit=tp_px) diff --git a/tests/gate-corpus/ok/validation__composite-trendmaster-integration-01.pine b/tests/gate-corpus/ok/validation__composite-trendmaster-integration-01.pine new file mode 100644 index 0000000..872c823 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-trendmaster-integration-01.pine @@ -0,0 +1,124 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe trendmaster-probe-integration — line projection + EMA stack + composite gate + pivot bracket +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the four surfaces isolated by `trendmaster-probe-01..04` +// into a single end-to-end trend strategy. The engine surface under test +// is the cross-surface composition: +// - pivot detection drives both line projection AND bracket anchors, +// - three-tier EMA stack provides regime gating AND the trend predicate, +// - RSI + 20-bar breakout add momentum + structure filters, +// - `strategy.exit` places pivot-anchored TP/SL once filled. +// Entries fire only when ALL gates align: stack regime matches direction, +// composite gate (trend+momentum+structure) is true, and a pivot anchor +// exists. `max_lines_count=500` keeps the drawing-object chain bounded. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Trade list parity is the +// pass criterion; line drawings are visual-only and need not be checked. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe trendmaster-probe-integration", shorttitle="PF_TMINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, max_lines_count=500) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength", minval=2, maxval=20) +int i_ema_fast = input.int(21, "EMA fast", minval=3, maxval=100) +int i_ema_mid = input.int(55, "EMA mid", minval=10, maxval=200) +int i_ema_slow = input.int(200, "EMA slow", minval=50, maxval=500) +int i_rsi_len = input.int(14, "RSI length", minval=2, maxval=50) +float i_rsi_lo = input.float(55, "RSI long threshold", minval=50, maxval=80) +float i_rsi_hi = input.float(45, "RSI short threshold", minval=20, maxval=50) +int i_break_len = input.int(20, "Breakout window", minval=5, maxval=200) +float i_rr = input.float(2.0, "Reward:risk ratio", minval=0.5, maxval=10.0, step=0.5) + +// ---- pivot detection + retention ---------------------------------- +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +var float last_ph = na +var float last_pl = na +var int last_ph_x = na +var int last_pl_x = na +var float prev_ph_y = na +var int prev_ph_x = na +var float prev_pl_y = na +var int prev_pl_x = na + +if not na(ph) + int cur_x = bar_index - i_pivot + if not na(last_ph) and not na(last_ph_x) + line.new(last_ph_x, last_ph, cur_x, ph, color=color.red) + prev_ph_y := last_ph + prev_ph_x := last_ph_x + last_ph := ph + last_ph_x := cur_x + +if not na(pl) + int cur_x = bar_index - i_pivot + if not na(last_pl) and not na(last_pl_x) + line.new(last_pl_x, last_pl, cur_x, pl, color=color.green) + prev_pl_y := last_pl + prev_pl_x := last_pl_x + last_pl := pl + last_pl_x := cur_x + +// ---- EMA stack regime --------------------------------------------- +float ema_fast = ta.ema(close, i_ema_fast) +float ema_mid = ta.ema(close, i_ema_mid) +float ema_slow = ta.ema(close, i_ema_slow) + +bool stack_bull = ema_fast > ema_mid and ema_mid > ema_slow +bool stack_bear = ema_fast < ema_mid and ema_mid < ema_slow + +// ---- composite gate (trend + momentum + structure) ---------------- +bool trend_bull = ema_fast > ema_mid +bool trend_bear = ema_fast < ema_mid + +float r = ta.rsi(close, i_rsi_len) +bool mom_bull = r > i_rsi_lo +bool mom_bear = r < i_rsi_hi + +// History access on chained ta.* calls hits a transpiler limitation; +// assign to a series-float carrier first, then take [1]. +float hi_now = ta.highest(high, i_break_len) +float lo_now = ta.lowest(low, i_break_len) +float break_hi = hi_now[1] +float break_lo = lo_now[1] +bool struct_bull = close > break_hi +bool struct_bear = close < break_lo + +bool gate_long = trend_bull and mom_bull and struct_bull +bool gate_short = trend_bear and mom_bear and struct_bear + +// ---- final entry signals (stack + gate + pivot anchor) ----------- +bool go_long = stack_bull and gate_long and not na(last_pl) +bool go_short = stack_bear and gate_short and not na(last_ph) + +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="integ trend long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="integ trend short") + +// ---- pivot-anchored bracket -------------------------------------- +float entry_px = strategy.position_avg_price + +if strategy.position_size > 0 and not na(last_pl) + float sl_px = last_pl + float tp_px = entry_px + (entry_px - last_pl) * i_rr + strategy.exit("Brk", from_entry="L", stop=sl_px, limit=tp_px) + +if strategy.position_size < 0 and not na(last_ph) + float sl_px = last_ph + float tp_px = entry_px - (last_ph - entry_px) * i_rr + strategy.exit("Brk", from_entry="S", stop=sl_px, limit=tp_px) diff --git a/tests/gate-corpus/ok/validation__composite-trendmaster-line-new-projection-01.pine b/tests/gate-corpus/ok/validation__composite-trendmaster-line-new-projection-01.pine new file mode 100644 index 0000000..f2dcc47 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-trendmaster-line-new-projection-01.pine @@ -0,0 +1,92 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe trendmaster-probe-01-trend-line-projection — line.new() chain stress with projection entry +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the `line.new()` drawing-object chain surface used by +// trend-line strategies that draw a fresh line on every confirmed pivot +// pair. Each new pivot connects the previous-pivot bar to the current +// pivot bar with `line.new(x1, y1, x2, y2)`, where x coordinates are +// `bar_index` offsets. `max_lines_count=500` caps the visible chain; +// older lines are auto-dropped by TV. The engine surface under test is +// (a) parsing `line.new` and the `max_lines_count` script header field, +// (b) bar_index arithmetic for line endpoints, (c) computing a slope +// projection from the most recent line and using it as an entry trigger. +// Drawing primitives have no PnL effect on TV — the entry is driven off +// the SLOPE arithmetic, which the engine must evaluate identically. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. If the engine doesn't draw +// lines but evaluates the slope projection correctly, parity still holds — +// only the slope math feeds the entry. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe trendmaster-probe-01-trend-line-projection", shorttitle="PF_TM01_LINE", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, max_lines_count=500) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength", minval=2, maxval=20) + +// ---- pivot detection ---------------------------------------------- +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +// ---- track last + previous confirmed pivot for line drawing ------ +// last_* = most recent confirmed pivot, prev_* = the one before that. +var float last_ph_y = na +var int last_ph_x = na +var float prev_ph_y = na +var int prev_ph_x = na +var float last_pl_y = na +var int last_pl_x = na +var float prev_pl_y = na +var int prev_pl_x = na + +// On confirm, ph/pl carry the pivot value; the actual swing bar is +// `i_pivot` bars back. Roll last_* into prev_* before overwriting. +if not na(ph) + int cur_x = bar_index - i_pivot + if not na(last_ph_y) and not na(last_ph_x) + line.new(last_ph_x, last_ph_y, cur_x, ph, color=color.red) + prev_ph_y := last_ph_y + prev_ph_x := last_ph_x + last_ph_y := ph + last_ph_x := cur_x + +if not na(pl) + int cur_x = bar_index - i_pivot + if not na(last_pl_y) and not na(last_pl_x) + line.new(last_pl_x, last_pl_y, cur_x, pl, color=color.green) + prev_pl_y := last_pl_y + prev_pl_x := last_pl_x + last_pl_y := pl + last_pl_x := cur_x + +// ---- slope-extrapolated reference at current bar ----------------- +float ref_lo = na +if not na(prev_pl_y) and not na(prev_pl_x) and not na(last_pl_y) and not na(last_pl_x) and last_pl_x != prev_pl_x + float slope = (last_pl_y - prev_pl_y) / (last_pl_x - prev_pl_x) + ref_lo := last_pl_y + slope * (bar_index - last_pl_x) + +float ref_hi = na +if not na(prev_ph_y) and not na(prev_ph_x) and not na(last_ph_y) and not na(last_ph_x) and last_ph_x != prev_ph_x + float slope = (last_ph_y - prev_ph_y) / (last_ph_x - prev_ph_x) + ref_hi := last_ph_y + slope * (bar_index - last_ph_x) + +// ---- entries on slope-projection cross ---------------------------- +bool long_signal = not na(ref_lo) and ta.crossover(close, ref_lo) +bool short_signal = not na(ref_hi) and ta.crossunder(close, ref_hi) + +if long_signal and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="proj long") + +if short_signal and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="proj short") diff --git a/tests/gate-corpus/ok/validation__composite-trendmaster-pivot-anchored-bracket-01.pine b/tests/gate-corpus/ok/validation__composite-trendmaster-pivot-anchored-bracket-01.pine new file mode 100644 index 0000000..5c6a996 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-trendmaster-pivot-anchored-bracket-01.pine @@ -0,0 +1,75 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe trendmaster-probe-03-pivot-tp-sl — TP/SL anchored to recent pivots, not fixed offset +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the pivot-anchored bracket surface used by trend +// strategies that risk-frame each entry against the last confirmed swing. +// SL for a long is set to the most recent pivot LOW; TP is computed as a +// 2R extrapolation from entry (`entry + (entry - last_pl) * 2`). Short is +// symmetric off the most recent pivot HIGH. The engine surface under test +// is (a) cross-bar `var float` pivot retention, (b) on-entry derivation +// of bracket prices using `strategy.position_avg_price`, (c) the +// `strategy.exit` placement of asymmetric stop/limit pairs. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. SL and TP fill prices must +// match TV exactly; any drift in pivot retention will shift both sides of +// the bracket. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe trendmaster-probe-03-pivot-tp-sl", shorttitle="PF_TM03_PVTP", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_pivot = input.int(5, "Pivot strength", minval=2, maxval=20) +float i_rr = input.float(2.0, "Reward:risk ratio", minval=0.5, maxval=10.0, step=0.5) +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) + +// ---- pivot retention ---------------------------------------------- +float ph = ta.pivothigh(high, i_pivot, i_pivot) +float pl = ta.pivotlow(low, i_pivot, i_pivot) + +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- entry trigger: simple EMA cross ----------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- entries (only when a pivot anchor exists) ------------------- +if long_signal and not na(last_pl) and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="pivot long") + +if short_signal and not na(last_ph) and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="pivot short") + +// ---- pivot-anchored bracket -------------------------------------- +float entry_px = strategy.position_avg_price + +if strategy.position_size > 0 and not na(last_pl) + float sl_px = last_pl + float tp_px = entry_px + (entry_px - last_pl) * i_rr + strategy.exit("Brk", from_entry="L", stop=sl_px, limit=tp_px) + +if strategy.position_size < 0 and not na(last_ph) + float sl_px = last_ph + float tp_px = entry_px - (last_ph - entry_px) * i_rr + strategy.exit("Brk", from_entry="S", stop=sl_px, limit=tp_px) diff --git a/tests/gate-corpus/ok/validation__composite-trendmaster-three-tier-ema-state-01.pine b/tests/gate-corpus/ok/validation__composite-trendmaster-three-tier-ema-state-01.pine new file mode 100644 index 0000000..e24374a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-trendmaster-three-tier-ema-state-01.pine @@ -0,0 +1,66 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe trendmaster-probe-02-multi-tier-ma — three-tier EMA stack with state-change entries +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the multi-tier EMA stack-state surface used by trend +// strategies. Three EMAs (21, 55, 200) define a stack: bullish when +// `ema21 > ema55 > ema200`, bearish on the symmetric inversion, neutral +// otherwise. A `var int stack_state` tracks the regime and entries fire +// only on TRANSITIONS into bull or bear (the rising edge of the state). +// The engine surface under test is (a) three concurrent `ta.ema` recursions +// at different lengths — long warmup convergence to TV, (b) state-machine +// edge detection across bars, (c) the prior-state comparison ordering +// constraint mirrored from probe 03 of the market-shift family. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe trendmaster-probe-02-multi-tier-ma", shorttitle="PF_TM02_STACK", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(21, "EMA fast", minval=3, maxval=100) +int i_mid = input.int(55, "EMA mid", minval=10, maxval=200) +int i_slow = input.int(200, "EMA slow", minval=50, maxval=500) + +// ---- three-tier EMA stack ----------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_mid = ta.ema(close, i_mid) +float ema_slow = ta.ema(close, i_slow) + +bool stack_bull = ema_fast > ema_mid and ema_mid > ema_slow +bool stack_bear = ema_fast < ema_mid and ema_mid < ema_slow + +// ---- state machine + edge detector -------------------------------- +var int stack_state = 0 +var int prev_state = 0 + +if stack_bull + stack_state := 1 +else if stack_bear + stack_state := -1 +else + stack_state := 0 + +bool long_edge = stack_state == 1 and prev_state != 1 +bool short_edge = stack_state == -1 and prev_state != -1 + +// ---- routing ------------------------------------------------------- +if long_edge and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="stack bull edge") + +if short_edge and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="stack bear edge") + +// Update prev_state AFTER comparisons + entries — last statement. +prev_state := stack_state diff --git a/tests/gate-corpus/ok/validation__composite-trendmaster-trend-momentum-structure-gate-01.pine b/tests/gate-corpus/ok/validation__composite-trendmaster-trend-momentum-structure-gate-01.pine new file mode 100644 index 0000000..ec65681 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-trendmaster-trend-momentum-structure-gate-01.pine @@ -0,0 +1,73 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe trendmaster-probe-04-trend-entry-gate — composite trend+momentum+structure gate +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the composite-gate entry surface used by trend +// strategies that require multiple confluence factors before firing. Three +// independent predicates are AND-combined: +// +// trend = ema21 > ema55 (long bias) +// momentum = ta.rsi(close, 14) > 55 (momentum confirm) +// structure = close > ta.highest(high, 20)[1] (20-bar breakout) +// +// All three must be true on the same bar. Short is the symmetric inversion. +// The engine surface under test is the boolean conjunction of three +// different ta.* recursion families on a single bar — any single drift +// (EMA, RSI, or rolling-highest) flips the gate to false and a trade goes +// missing. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe trendmaster-probe-04-trend-entry-gate", shorttitle="PF_TM04_GATE", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(21, "Trend EMA fast", minval=3, maxval=100) +int i_slow = input.int(55, "Trend EMA slow", minval=10, maxval=200) +int i_rsi_len = input.int(14, "RSI length", minval=2, maxval=50) +float i_rsi_lo = input.float(55, "RSI long threshold", minval=50, maxval=80) +float i_rsi_hi = input.float(45, "RSI short threshold", minval=20, maxval=50) +int i_break_len = input.int(20, "Breakout window", minval=5, maxval=200) + +// ---- predicate 1: trend ------------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) +bool trend_bull = ema_fast > ema_slow +bool trend_bear = ema_fast < ema_slow + +// ---- predicate 2: momentum ---------------------------------------- +float r = ta.rsi(close, i_rsi_len) +bool mom_bull = r > i_rsi_lo +bool mom_bear = r < i_rsi_hi + +// ---- predicate 3: structure (prior-bar breakout reference) ------- +// History access on chained ta.* calls hits a transpiler limitation; +// assign to a series-float carrier first, then take [1]. +float hi_now = ta.highest(high, i_break_len) +float lo_now = ta.lowest(low, i_break_len) +float break_hi = hi_now[1] +float break_lo = lo_now[1] +bool struct_bull = close > break_hi +bool struct_bear = close < break_lo + +// ---- composite gate ----------------------------------------------- +bool go_long = trend_bull and mom_bull and struct_bull +bool go_short = trend_bear and mom_bear and struct_bear + +// ---- routing ------------------------------------------------------- +if go_long and strategy.position_size <= 0 + strategy.entry("L", strategy.long, qty=1, comment="gate long") + +if go_short and strategy.position_size >= 0 + strategy.entry("S", strategy.short, qty=1, comment="gate short") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-cumulative-volume-delta-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-cumulative-volume-delta-01.pine new file mode 100644 index 0000000..e50d625 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-cumulative-volume-delta-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 05 — candle-position volume delta + cumulative sum +// +// Purpose: isolate the cumulative-volume-delta half of the VCP integration +// probe family. That family synthesises buy/sell volume from candle position +// with a tiny +// epsilon in the denominator (``high - low + 0.0001``) to avoid +// division-by-zero on doji bars, computes the per-bar delta, and +// then runs a 10-bar rolling ``math.sum`` on top. ``cumDelta`` then +// gates ``volumeBullish`` / ``volumeBearish`` (which feed the volume +// confluence). Two candidate sources of TV/engine drift here: +// (1) the doji-epsilon arithmetic on bars where ``high == low`` +// (extremely rare on ETH 15m but possible); +// (2) the rolling ``math.sum`` window precision over 10 bars. +// +// This probe routes a trivial entry/exit through ONLY the cumulative +// delta. Any TV/engine drift here is attributable to candle-position +// buy/sell synthesis and the 10-bar ``math.sum``, NOT to VCP's other +// filters. +// +// Trade shape: long-only, pyramiding=1. Enter long on cumDelta crossing +// ABOVE zero (was <= 0 last bar, > 0 this bar). Close on cumDelta +// crossing BELOW zero. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 05 - cumulative vol delta", shorttitle="VCP_p05", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- buy/sell volume — IDENTICAL to VCP (incl. 0.0001 epsilon) ---- +float buyVolume = close > open ? volume : volume * (close - low) / (high - low + 0.0001) +float sellVolume = close < open ? volume : volume * (high - close) / (high - low + 0.0001) +float volumeDelta = buyVolume - sellVolume + +// ---- 10-bar cumulative delta (matches VCP line 309) ---------------- +float cumDelta = math.sum(volumeDelta, 10) + +bool crossUp = cumDelta > 0 and cumDelta[1] <= 0 +bool crossDown = cumDelta < 0 and cumDelta[1] >= 0 + +// ---- trivial entry / exit ------------------------------------------ +if crossUp and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="cumDelta cross-up") + +if crossDown and strategy.position_size > 0 + strategy.close("L", comment="cumDelta cross-down exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-fvg-active-zones-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-fvg-active-zones-01.pine new file mode 100644 index 0000000..eb626e0 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-fvg-active-zones-01.pine @@ -0,0 +1,96 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 02 — Fair Value Gap detection + active-zone iteration +// +// Purpose: isolate the FVG half of the VCP integration probe family. That +// family defines an FVG as a 3-bar imbalance gated by an ATR-fraction +// minimum size, then keeps every active gap in a parallel +// ``array`` / ``array`` triple (tops / bottoms / bullish +// flag) capped at 30. On every bar it iterates ALL active zones to +// compute ``inBullFVG`` / ``inBearFVG`` for the current candle. That zone +// iteration is what gates VCP's structure-confluence bonus (``+1`` to +// structScore when in zone + matching structureDirection). +// +// This probe replicates the FVG state machine VERBATIM — same gap +// conditions, same ATR multiplier (0.3), same array retention, +// same per-bar containment loop — and routes a trivial entry/exit +// through it. Any TV/engine drift here is attributable to FVG state +// management (push/shift cap, per-bar containment loop, atr-mult +// minimum size) and NOT to the rest of VCP's stack. +// +// Trade shape: long-only, pyramiding=1. Enter long when the current +// bar's HL range overlaps an active BULL FVG (i.e. ``inBullFVG`` +// becomes true while flat). Close the long when an active BEAR FVG +// is touched. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 02 - fvg zones", shorttitle="VCP_p02", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + max_boxes_count=300) + +// ---- ATR (literal length 14, matching VCP line 187) ---------------- +float atrVal = ta.atr(14) + +// ---- FVG detection — IDENTICAL conditions to VCP ------------------- +bool bullFVG = low > high[2] and close[1] > open[1] +bool bearFVG = high < low[2] and close[1] < open[1] + +float minFVGSize = atrVal * 0.3 + +// ---- FVG storage (mirror of VCP's parallel arrays) ----------------- +var array fvgTops = array.new() +var array fvgBottoms = array.new() +var array fvgBullish = array.new() + +float fvgTop = na +float fvgBottom = na + +if bullFVG and (low - high[2]) >= minFVGSize + fvgTop := low + fvgBottom := high[2] + array.push(fvgTops, fvgTop) + array.push(fvgBottoms, fvgBottom) + array.push(fvgBullish, true) + +if bearFVG and (low[2] - high) >= minFVGSize + fvgTop := low[2] + fvgBottom := high + array.push(fvgTops, fvgTop) + array.push(fvgBottoms, fvgBottom) + array.push(fvgBullish, false) + +while array.size(fvgTops) > 30 + array.shift(fvgTops) + array.shift(fvgBottoms) + array.shift(fvgBullish) + +// ---- per-bar containment loop — IDENTICAL pattern to VCP ----------- +bool inBullFVG = false +bool inBearFVG = false + +if array.size(fvgTops) > 0 + for i = 0 to array.size(fvgTops) - 1 + float fTop = array.get(fvgTops, i) + float fBottom = array.get(fvgBottoms, i) + bool isBull = array.get(fvgBullish, i) + + if low <= fTop and high >= fBottom + if isBull + inBullFVG := true + else + inBearFVG := true + +// ---- trivial entry / exit ------------------------------------------ +if inBullFVG and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="bull-fvg touch") + +if inBearFVG and strategy.position_size > 0 + strategy.close("L", comment="bear-fvg touch exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-integration-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-integration-01.pine new file mode 100644 index 0000000..1d1ab7a --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-integration-01.pine @@ -0,0 +1,195 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 08 — integration probe (pivot + FVG + RSI divergence + vol +// z-anomaly + cumulative delta + ADX regime + NY session, +// composed in a clean-room layered design) +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the surfaces already isolated in vcp-probe-01..07 in +// a single strategy so engine/TV agreement on each component probe can +// be cross-checked against agreement on a representative composition. +// The seven layers exercised here are: +// (1) pivot — ta.pivothigh / ta.pivotlow at strength 5 (probe 01). +// (2) FVG — 3-bar imbalance + ATR-fraction min size + active-zone +// array iteration (probe 02). +// (3) RSI div — smoothed RSI(14)→EMA(3) with 5-bar memory (probe 03). +// (4) vol z — (volume - sma) / stdev anomaly threshold (probe 04). +// (5) cum delta — candle-position buy/sell volume + 10-bar sum (probe 05). +// (6) ADX regime — manual DI/ADX chain via ta.rma (probe 06). +// (7) session — NY 0800-1600 with DST-aware tz (probe 07). +// Long entry requires (a) confirmed pivot LOW carried as breakout level +// AND close > carried pivot HIGH, (b) currently inside a bull FVG zone, +// (c) bullish vol-z anomaly OR cum-delta cross-up, (d) trending bull +// ADX regime, (e) NY session active. Short symmetric. The composition +// is the author's; the per-layer math mirrors the surfaces isolated by +// the component probes (NOT the original VCP source). +// +// Validation goal: integration parity. If probes 01–07 each pass +// individually but this composition disagrees with TradingView, the +// gap is attributable to interaction between the layers (state ordering, +// per-bar containment loops touching arrays under load, ta-on-ta +// seeding) rather than any single layer. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("VCP probe 08 - integration", shorttitle="VCP_p08_INT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, + max_boxes_count=300) + +// ===================================================================== +// inputs (one per layer; defaults match component probes) +// ===================================================================== +int i_pivot = input.int(5, "Pivot strength", minval=2, maxval=20) +float i_fvg_atr = input.float(0.3,"FVG min size (atr fraction)", minval=0.05,maxval=2.0, step=0.05) +int i_rsi_len = input.int(14,"RSI length", minval=5, maxval=30) +int i_vol_ma_len = input.int(20,"Volume MA length", minval=5, maxval=50) +float i_vol_z = input.float(2.0,"Volume z threshold", minval=1.0, maxval=4.0, step=0.1) +int i_cd_sum = input.int(10,"Cum-delta window", minval=2, maxval=50) +int i_adx_len = input.int(14,"ADX length", minval=5, maxval=30) +float i_adx_thr = input.float(25,"ADX trend threshold", minval=15, maxval=40) +string i_session = input.session("0800-1600", "Active session") +string i_tz = input.string("America/New_York", "Timezone", + options=["America/New_York", "Europe/London", "Asia/Tokyo", "UTC"]) + +// ===================================================================== +// shared building blocks +// ===================================================================== +float atr_v = ta.atr(14) + +// ===================================================================== +// layer 1 — pivot detection + carried last-confirmed levels +// ===================================================================== +float ph_v = ta.pivothigh(high, i_pivot, i_pivot) +float pl_v = ta.pivotlow(low, i_pivot, i_pivot) + +var float last_ph = na +var float last_pl = na +if not na(ph_v) + last_ph := ph_v +if not na(pl_v) + last_pl := pl_v + +bool pivot_break_up = not na(last_ph) and close > last_ph +bool pivot_break_dn = not na(last_pl) and close < last_pl + +// ===================================================================== +// layer 2 — FVG zones (parallel array trio + per-bar containment loop) +// ===================================================================== +bool bull_fvg_event = low > high[2] and close[1] > open[1] +bool bear_fvg_event = high < low[2] and close[1] < open[1] +float fvg_min_w = atr_v * i_fvg_atr + +var array z_top = array.new() +var array z_bot = array.new() +var array z_isb = array.new() + +if bull_fvg_event and (low - high[2]) >= fvg_min_w + array.push(z_top, low) + array.push(z_bot, high[2]) + array.push(z_isb, true) + +if bear_fvg_event and (low[2] - high) >= fvg_min_w + array.push(z_top, low[2]) + array.push(z_bot, high) + array.push(z_isb, false) + +while array.size(z_top) > 30 + array.shift(z_top) + array.shift(z_bot) + array.shift(z_isb) + +bool in_bull_fvg = false +bool in_bear_fvg = false +if array.size(z_top) > 0 + for k = 0 to array.size(z_top) - 1 + float t = array.get(z_top, k) + float b = array.get(z_bot, k) + bool s = array.get(z_isb, k) + if low <= t and high >= b + if s + in_bull_fvg := true + else + in_bear_fvg := true + +// ===================================================================== +// layer 3 — smoothed-RSI divergence (kept as a bias hint, not required) +// ===================================================================== +float rsi_v = ta.rsi(close, i_rsi_len) +float rsi_smooth = ta.ema(rsi_v, 3) +bool rsi_div_bull = rsi_smooth < 40 and close > close[5] and rsi_smooth > rsi_smooth[5] +bool rsi_div_bear = rsi_smooth > 60 and close < close[5] and rsi_smooth < rsi_smooth[5] + +// ===================================================================== +// layer 4 — volume z-score anomaly +// ===================================================================== +float vol_ma = ta.sma(volume, i_vol_ma_len) +float vol_std = ta.stdev(volume, 20) +float vol_z = vol_std > 0 ? (volume - vol_ma) / vol_std : 0 +bool vol_anom_bull = math.abs(vol_z) > i_vol_z and close > open +bool vol_anom_bear = math.abs(vol_z) > i_vol_z and close < open + +// ===================================================================== +// layer 5 — cumulative volume delta (candle-position synthesis + sum) +// ===================================================================== +float buy_vol = close > open ? volume : volume * (close - low) / (high - low + 0.0001) +float sell_vol = close < open ? volume : volume * (high - close) / (high - low + 0.0001) +float vol_d = buy_vol - sell_vol +float cum_d = math.sum(vol_d, i_cd_sum) +bool cd_up = cum_d > 0 +bool cd_dn = cum_d < 0 + +// ===================================================================== +// layer 6 — manual ADX regime (matches probe 06) +// ===================================================================== +float up_mv = ta.change(high) +float dn_mv = -ta.change(low) +float p_dm_v = na(up_mv) ? na : (up_mv > dn_mv and up_mv > 0 ? up_mv : 0) +float m_dm_v = na(dn_mv) ? na : (dn_mv > up_mv and dn_mv > 0 ? dn_mv : 0) +float tr_smo = ta.rma(ta.tr, i_adx_len) +float p_di_v = tr_smo > 0 ? 100 * ta.rma(p_dm_v, i_adx_len) / tr_smo : 0 +float m_di_v = tr_smo > 0 ? 100 * ta.rma(m_dm_v, i_adx_len) / tr_smo : 0 +float dx_v = (p_di_v + m_di_v) > 0 ? 100 * math.abs(p_di_v - m_di_v) / (p_di_v + m_di_v) : 0 +float adx_v = ta.rma(dx_v, i_adx_len) +bool trending_bull = adx_v > i_adx_thr and p_di_v > m_di_v +bool trending_bear = adx_v > i_adx_thr and m_di_v > p_di_v + +// ===================================================================== +// layer 7 — NY session gate +// ===================================================================== +bool in_session = not na(time(timeframe.period, i_session, i_tz)) + +// ===================================================================== +// composition — long requires pivot break up + bull fvg + (vol-anomaly +// bull OR cum-delta up) + trending bull + session active. Short symmetric. +// rsi divergences feed nothing here on purpose (kept as a tap point in +// case a future revision wants to gate on them). +// ===================================================================== +bool long_setup = + pivot_break_up and in_bull_fvg and (vol_anom_bull or cd_up) + and trending_bull and in_session + +bool short_setup = + pivot_break_dn and in_bear_fvg and (vol_anom_bear or cd_dn) + and trending_bear and in_session + +if long_setup and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="vcp confluence long") + +if short_setup and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="vcp confluence short") + +// session-end exit: flatten on the falling edge of the session window +if not in_session and in_session[1] and strategy.position_size != 0 + strategy.close_all(comment="session end") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-manual-adx-regime-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-manual-adx-regime-01.pine new file mode 100644 index 0000000..e8da559 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-manual-adx-regime-01.pine @@ -0,0 +1,73 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 06 — manual ADX regime detection +// +// Purpose: isolate the regime-filter half of the VCP integration probe +// family. That family does NOT use ``ta.dmi`` — it builds DI/ADX BY HAND +// from ``ta.change(high)`` / +// ``ta.change(low)`` plus a chain of ``ta.rma`` smoothings on plusDM / +// minusDM / true range / dx. That manual chain is a likely source of +// TV/engine drift because: +// (1) ``ta.change(high)`` produces ``na`` on bar 0; the plusDM / +// minusDM ternaries explicitly carry the ``na`` (``na(upMove) ? +// na : ...``); +// (2) ``ta.rma`` is Wilder's smoothing (alpha = 1/length), which is +// seeded differently across implementations (some use SMA seed, +// some use first-non-na, some use 0); +// (3) the ``adxValue = ta.rma(dx, adxLen)`` final pass adds another +// length of warm-up, so any seed drift compounds. +// VCP then derives ``regime.isTrending = adxValue > 25`` and +// ``regime.trend = plusDI > minusDI ? 1 : -1`` and uses both to gate +// long/short entries via ``regimeAllowsLong`` / ``regimeAllowsShort``. +// +// This probe routes a trivial entry/exit through ONLY VCP's manual +// ADX. Any TV/engine drift here is attributable to the +// ``ta.rma``-of-``ta.tr`` / DI / DX / ADX chain at length 14, NOT +// to other VCP filters. +// +// Trade shape: long-only, pyramiding=1. Enter long on the bar that +// regime FIRST becomes "trending bull" (``isTrending and trend > 0`` +// rising-edge). Close the long when the regime exits "trending bull". +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 06 - adx regime", shorttitle="VCP_p06", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- ATR for the volatility ratio (literal 14, matches VCP) ------- +float atrVal = ta.atr(14) + +// ---- Manual ADX — IDENTICAL formula to VCP lines 419-428 ---------- +int adxLen = 14 +float upMove = ta.change(high) +float downMove = -ta.change(low) +float plusDM = na(upMove) ? na : (upMove > downMove and upMove > 0 ? upMove : 0) +float minusDM = na(downMove) ? na : (downMove > upMove and downMove > 0 ? downMove : 0) +float trueRange = ta.rma(ta.tr, adxLen) +float plusDI = 100 * ta.rma(plusDM, adxLen) / trueRange +float minusDI = 100 * ta.rma(minusDM, adxLen) / trueRange +float dx = 100 * math.abs(plusDI - minusDI) / (plusDI + minusDI) +float adxValue = ta.rma(dx, adxLen) + +bool isTrending = adxValue > 25 +int trend = plusDI > minusDI ? 1 : -1 +float volatility = atrVal / close * 100 + +// ---- regime rising / falling edge ---------------------------------- +bool trendingBull = isTrending and trend > 0 +bool regimeBullStart = trendingBull and not trendingBull[1] +bool regimeBullEnd = not trendingBull and trendingBull[1] + +// ---- trivial entry / exit ------------------------------------------ +if regimeBullStart and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="adx trending-bull start") + +if regimeBullEnd and strategy.position_size > 0 + strategy.close("L", comment="adx trending-bull end exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-pivot-strength-5-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-pivot-strength-5-01.pine new file mode 100644 index 0000000..b265a76 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-pivot-strength-5-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 01 — pivot detection at strength 5 +// +// Purpose: isolate the market-structure half of the VCP integration probe +// family. That family detects +// swing highs/lows with ``ta.pivothigh(high, pivotStrength, pivotStrength)`` +// and ``ta.pivotlow(low, pivotStrength, pivotStrength)`` at the default +// ``pivotStrength = 5``. Those pivots seed BOS / CHoCH structure flips, +// which then gate every entry. If TV and the engine disagree on WHEN a +// strength-5 pivot confirms (or its value), VCP's structureDirection +// flips on different bars and the whole confluence stack desyncs. +// +// This probe routes a trivial entry/exit through ONLY the pivot pair — +// no FVG, no MTF, no RSI, no volume — so any TV/engine drift here is +// attributable to ``ta.pivothigh`` / ``ta.pivotlow`` left-bar+right-bar +// confirmation precision at strength 5. +// +// Trade shape: long-only, pyramiding=1. Enter long on the bar that +// confirms a strength-5 pivot LOW (buy-the-dip on the confirmation +// bar). Close the long on the next confirmed strength-5 pivot HIGH. +// Both are computed with the EXACT VCP pivot signature. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 01 - pivot strength 5", shorttitle="VCP_p01", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs (mirror VCP's defaults) -------------------------------- +int i_pivot = input.int(5, "Pivot Strength", minval=2, maxval=20) + +// ---- pivot detection — IDENTICAL signature to VCP ------------------ +float pivotHigh = ta.pivothigh(high, i_pivot, i_pivot) +float pivotLow = ta.pivotlow(low, i_pivot, i_pivot) + +// Confirmation events: na/non-na rising edges. ta.pivothigh returns +// the pivot price ``i_pivot`` bars AFTER the actual swing bar (i.e. +// once enough right-bars exist). VCP keys structureDirection off +// exactly these events. +bool pivotHighConfirmed = not na(pivotHigh) +bool pivotLowConfirmed = not na(pivotLow) + +// ---- trivial entry / exit ------------------------------------------ +if pivotLowConfirmed and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="pivot low entry") + +if pivotHighConfirmed and strategy.position_size > 0 + strategy.close("L", comment="pivot high exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-rsi-smooth-divergence-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-rsi-smooth-divergence-01.pine new file mode 100644 index 0000000..c55ed93 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-rsi-smooth-divergence-01.pine @@ -0,0 +1,57 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 03 — smoothed-RSI divergence detection +// +// Purpose: isolate the momentum half of the VCP integration probe family. +// That family smooths RSI through an EMA(3) and then derives both +// ``momBullish`` / ``momBearish`` regime flags AND a divergence pair +// ``rsiDivBull`` / ``rsiDivBear`` keyed off ``close[5]`` and +// ``rsiSmooth[5]``. That 5-bar history-reference of an EMA-of-RSI is the +// most-likely place for a numerical drift to creep in — even a single +// epsilon error in the EMA(3) seed shifts every divergence event by a bar. +// +// This probe routes a trivial entry/exit through ONLY the smoothed-RSI +// divergence pair, with the EXACT VCP expressions and the VCP defaults +// (``rsiLength = 14``, EMA smoothing length 3, divergence lookback 5, +// ``rsiOverbought = 70`` / ``rsiOversold = 30`` not used here — only +// the 60/40 divergence thresholds). Any TV/engine drift here is +// attributable to ``ta.ema(ta.rsi(close, 14), 3)`` precision and the +// 5-bar history references the divergence formula uses, NOT to other +// VCP filters. +// +// Trade shape: long-only, pyramiding=1. Enter long on bullish RSI +// divergence (smoothed RSI < 40 with rising RSI vs falling 5-bar +// price reference, EXACT VCP wording flipped to a ``close > close[5]`` +// constructive divergence). Close the long on bearish RSI divergence +// (smoothed RSI > 60 with falling RSI vs rising 5-bar price reference, +// VCP's exact ``rsiDivBear``). +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 03 - rsi smooth divergence", shorttitle="VCP_p03", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs (mirror VCP defaults) ---------------------------------- +int i_rsi = input.int(14, "RSI Length", minval=5, maxval=30) + +// ---- smoothed RSI — IDENTICAL formula to VCP ----------------------- +float rsiValue = ta.rsi(close, i_rsi) +float rsiSmooth = ta.ema(rsiValue, 3) + +// ---- divergence flags — IDENTICAL expressions to VCP -------------- +bool rsiDivBull = rsiSmooth < 40 and close > close[5] and rsiSmooth > rsiSmooth[5] +bool rsiDivBear = rsiSmooth > 60 and close < close[5] and rsiSmooth < rsiSmooth[5] + +// ---- trivial entry / exit ------------------------------------------ +if rsiDivBull and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="rsi div bull") + +if rsiDivBear and strategy.position_size > 0 + strategy.close("L", comment="rsi div bear exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-session-tz-newyork-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-session-tz-newyork-01.pine new file mode 100644 index 0000000..ba242cf --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-session-tz-newyork-01.pine @@ -0,0 +1,56 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 07 — session/timezone gate (America/New_York 0800-1600) +// +// Purpose: isolate the session-filter half of the VCP integration probe +// family. That family gates +// every entry on ``inActiveSession = not na(time(timeframe.period, +// "0800-1600", "America/New_York"))``. That single ``time()`` call +// folds two independent sources of TV/engine drift into one boolean: +// (1) the session-string parser ("0800-1600" must include 16:00:00 +// UTC of America/New_York for the right window of bars); +// (2) the timezone resolver — DST transitions mean the wall-clock +// 08:00 maps to UTC 12:00 (EST) or 13:00 (EDT) depending on +// the date, so any engine that hardcodes a single offset will +// drift on the daylight-saving boundaries inside the OHLCV +// window. +// +// This probe routes a trivial entry/exit through ONLY the session +// gate. Any TV/engine drift here is attributable to the +// ``time(timeframe.period, "0800-1600", "America/New_York")`` call +// (session-string parsing + DST-aware timezone offset), NOT to other +// VCP filters. +// +// Trade shape: long-only, pyramiding=1. Enter long on the rising edge +// of the NY session (``inSession`` true this bar, false last bar). +// Close the long on the falling edge (session ends at NY 16:00). +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 07 - session ny", shorttitle="VCP_p07", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs (mirror VCP defaults) ---------------------------------- +string i_session = input.session("0800-1600", "Active Trading Session") +string i_tz = input.string("America/New_York", "Timezone", + options=["America/New_York", "Europe/London", "Asia/Tokyo", "UTC"]) + +// ---- session gate — IDENTICAL call to VCP line 413 ---------------- +bool inSession = not na(time(timeframe.period, i_session, i_tz)) + +bool sessionStart = inSession and not inSession[1] +bool sessionEnd = not inSession and inSession[1] + +// ---- trivial entry / exit ------------------------------------------ +if sessionStart and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="ny session open") + +if sessionEnd and strategy.position_size > 0 + strategy.close("L", comment="ny session close exit") diff --git a/tests/gate-corpus/ok/validation__composite-vcp-vol-zscore-anomaly-01.pine b/tests/gate-corpus/ok/validation__composite-vcp-vol-zscore-anomaly-01.pine new file mode 100644 index 0000000..d6c8c74 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-vcp-vol-zscore-anomaly-01.pine @@ -0,0 +1,52 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// VCP probe 04 — volume Z-score anomaly +// +// Purpose: isolate the volume-anomaly half of the VCP integration probe +// family. That family computes +// ``volZScore = (volume - volMA) / volStd`` where ``volMA = ta.sma(volume, 20)`` +// and ``volStd = ta.stdev(volume, 20)``. ``volAnomaly = math.abs(volZScore) > 2`` +// then feeds the volume confluence as a ``+1`` bonus AND multiplies the +// smart-money score by 1.5. A small drift in ``ta.stdev`` (e.g. sample-vs- +// population denominator, or order of summation) shifts the |z|>2 threshold +// crossings and changes WHICH bars trip the anomaly bonus. +// +// This probe routes a trivial entry/exit through ONLY the volume z-score +// anomaly. Any TV/engine drift here is attributable to ``ta.sma(volume, 20)`` +// + ``ta.stdev(volume, 20)`` precision at a length matching VCP, NOT to +// VCP's other filters. +// +// Trade shape: long-only, pyramiding=1. Enter long on a high-volume +// bullish anomaly (|z|>2 on an up-bar, ``close > open``). Close the +// long on a high-volume bearish anomaly (|z|>2 on a down-bar). +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("VCP probe 04 - vol zscore anomaly", shorttitle="VCP_p04", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs (mirror VCP defaults) ---------------------------------- +int i_vol_ma = input.int(20, "Volume MA Length", minval=5, maxval=50) + +// ---- volume Z-score — IDENTICAL formula to VCP --------------------- +// Note: VCP hardcodes the stdev length to 20 (line 312). The MA length +// is also 20 by default. Both passes use the same lookback so ``volMA`` +// and ``volStd`` are aligned. +float volMA = ta.sma(volume, i_vol_ma) +float volStd = ta.stdev(volume, 20) +float volZ = (volume - volMA) / volStd +bool volAnomaly = math.abs(volZ) > 2.0 + +// ---- trivial entry / exit ------------------------------------------ +if volAnomaly and close > open and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="vol-z anomaly bull") + +if volAnomaly and close < open and strategy.position_size > 0 + strategy.close("L", comment="vol-z anomaly bear exit") diff --git a/tests/gate-corpus/ok/validation__composite-wunderscalper-alert-templates-01.pine b/tests/gate-corpus/ok/validation__composite-wunderscalper-alert-templates-01.pine new file mode 100644 index 0000000..37fe342 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-wunderscalper-alert-templates-01.pine @@ -0,0 +1,56 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe wunder-scalper-probe-02-alert-message-templates — alert() with TV placeholder strings +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the `alert(...)` hook surface that WunderTrading-style +// bots use to forward signals to an external broker. Entries are driven by +// a short-period EMA cross; on each cross the script also emits an +// `alert("...", alert.freq_once_per_bar)` call carrying TV-side placeholder +// strings (`{{strategy.order.action}}`, `{{ticker}}`). These placeholders +// are TV broker substitutions — they do not affect the trade list or the +// engine's order routing. If the engine doesn't process `alert()` at all, +// trade-list parity still holds because alerts and orders are independent +// surfaces. The probe's value is to confirm the engine PARSES `alert()` +// without erroring and produces the same orders as a script without the +// alert calls. +// +// Caveat: TV-side template substitution (`{{...}}`) is performed by the +// alert delivery system, not by Pine. The engine is not expected to +// substitute these strings — only to accept the literal text and not raise. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Trade list parity is the +// pass criterion; alert message contents are out of scope. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe wunder-scalper-probe-02-alert-message-templates", shorttitle="PF_WS02_ALRT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) + +// ---- direction signal --------------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- entries + alert hooks ---------------------------------------- +if long_signal + strategy.entry("L", strategy.long, qty=1, comment="alert long") + alert("{{strategy.order.action}} {{ticker}} long", alert.freq_once_per_bar) + +if short_signal + strategy.entry("S", strategy.short, qty=1, comment="alert short") + alert("{{strategy.order.action}} {{ticker}} short", alert.freq_once_per_bar) diff --git a/tests/gate-corpus/ok/validation__composite-wunderscalper-explicit-reverse-01.pine b/tests/gate-corpus/ok/validation__composite-wunderscalper-explicit-reverse-01.pine new file mode 100644 index 0000000..ab0fc20 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-wunderscalper-explicit-reverse-01.pine @@ -0,0 +1,53 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe wunder-scalper-probe-01-reverse-on-signal — explicit close-then-entry reverse logic +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolates the explicit reverse-position pattern used by +// WunderTrading-style scalp bots. On a long signal, if the position is +// currently short, the script first calls `strategy.close_all()` and then +// `strategy.entry("L", strategy.long)` — same bar, two distinct orders +// rather than the single auto-reversing `strategy.entry`. The engine +// surface under test is the same-bar `close_all → entry` ordering: TV +// generates a closing trade for the short followed by an opening trade +// for the long, both at the next-bar open by default. Any disagreement +// in trade list length or fill price points to engine reverse-handling +// drift. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Each reversal must show as +// two trades (close + open), not a single auto-reverse trade. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe wunder-scalper-probe-01-reverse-on-signal", shorttitle="PF_WS01_REV", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) + +// ---- direction signal --------------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- explicit reverse routing ------------------------------------- +if long_signal + if strategy.position_size < 0 + strategy.close_all(comment="reverse close short") + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if short_signal + if strategy.position_size > 0 + strategy.close_all(comment="reverse close long") + strategy.entry("S", strategy.short, qty=1, comment="open short") diff --git a/tests/gate-corpus/ok/validation__composite-wunderscalper-integration-01.pine b/tests/gate-corpus/ok/validation__composite-wunderscalper-integration-01.pine new file mode 100644 index 0000000..b89d7a3 --- /dev/null +++ b/tests/gate-corpus/ok/validation__composite-wunderscalper-integration-01.pine @@ -0,0 +1,53 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe wunder-scalper-probe-integration — explicit reverse routing + alert hooks +// Clean-room: written from validation goal, not from source. +// +// Purpose: composes the two surfaces isolated by `wunder-scalper-probe-01..02` +// into a single end-to-end WunderTrading-style scalp strategy. The engine +// surface under test is the cross-surface composition: every cross signal +// triggers (a) explicit `strategy.close_all()` if positioned the wrong way +// followed by `strategy.entry(...)` to open new direction, AND (b) an +// `alert(...)` with TV-side placeholder strings. The engine must reproduce +// the two-trade reversal pattern AND parse the alert calls without raising. +// Alert message content is not validated. +// +// Validation goal: bit-exact trade list against TradingView's broker emulator +// on the reference 15-minute ETH-USDT-USDT feed. Trade list parity is the +// pass criterion. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full window +// matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy properties: +// leave defaults from script header. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe wunder-scalper-probe-integration", shorttitle="PF_WSINT", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(5, "Fast EMA", minval=2, maxval=50) +int i_slow = input.int(13, "Slow EMA", minval=3, maxval=100) + +// ---- direction signal --------------------------------------------- +float ema_fast = ta.ema(close, i_fast) +float ema_slow = ta.ema(close, i_slow) + +bool long_signal = ta.crossover(ema_fast, ema_slow) +bool short_signal = ta.crossunder(ema_fast, ema_slow) + +// ---- explicit reverse routing + alert hooks ----------------------- +if long_signal + if strategy.position_size < 0 + strategy.close_all(comment="integ reverse close short") + strategy.entry("L", strategy.long, qty=1, comment="integ open long") + alert("{{strategy.order.action}} {{ticker}} long", alert.freq_once_per_bar) + +if short_signal + if strategy.position_size > 0 + strategy.close_all(comment="integ reverse close long") + strategy.entry("S", strategy.short, qty=1, comment="integ open short") + alert("{{strategy.order.action}} {{ticker}} short", alert.freq_once_per_bar) diff --git a/tests/gate-corpus/ok/validation__input-source-runtime-override-high-01.pine b/tests/gate-corpus/ok/validation__input-source-runtime-override-high-01.pine new file mode 100644 index 0000000..3cb5951 --- /dev/null +++ b/tests/gate-corpus/ok/validation__input-source-runtime-override-high-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe — input.source RUNTIME OVERRIDE (#23 / codegen #9) +// Purpose: parity test for changing the price series fed to an indicator at +// RUN TIME via the input panel (not the script default). The script defaults +// the source to `close`; the probe overrides the "Source" input to `high`. +// Pre-fix the engine routed input.source to get_input_double, where the +// override string ("high") failed std::stod and silently fell back to close — +// so the override was ignored. Post-fix get_input_source resolves "high" to +// the native high series, matching TV when the input is set to High. +// +//@version=6 +strategy("input.source runtime override", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +src = input.source(close, "Source") +fastLen = input.int(10, "Fast Length", minval=2) +slowLen = input.int(30, "Slow Length", minval=5) + +// === MAs computed on the (overridable) source === +fast = ta.sma(src, fastLen) +slow = ta.sma(src, slowLen) + +// === Signals === +if ta.crossover(fast, slow) + strategy.entry("Long", strategy.long) +if ta.crossunder(fast, slow) + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__input-source-subscript-hl2-01.pine b/tests/gate-corpus/ok/validation__input-source-subscript-hl2-01.pine new file mode 100644 index 0000000..aea8c36 --- /dev/null +++ b/tests/gate-corpus/ok/validation__input-source-subscript-hl2-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe — input.source HISTORY SUBSCRIPT (#23 / codegen #9) +// Purpose: parity test for subscripting an input.source variable (src[1], +// src[2]). Pre-fix input.source was a scalar double, so src[1] either +// compile-failed or froze; post-fix the analyzer tracks the subscripted +// source var as a Series and codegen pushes the resolved source's current +// value, so src[k] is a real history read. Defaults to hl2 to also exercise +// a derived native source (_src_hl2_). +// +//@version=6 +strategy("input.source history subscript", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Input: derived native source, read with history === +src = input.source(hl2, "Source") + +// Two-bar momentum turn detector — uses src[0], src[1], src[2]. +mom = src - src[1] +momPrev = src[1] - src[2] + +// Long when momentum turns up; short when it turns down. +if mom > 0 and momPrev <= 0 + strategy.entry("Long", strategy.long) +if mom < 0 and momPrev >= 0 + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__ltf-bool-array-bull-majority-01.pine b/tests/gate-corpus/ok/validation__ltf-bool-array-bull-majority-01.pine new file mode 100644 index 0000000..861b92f --- /dev/null +++ b/tests/gate-corpus/ok/validation__ltf-bool-array-bull-majority-01.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Lower-TF probe 02 — bool array element type +// +// Purpose: ``request.security_lower_tf`` returning ``array``. +// Asserts the LTF synthesis path handles non-numeric element types +// (bool storage / count_true reduce). On 15m we pull 1m bars and tag +// each as bullish (close>open); entry triggers when at least 9 of 15 +// sub-bars are bullish — i.e. >60% intrabar bull dominance. +// +// Trade shape: long-only. Entry on bull-bar majority + EMA filter; exit +// on bull-majority break (count <= 6). Yields ~20-50 trades on 36k bar +// ETH-USDT 15m window. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export trade list to tv_trades.csv. +//@version=6 +strategy("PF lower-tf probe 02 - bool array", shorttitle="LTF_p02_BOOL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// 1m bull-bar flags from 15m chart. +ltfBullArr = request.security_lower_tf(syminfo.tickerid, "1", close > open) + +int bullCount = 0 +if array.size(ltfBullArr) > 0 + for i = 0 to array.size(ltfBullArr) - 1 + if array.get(ltfBullArr, i) + bullCount := bullCount + 1 + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = bullCount >= 9 and emaFast > emaSlow +bool exitCond = bullCount <= 6 or emaFast < emaSlow + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="bull-majority") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="bull-broken") diff --git a/tests/gate-corpus/ok/validation__ltf-numeric-float-ratio15-01.pine b/tests/gate-corpus/ok/validation__ltf-numeric-float-ratio15-01.pine new file mode 100644 index 0000000..7610d3c --- /dev/null +++ b/tests/gate-corpus/ok/validation__ltf-numeric-float-ratio15-01.pine @@ -0,0 +1,49 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Lower-TF probe 01 — numeric float array, ratio=15 (15m -> 1m) +// +// Purpose: smallest acceptance probe for ``request.security_lower_tf`` +// returning ``array``. On a 15m chart we pull 1-minute closes +// (15 sub-bars per chart bar) and reduce by sum to a synthetic +// "intra-bar momentum" that an EMA cross gates entries on. Drives the +// LTF sub-bar-index synthesis path and the numeric-array reduce parity +// vs script-side computations. +// +// Trade shape: long-only. Entry when EMA9>EMA21 AND the sum of the 15 +// sub-bar closes diverges from 15*close (i.e. intrabar drift is +// non-trivial). Exit on EMA cross-under. Produces ~15-40 trades per +// 36k-bar window depending on ETH volatility regime. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export trade list to tv_trades.csv. +//@version=6 +strategy("PF lower-tf probe 01 - float ratio15", shorttitle="LTF_p01_F15", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// LTF call: 1-minute closes from 15m chart -> array length 15. +ltfCloses = request.security_lower_tf(syminfo.tickerid, "1", close) + +// Reduce sum-of-LTF closes; null-safe via array.size guard. +float ltfSum = 0.0 +if array.size(ltfCloses) > 0 + for i = 0 to array.size(ltfCloses) - 1 + ltfSum := ltfSum + array.get(ltfCloses, i) + +// Synthetic drift: how far the sub-bar sum is from 15*close. +float drift = ltfSum - 15.0 * close + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and math.abs(drift) > close * 0.001 +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="ltf-entry") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="ltf-exit") diff --git a/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-01.pine b/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-01.pine new file mode 100644 index 0000000..36b2e7c --- /dev/null +++ b/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-01.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Magnifier distribution probe 01-endpoints — ENDPOINTS +// +// Purpose: cycle the 6 magnifier tick distributions across sibling +// dirs to pin tick-density divergence on intra-bar fills (gap X3, +// C++#11 — 11/12 codepaths cold). Pine source is identical for all +// six dirs; only the harness/build override switches the +// MagnifierDistribution enum to ENDPOINTS. TV always uses ENDPOINTS, +// so non-ENDPOINTS variants are documented expected divergence. +// +// Trade shape: long-only EMA(9/21) crossover entry plus a protective +// stop placed midway between bar open and high. The stop sits inside +// the bar intra-bar range, so whether it fires before the close +// depends on tick density — exactly the codepath we want to observe. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier ON. For ENDPOINTS the +// TV export is the parity baseline; for the other 5 the harness +// override produces the comparison. Export trades to trades.csv. +//@version=6 +strategy("PF magnifier dist probe 01-endpoints ENDPOINTS", shorttitle="MAG_END", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=true, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// Mid-bar entry on EMA crossover; protective stop placed between bar +// open and high so intra-bar tick distribution decides whether the +// stop fires before the bar close. +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + float stopLvl = open + (high - open) * 0.5 + strategy.exit("X", from_entry="L", stop=stopLvl, comment="mid-bar stop") + +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-rsi-cross-08a.pine b/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-rsi-cross-08a.pine new file mode 100644 index 0000000..69bad70 --- /dev/null +++ b/tests/gate-corpus/ok/validation__magnifier-tick-dist-endpoints-rsi-cross-08a.pine @@ -0,0 +1,37 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Magnifier distribution probe 08a — ENDPOINTS, RSI-cross entry +// +// Purpose: pair with 08b (UNIFORM) to pin tick-density divergence on +// intra-bar fills using an RSI(14) cross-50 entry. Stop placed +// mid-bar so the magnifier sample sequence selects the fill bar. +// 08a is the TV-parity baseline (TV uses ENDPOINTS); 08b runs the +// same Pine through the harness with UNIFORM and the diff is the +// observed tick-density signal. +// +// Trade shape: long-only RSI(14) cross-up through 50; mid-bar +// protective stop at (open+high)/2. Exit on RSI cross-down through 50. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier ON. Export trades to +// trades.csv. +//@version=6 +strategy("PF magnifier dist probe 08a - ENDPOINTS RSI cross", shorttitle="MAG_08a_END", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=true, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) + +bool entryCond = ta.crossover(rsiVal, 50.0) +bool exitCond = ta.crossunder(rsiVal, 50.0) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + float stopLvl = (open + high) * 0.5 + strategy.exit("X", from_entry="L", stop=stopLvl, comment="mid-bar stop") + +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__magnifier-tick-dist-volume-weighted-on-01.pine b/tests/gate-corpus/ok/validation__magnifier-tick-dist-volume-weighted-on-01.pine new file mode 100644 index 0000000..01d2ee8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__magnifier-tick-dist-volume-weighted-on-01.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Magnifier distribution probe 07 — volume-weighted ON +// +// Purpose: exercise the magnifier volume-weighted code path with a +// stop placed mid-bar so the chosen tick distribution and the volume +// weighting jointly decide the fill. Pine cannot toggle +// volume_weighted itself (no Pine v6 keyword); the harness must call +// strategy_set_magnifier_volume_weighted(true) before run(). TV does +// not expose volume weighting in the same form, so divergence vs the +// TV export is expected and should be filed as parity-anomaly. +// +// Trade shape: long-only EMA(9/21) crossover entry plus a protective +// stop at exactly the bar midpoint (open+high)/2. Whether the stop +// fires before close depends on volume-weighted tick density. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier ON. Export trades to +// trades.csv as the volume-weighted-OFF baseline; the volume-weighted +// ON run is harness-only. +//@version=6 +strategy("PF magnifier dist probe 07 - volume weighted ON", shorttitle="MAG_VWT", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=true, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + float stopLvl = (open + high) * 0.5 + strategy.exit("X", from_entry="L", stop=stopLvl, comment="mid-bar stop") + +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__matrix-bool-mask-explicit-utc-tz-01.pine b/tests/gate-corpus/ok/validation__matrix-bool-mask-explicit-utc-tz-01.pine new file mode 100644 index 0000000..51c4edd --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-bool-mask-explicit-utc-tz-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 10 — typed-matrix-01 with explicit hour(time, "UTC") +// +// Purpose: typed-matrix-probe-01-bool-regime-mask uses bare `hour(time)` and +// `dayofweek(time)`, which Pine resolves to `syminfo.timezone` (the EXCHANGE +// timezone, NOT the chart display TZ). For BINANCE:ETHUSDT.P, TV may report +// syminfo.timezone as "UTC" or a non-UTC value depending on TV's symbol +// metadata — engine defaults to "UTC" via its constructor. +// +// This probe pins both sides to "UTC" by passing the timezone explicitly. +// Pine v6: `hour(time, tz_arg)` honours the explicit `tz_arg` regardless of +// syminfo or chart TZ. +// +// Diagnostic: +// excellent → bug in typed-matrix-01 is engine syminfo_.timezone vs TV +// syminfo.timezone for BINANCE:ETHUSDT.P. Fix: validator +// passes BINANCE's syminfo.timezone (whatever TV reports) to +// engine via syminfo_, separately from chart_timezone_. +// strong/moderate → bug is in matrix bool dispatch (set/get/transpose) or +// hotCount accumulator, NOT TZ-related. +//@version=6 +strategy("PF TA isolate 10 - typed-matrix UTC", shorttitle="TAI_10_TM_UTC", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +var matrix mask = matrix.new(24, 7, false) + +rsiVal = ta.rsi(close, 14) + +int h = hour(time, "UTC") +int d = dayofweek(time, "UTC") - 1 // 0..6 + +if not na(rsiVal) and rsiVal > 60.0 and h >= 0 and h < 24 and d >= 0 and d < 7 + matrix.set(mask, h, d, true) + +var matrix mT = matrix.new(7, 24, false) +var matrix mTT = matrix.new(24, 7, false) +mT := matrix.transpose(mask) +mTT := matrix.transpose(mT) + +bool sample = matrix.get(mTT, h < 24 ? h : 0, d >= 0 and d < 7 ? d : 0) + +int hotCount = 0 +for i = 0 to 23 + for j = 0 to 6 + if matrix.get(mTT, i, j) + hotCount += 1 + +bool entryCond = hotCount >= 6 and rsiVal > 55.0 and sample +bool exitCond = not na(rsiVal) and rsiVal < 45.0 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__matrix-bool-mask-no-transpose-01.pine b/tests/gate-corpus/ok/validation__matrix-bool-mask-no-transpose-01.pine new file mode 100644 index 0000000..d922b6d --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-bool-mask-no-transpose-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 11 — matrix.new(24,7) mask without transpose +// Purpose: parity test for `var matrix.new(24, 7, false)` cell mutation +// (`matrix.set(mask, h, d, true)`) and a 24×7 nested-loop `matrix.get` +// hot-count, with NO matrix.transpose() in the path. +// +//@version=6 +strategy("PF TA isolate 11 - mask only no transpose", shorttitle="TAI_11_MASK", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +var matrix mask = matrix.new(24, 7, false) +rsiVal = ta.rsi(close, 14) +int h = hour(time) +int d = dayofweek(time) - 1 + +if not na(rsiVal) and rsiVal > 60.0 and h >= 0 and h < 24 and d >= 0 and d < 7 + matrix.set(mask, h, d, true) + +bool sample = matrix.get(mask, h < 24 ? h : 0, d >= 0 and d < 7 ? d : 0) +int hotCount = 0 +for i = 0 to 23 + for j = 0 to 6 + if matrix.get(mask, i, j) + hotCount += 1 + +bool entryCond = hotCount >= 6 and rsiVal > 55.0 and sample +bool exitCond = not na(rsiVal) and rsiVal < 45.0 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__matrix-bool-mask-transpose-roundtrip-01.pine b/tests/gate-corpus/ok/validation__matrix-bool-mask-transpose-roundtrip-01.pine new file mode 100644 index 0000000..72c5bb7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-bool-mask-transpose-roundtrip-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 12 — matrix.transpose round-trip (mask → mT → mTT) +// Purpose: parity test for `matrix.transpose()` (mask 24×7 → mT 7×24 → mTT +// 24×7 round-trip) followed by `matrix.get(mTT, h, d)` sampling — no +// hot-count loop, so the only matrix surface under test is transpose. +// +//@version=6 +strategy("PF TA isolate 12 - sample only no hotCount", shorttitle="TAI_12_SAMPLE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +var matrix mask = matrix.new(24, 7, false) +rsiVal = ta.rsi(close, 14) +int h = hour(time) +int d = dayofweek(time) - 1 + +if not na(rsiVal) and rsiVal > 60.0 and h >= 0 and h < 24 and d >= 0 and d < 7 + matrix.set(mask, h, d, true) + +var matrix mT = matrix.new(7, 24, false) +var matrix mTT = matrix.new(24, 7, false) +mT := matrix.transpose(mask) +mTT := matrix.transpose(mT) + +bool sample = matrix.get(mTT, h < 24 ? h : 0, d >= 0 and d < 7 ? d : 0) + +bool entryCond = sample and rsiVal > 55.0 +bool exitCond = not na(rsiVal) and rsiVal < 45.0 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__matrix-bool-regime-mask-24x7-01.pine b/tests/gate-corpus/ok/validation__matrix-bool-regime-mask-24x7-01.pine new file mode 100644 index 0000000..0d42fbc --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-bool-regime-mask-24x7-01.pine @@ -0,0 +1,65 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Typed-matrix probe 01 — bool regime mask (matrix.new) +// +// Purpose: exercise typed matrix dispatch with `matrix.new(N, M, false)`. +// We build a 24x7 (hour-of-day x day-of-week) regime mask, populate it as +// RSI > 60 closes accumulate, round-trip through `matrix.transpose`, then +// gate entries on the running sum of `true` cells crossing a threshold. +// Surfaces: bool proxy storage in typed matrices, set/get/transpose dispatch, +// flow of bool-matrix reductions into orders. +// +// Trade shape: long-only momentum-regime entry. We long when the bool mask +// has accumulated >= 6 hot (hour, dow) buckets AND current bar's RSI > 55; +// exit on RSI < 45. The mask is sparse early then fills in, so trade count +// rises with corpus length — comfortably >=10 closed trades on the 15m +// ETH-USDT-USDT window. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to tv_trades.csv +// in this folder. +//@version=6 +strategy("PF typed-matrix probe 01 - bool regime mask", shorttitle="TM_p01_BOOL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +var matrix mask = matrix.new(24, 7, false) + +rsiVal = ta.rsi(close, 14) + +int h = hour(time) +int d = dayofweek(time) - 1 // 0..6 + +// Mark hot buckets when momentum is strong on this bar +if not na(rsiVal) and rsiVal > 60.0 and h >= 0 and h < 24 and d >= 0 and d < 7 + matrix.set(mask, h, d, true) + +// Round-trip transpose (24x7 -> 7x24 -> 24x7) to exercise dispatch. +// Use var + := so the LHS spec is set once via matrix.new at init, +// then reassignment each bar carries the correct PineGenericMatrix type. +var matrix mT = matrix.new(7, 24, false) +var matrix mTT = matrix.new(24, 7, false) +mT := matrix.transpose(mask) +mTT := matrix.transpose(mT) + +// Read-back parity check via get +bool sample = matrix.get(mTT, h < 24 ? h : 0, d >= 0 and d < 7 ? d : 0) + +// Count hot buckets +int hotCount = 0 +for i = 0 to 23 + for j = 0 to 6 + if matrix.get(mTT, i, j) + hotCount += 1 + +bool entryCond = hotCount >= 6 and rsiVal > 55.0 and sample +bool exitCond = not na(rsiVal) and rsiVal < 45.0 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__matrix-covariance-eigen-pca-01.pine b/tests/gate-corpus/ok/validation__matrix-covariance-eigen-pca-01.pine new file mode 100644 index 0000000..fa3e9b4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-covariance-eigen-pca-01.pine @@ -0,0 +1,43 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 33 — matrix.new() covariance + largest-eigenvalue extraction +// Purpose: parity test for `var matrix` cell mutations (`m.set(i,j,v)`) +// driven by per-bar ta.sma covariance entries, then eigenvalue derivation. +// +//@version=6 +strategy("Matrix Eigen PCA", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +length = input.int(14, "Length", minval=2) +v1 = close - open +v2 = high - low + +// Covariance matrix simulation over `length` +v1_mean = ta.sma(v1, length) +v2_mean = ta.sma(v2, length) + +cov11 = ta.sma((v1 - v1_mean) * (v1 - v1_mean), length) +cov12 = ta.sma((v1 - v1_mean) * (v2 - v2_mean), length) +cov21 = cov12 +cov22 = ta.sma((v2 - v2_mean) * (v2 - v2_mean), length) + +var m = matrix.new(2, 2, 0.0) +m.set(0, 0, cov11) +m.set(0, 1, cov12) +m.set(1, 0, cov21) +m.set(1, 1, cov22) + +covReady = not na(cov11) and not na(cov12) and not na(cov22) + +// Largest eigenvalue = principal variance (always >= 0); compare to its SMA so crosses are meaningful +float lam = na +if covReady + lam := array.size(m.eigenvalues()) > 0 ? array.get(m.eigenvalues(), 0) : na + +lamSma = ta.sma(lam, length) + +if covReady and not na(lam) and not na(lamSma) and ta.crossover(lam, lamSma) + strategy.entry("Long", strategy.long) +if covReady and not na(lam) and not na(lamSma) and ta.crossunder(lam, lamSma) + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__matrix-eigen-rank-deficient-cov-01.pine b/tests/gate-corpus/ok/validation__matrix-eigen-rank-deficient-cov-01.pine new file mode 100644 index 0000000..8351ae6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__matrix-eigen-rank-deficient-cov-01.pine @@ -0,0 +1,85 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Typed-matrix probe 02 — Eigen rank-deficient covariance (NaN propagation) +// +// Purpose: build a 3x3 covariance matrix from collinear sources +// (close, close*2, close*3). The covariance matrix is rank 1, so the +// `matrix.eigenvalues()` result is degenerate — two eigenvalues are +// theoretically zero and floating-point noise pushes them to NaN-prone +// territory. We document NaN propagation: when the smallest eigenvalue is +// `na`, entry must be skipped (no spurious orders from NaN comparisons). +// +// Trade shape: a baseline EMA-cross long is gated by an Eigen-derived +// "condition number ok" flag. When eigenvalues are well-defined we trade; +// when degenerate (always, here) we expect entry skip. To still produce +// >=10 closed trades, we add a fallback path on a non-degenerate 2x2 +// identity-perturbed covariance built from `close` vs `close + atr` (true +// rank 2). The probe still pins NaN-from-Eigen behavior because the 3x3 +// path is evaluated every bar. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to tv_trades.csv. +//@version=6 +strategy("PF typed-matrix probe 02 - eigen rank deficient", shorttitle="TM_p02_EIG", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +int N = 32 + +// Sliding-window mean helper +mean(src, len) => ta.sma(src, len) + +float c1 = close +float c2 = close * 2.0 +float c3 = close * 3.0 + +float m1 = mean(c1, N) +float m2 = mean(c2, N) +float m3 = mean(c3, N) + +// Build 3x3 covariance from collinear series — rank 1 by construction +var matrix cov3 = matrix.new(3, 3, 0.0) + +// Guard: SMA warmup leaves na for bar_index < N. nz at the leaf so the +// matrix never carries na (TV's matrix.eigenvalues throws on any na cell). +float d1 = nz(c1 - m1, 0.0) +float d2 = nz(c2 - m2, 0.0) +float d3 = nz(c3 - m3, 0.0) + +matrix.set(cov3, 0, 0, d1 * d1) +matrix.set(cov3, 0, 1, d1 * d2) +matrix.set(cov3, 0, 2, d1 * d3) +matrix.set(cov3, 1, 0, d2 * d1) +matrix.set(cov3, 1, 1, d2 * d2) +matrix.set(cov3, 1, 2, d2 * d3) +matrix.set(cov3, 2, 0, d3 * d1) +matrix.set(cov3, 2, 1, d3 * d2) +matrix.set(cov3, 2, 2, d3 * d3) + +// Only call eigenvalues after warmup completes, otherwise the matrix is +// all zeros and eigenvalues are trivially {0,0,0} (rank-deficient by intent). +bool warmedUp = bar_index >= N +array evals = warmedUp ? matrix.eigenvalues(cov3) : array.new(0) +float emin = array.size(evals) > 0 ? array.min(evals) : na +bool eigenOk = not na(emin) and emin > 1e-9 + +// Fallback non-degenerate path so we still close >=10 trades +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool baseEntry = ta.crossover(emaFast, emaSlow) +bool baseExit = ta.crossunder(emaFast, emaSlow) + +// Gate: skip when Eigen path is na (documents NaN-skip); +// otherwise allow the cross to fire. +if baseEntry and strategy.position_size == 0 + if eigenOk + strategy.entry("L_eig", strategy.long, qty=1, comment="entry long eig-ok") + else + strategy.entry("L_fb", strategy.long, qty=1, comment="entry long fallback") +if baseExit and strategy.position_size > 0 + strategy.close_all(comment="exit") diff --git a/tests/gate-corpus/ok/validation__mtf-daily-array-median-percentrank-01.pine b/tests/gate-corpus/ok/validation__mtf-daily-array-median-percentrank-01.pine new file mode 100644 index 0000000..d1a2a86 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-daily-array-median-percentrank-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 36 — request.security daily close + array rolling buffer +// Purpose: parity test for HTF feed pushed into a `var array`, with +// array.median / array.percentrank statistics driving entries. +// +//@version=6 +strategy("MTF Array Stats", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +htf_c = request.security(syminfo.tickerid, "D", close) + +var a = array.new() +array.push(a, htf_c) +if array.size(a) > 20 + array.shift(a) + +if array.size(a) == 20 + med = array.median(a) + pr = array.percentrank(a, array.size(a) - 1) + + if pr < 10 + strategy.entry("Long", strategy.long) + if pr > 90 + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__mtf-daily-ema26-warmup-01.pine b/tests/gate-corpus/ok/validation__mtf-daily-ema26-warmup-01.pine new file mode 100644 index 0000000..c108742 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-daily-ema26-warmup-01.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 09 — daily HTF EMA(26) warmup behaviour +// +// Purpose: pin down the HTF TA-chain warmup discrepancy spotted in probe 08. +// Probe 08 confirmed steady-state agreement across three concurrent TFs + +// user-defined function inside request.security; the only divergence was the +// very first long entry — engine fired ~2 weeks before TradingView. The +// likely cause is the seed/early values of `ta.ema(close, 26)` aggregated on +// the daily timeframe (slowest standard MACD component). +// +// This probe strips the strategy down to a single daily HTF + a single TA +// chain. Trades fire on rising/falling daily EMA, so any difference in seed +// or NA propagation manifests as extra/early/missing trades in the first +// ~26 daily bars of the dataset. After warmup the two should agree exactly. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF MTF probe 09 - daily EMA warmup", shorttitle="MTF_p09_DEMA", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float htfEma = request.security(syminfo.tickerid, "D", ta.ema(close, 26), lookahead=barmerge.lookahead_off) + +bool emaRising = not na(htfEma) and not na(htfEma[1]) and htfEma > htfEma[1] +bool emaFalling = not na(htfEma) and not na(htfEma[1]) and htfEma < htfEma[1] + +if emaRising and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip to long") + strategy.entry("L", strategy.long, comment="daily EMA rising") + +if emaFalling and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip to short") + strategy.entry("S", strategy.short, comment="daily EMA falling") diff --git a/tests/gate-corpus/ok/validation__mtf-daily-prev-high-break-01.pine b/tests/gate-corpus/ok/validation__mtf-daily-prev-high-break-01.pine new file mode 100644 index 0000000..9e02bad --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-daily-prev-high-break-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 04 — Daily high[1] vs 15m close (classic “HTF level” gate) +// Purpose: parity test for `request.security(..., "D", high[1], lookahead_off)` +// — the `[1]` history reference INSIDE the security call must resolve on the +// HTF bar, not the LTF chart bar. +// +// request.security(..., "D", high[1]) +// +// Strict TV↔engine parity checklist: +// - Use the same symbol feed as data/ohlcv_ETH-USDT-USDT_15m.csv (Binance USDT-M ETH). +// - Match chart date range to the OHLCV file; TV “List of trades” must cover that window. +// - Set validate_detailed_report / inputs.json: tv_trades_csv_tz = "utc" if chart TZ was UTC +// when exporting trades, else "utc_plus_8" (default). Mismatch causes ±8h pairing errors. +//@version=6 +strategy("PF MTF probe 04 — D high[1] break", shorttitle="MTF_p04", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float dHigh1 = request.security(syminfo.tickerid, "D", high[1], lookahead=barmerge.lookahead_off) + +bool crossUp = close > dHigh1 and close[1] <= dHigh1[1] + +if crossUp and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1) + +if close < dHigh1 * 0.995 and strategy.position_size > 0 + strategy.close("L") diff --git a/tests/gate-corpus/ok/validation__mtf-dual-tf-60-240-rising-01.pine b/tests/gate-corpus/ok/validation__mtf-dual-tf-60-240-rising-01.pine new file mode 100644 index 0000000..009e69a --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-dual-tf-60-240-rising-01.pine @@ -0,0 +1,28 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 02 — two HTF closes (60m + 240m); long only when both close > prior bar close +// Purpose: parity test for alignment of two parallel `request.security` series +// at distinct HTFs and the AND of their `[1]`-history rising checks. +// +//@version=6 +strategy("PF MTF probe 02 — dual TF closes", shorttitle="MTF_p02", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float h60 = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) +float h240 = request.security(syminfo.tickerid, "240", close, lookahead=barmerge.lookahead_off) + +bool up60 = h60 > h60[1] +bool up240 = h240 > h240[1] +bool bothUp = up60 and up240 + +if bothUp and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1) + +bool exitSig = not up60 and strategy.position_size > 0 +if exitSig + strategy.close("L") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-close-change-baseline-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-close-change-baseline-01.pine new file mode 100644 index 0000000..307f3e2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-close-change-baseline-01.pine @@ -0,0 +1,57 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 39 — request.security parity baseline +// Purpose: parity test for one-trade-per-HTF-close-change pattern using +// `request.security(..., "60", close, lookahead=barmerge.lookahead_off)` and +// `ta.change(htfClose) != 0` as the sole entry trigger. +// +// ══════════════════════════════════════════════════════════════════════════════ +// Request.security parity — TV export vs PineForge +// ══════════════════════════════════════════════════════════════════════════════ +// +// Detail +// ------ +// One **closed** long per **HTF close change** (15m chart → `request.security` 60m `close`, +// `lookahead_off`). Compare your TradingView “List of trades” CSV to PineForge’s backtest +// on the **same** OHLCV window (e.g. `data/ohlcv_ETH-USDT-USDT_15m.csv`). +// +// TradingView setup (match PineForge validation feed) +// -------------------------------------------------- +// • Chart: **15m** (must match the CSV bar size you use in PineForge). +// • Symbol: **same** as OHLCV (e.g. ETHUSDT perpetual on Binance if that is your feed). +// • Strategy settings: **Initial capital** 1e6, **Order size** fixed **1** contract/share, +// **Commission** 0%, **Slippage** 0, **Pyramiding** 1 (below). +// • Deep backtest: on. Export **List of trades** CSV after running. +// +// What each trade is +// ------------------ +// • **Entry**: bar where `ta.change(request.security(..., "60", close)) != 0` (new completed 1h close vs prior bar). +// • **Exit**: next bar (or forced flat on last historical bar so the last roll still closes). +// +// If TV trade count vs PineForge differs, the gap is usually HTF bucket alignment, data feed, +// or session — not the pytest HTF ratio reference (see `tests/test_request_security_htf_reference.py`). +// +// ══════════════════════════════════════════════════════════════════════════════ + +//@version=6 +strategy("PineForge RS parity [15m→60m close]", shorttitle="PF_RS_60m", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float htfClose = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) + +bool htfRoll = ta.change(htfClose) != 0 + +// Enter long on each HTF close roll; exit on the following bar (closed trade = comparable row in export). +if htfRoll + strategy.entry("HTF_ROLL", strategy.long, qty=1, comment="roll") + +if htfRoll[1] + strategy.close("HTF_ROLL", comment="roll+1") + +if barstate.islast and strategy.position_size > 0 + strategy.close("HTF_ROLL", comment="last bar flat") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-close-roll-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-close-roll-01.pine new file mode 100644 index 0000000..eb15781 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-close-roll-01.pine @@ -0,0 +1,25 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 01 — HTF close roll (60m) on 15m chart +// Purpose: parity test for `request.security(..., "60", close, lookahead_off)` +// + `ta.change(htfClose) != 0` as the entry trigger; trade per-HTF-bar-close. +// +//@version=6 +strategy("PF MTF probe 01 — 60m close roll", shorttitle="MTF_p01", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float h60 = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) +bool roll = ta.change(h60) != 0 + +if roll + strategy.entry("R", strategy.long, qty=1) +if roll[1] + strategy.close("R") + +if barstate.islast and strategy.position_size > 0 + strategy.close("R") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-gaps-on-roll-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-gaps-on-roll-01.pine new file mode 100644 index 0000000..1ee1a6f --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-gaps-on-roll-01.pine @@ -0,0 +1,34 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 47 - request.security gaps_on +// +// Purpose: isolate barmerge.gaps_on with lookahead_off on a 15m chart requesting 60m data. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 47 - security gaps on", shorttitle="PF_G47_GAPS", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +h60Gap = request.security(syminfo.tickerid, "60", close, + gaps=barmerge.gaps_on, lookahead=barmerge.lookahead_off) + +var float lastHtfClose = na +bool hasHtfValue = not na(h60Gap) +bool rollUp = false +bool rollDown = false + +if hasHtfValue + rollUp := not na(lastHtfClose) and h60Gap > lastHtfClose + rollDown := not na(lastHtfClose) and h60Gap < lastHtfClose + lastHtfClose := h60Gap + +if rollUp and strategy.position_size == 0 + strategy.entry("GAP_UP", strategy.long, qty=1, comment="gap roll up") + +if rollDown and strategy.position_size > 0 + strategy.close("GAP_UP", comment="gap roll down") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-rsi14-inside-security-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-rsi14-inside-security-01.pine new file mode 100644 index 0000000..2035f3e --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-rsi14-inside-security-01.pine @@ -0,0 +1,25 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 05 — RSI(14) on 60m series inside request.security +// Purpose: parity test for `ta.rsi(close, 14)` evaluated INSIDE +// `request.security(..., "60", ...)` with 50-line crossover entries on the +// HTF RSI series. +// +//@version=6 +strategy("PF MTF probe 05 — 60m RSI(14)", shorttitle="MTF_p05", overlay=false, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float htfRsi = request.security(syminfo.tickerid, "60", ta.rsi(close, 14), lookahead=barmerge.lookahead_off) + +bool longCond = ta.crossover(htfRsi, 50) +bool shortCond = ta.crossunder(htfRsi, 50) + +if longCond + strategy.entry("L", strategy.long, qty=1) +if shortCond and strategy.position_size > 0 + strategy.close("L") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-sma20-inside-security-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-sma20-inside-security-01.pine new file mode 100644 index 0000000..e1363cd --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-sma20-inside-security-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 03 — ta.sma(close,20) computed *inside* 60m context via request.security +// Purpose: parity test for `ta.sma(close, 20)` evaluated INSIDE +// `request.security(..., "60", ...)` so SMA state runs on the HTF series, not +// resampled from LTF. +// +//@version=6 +strategy("PF MTF probe 03 — 60m SMA(20)", shorttitle="MTF_p03", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float htfSma = request.security(syminfo.tickerid, "60", ta.sma(close, 20), lookahead=barmerge.lookahead_off) +float htfCl = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) + +bool longCond = htfCl > htfSma and htfCl[1] <= htfSma[1] + +if longCond + strategy.entry("L", strategy.long, qty=1) + +bool exitCond = htfCl < htfSma and strategy.position_size > 0 +if exitCond + strategy.close("L") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-60-volume-spike-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-60-volume-spike-01.pine new file mode 100644 index 0000000..3dbaab1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-60-volume-spike-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 06 — sum of volume on 60m (aggregated series inside security) +// Purpose: parity test for non-OHLC `volume` series passed through +// `request.security` and a parallel `ta.sma(volume, 20)` HTF feed for +// spike-vs-baseline detection. +// +//@version=6 +strategy("PF MTF probe 06 — 60m volume sum", shorttitle="MTF_p06", overlay=false, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float htfVol = request.security(syminfo.tickerid, "60", volume, lookahead=barmerge.lookahead_off) +float htfVolSma = request.security(syminfo.tickerid, "60", ta.sma(volume, 20), lookahead=barmerge.lookahead_off) + +bool spike = htfVol > htfVolSma * 2.0 + +if spike and strategy.position_size == 0 + strategy.entry("V", strategy.long, qty=1) + +if not spike and strategy.position_size > 0 + strategy.close("V") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-confluence-manual-trail-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-confluence-manual-trail-01.pine new file mode 100644 index 0000000..4fe3df5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-confluence-manual-trail-01.pine @@ -0,0 +1,77 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 11 — HTF confluence gate with MANUAL TRAILING stop +// +// Purpose: companion to mtf-probe-10. Keeps the exact same HTF confluence +// gate so any drift between probe 10 and probe 11 isolates the manual +// trailing-stop / per-bar strategy.exit re-issue pattern that VCP uses. +// +// The trailing implementation matches VCP's: +// * a ``var float`` trail slot is updated each bar to the higher of +// (close - atr * trailMult) and the previous trail (so it only +// ratchets up for longs); +// * ``strategy.exit("LX", "L", stop=trail, limit=tp)`` is called every +// bar while in position with the freshly-updated trail. +// The engine must recognize that a strategy.exit re-issue with the same +// id+from_entry replaces the previous pending order rather than fanning +// out into multiple sibling exits — and the consumed-partial-id guard +// must NOT spuriously cancel valid full exits when the same id re-issues +// with a different stop. +// +// Trade shape: long-only, pyramiding=1. Same entry signal as probe 10. +// Exit via a static TP and a per-bar manual trail. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("MTF probe 11 - HTF bracket manual trail", shorttitle="MTF_p11", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs --------------------------------------------------------- +string i_htf = input.timeframe("60", "Higher Timeframe") +int i_htf_ema = input.int(20, "HTF EMA Length", minval=2, maxval=200) +int i_fast_ema = input.int(9, "Fast EMA Length", minval=2, maxval=50) +int i_slow_ema = input.int(21, "Slow EMA Length", minval=3, maxval=100) +int i_atr_len = input.int(14, "ATR Length", minval=2, maxval=50) +float i_stop_mult = input.float(2.0, "Initial Stop ATR mult", minval=0.5, maxval=5.0, step=0.1) +float i_tp_mult = input.float(4.0, "Take-profit ATR mult", minval=0.5, maxval=10.0, step=0.1) +float i_trail_mult = input.float(2.0, "Manual Trail ATR mult", minval=0.5, maxval=5.0, step=0.1) + +// ---- 15m series ----------------------------------------------------- +float emaFast = ta.ema(close, i_fast_ema) +float emaSlow = ta.ema(close, i_slow_ema) +float atrVal = ta.atr(i_atr_len) + +// ---- HTF confluence (identical to probe 10) ------------------------ +float _htfEmaSeries = ta.ema(close, i_htf_ema) +float htfClose = request.security(syminfo.tickerid, i_htf, + close, barmerge.gaps_off, barmerge.lookahead_off) +float htfEma = request.security(syminfo.tickerid, i_htf, + _htfEmaSeries, barmerge.gaps_off, barmerge.lookahead_off) +bool htfBull = not na(htfClose) and not na(htfEma) and htfClose > htfEma + +// ---- entry + per-bar manual trailing ------------------------------ +bool longSig = ta.crossover(emaFast, emaSlow) and htfBull + +var float entryTP = na +var float trailStop = na + +if longSig and strategy.position_size == 0 and not na(atrVal) + entryTP := close + atrVal * i_tp_mult + trailStop := close - atrVal * i_stop_mult + strategy.entry("L", strategy.long, qty=1, comment="entry long") + +// Manual trailing — ratchet trailStop higher each bar and re-issue +// the strategy.exit so the engine pending-order replaces in place. +if strategy.position_size > 0 and not na(atrVal) + float candidate = close - atrVal * i_trail_mult + if not na(trailStop) and candidate > trailStop + trailStop := candidate + strategy.exit("LX", "L", stop=trailStop, limit=entryTP, + comment="manual trail") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-confluence-static-bracket-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-confluence-static-bracket-01.pine new file mode 100644 index 0000000..6b0d9f6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-confluence-static-bracket-01.pine @@ -0,0 +1,75 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 10 — HTF confluence gate with STATIC bracket exit +// +// Purpose: isolate the MTF-state half of the VCP integration probe family's +// failure mode. That family gates entries on 1H htf trend confluence +// (request.security) and also re-issues a trailing stop every bar via +// repeated strategy.exit +// calls. This probe keeps ONLY the HTF confluence half — the bracket +// exit is set ONCE at entry and never touched again — so any TV/engine +// drift here is attributable to MTF state evaluation timing alone. +// The companion probe (mtf-probe-11-htf-bracket-manual-trail) keeps the +// same HTF gate but adds the per-bar manual trailing stop. Compare the +// two: if probe 10 matches and probe 11 doesn't, the gap is in the +// manual-trail pattern; if both fail, the gap is in HTF confluence. +// +// Trade shape: long-only, pyramiding=1. Enter when 15m EMA(9) crosses +// above EMA(21) AND the 1H htf close is above its 1H EMA(20). Exit +// via a static stop + ATR-multiple take-profit set at entry — NEVER +// re-issued. Bracket prices are captured into ``var float`` slots +// at entry time, so the engine's strategy.exit pending-order pruning +// can't be confused by per-bar recomputation. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("MTF probe 10 - HTF bracket static", shorttitle="MTF_p10", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs --------------------------------------------------------- +string i_htf = input.timeframe("60", "Higher Timeframe") +int i_htf_ema = input.int(20, "HTF EMA Length", minval=2, maxval=200) +int i_fast_ema = input.int(9, "Fast EMA Length", minval=2, maxval=50) +int i_slow_ema = input.int(21, "Slow EMA Length", minval=3, maxval=100) +int i_atr_len = input.int(14, "ATR Length", minval=2, maxval=50) +float i_stop_mult = input.float(2.0, "Stop ATR mult", minval=0.5, maxval=5.0, step=0.1) +float i_tp_mult = input.float(4.0, "Take-profit ATR mult", minval=0.5, maxval=10.0, step=0.1) + +// ---- 15m series ----------------------------------------------------- +float emaFast = ta.ema(close, i_fast_ema) +float emaSlow = ta.ema(close, i_slow_ema) +float atrVal = ta.atr(i_atr_len) + +// ---- HTF confluence (1H by default) -------------------------------- +// Pre-compute the htf EMA at global scope so request.security pulls a +// stable expression (same pattern VCP uses for its mtf checks). Engine +// must materialize this with lookahead_off semantics so we never see a +// future htf-bar value. +float _htfEmaSeries = ta.ema(close, i_htf_ema) +float htfClose = request.security(syminfo.tickerid, i_htf, + close, barmerge.gaps_off, barmerge.lookahead_off) +float htfEma = request.security(syminfo.tickerid, i_htf, + _htfEmaSeries, barmerge.gaps_off, barmerge.lookahead_off) +bool htfBull = not na(htfClose) and not na(htfEma) and htfClose > htfEma + +// ---- entry --------------------------------------------------------- +bool longSig = ta.crossover(emaFast, emaSlow) and htfBull + +// Static bracket levels captured at entry. Initialized to na so they +// can't fire pre-entry; once set, never recomputed. +var float entryStop = na +var float entryTP = na + +if longSig and strategy.position_size == 0 and not na(atrVal) + entryStop := close - atrVal * i_stop_mult + entryTP := close + atrVal * i_tp_mult + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", stop=entryStop, limit=entryTP, + comment="static bracket") diff --git a/tests/gate-corpus/ok/validation__mtf-htf-weekly-sma-cross-01.pine b/tests/gate-corpus/ok/validation__mtf-htf-weekly-sma-cross-01.pine new file mode 100644 index 0000000..7738077 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-htf-weekly-sma-cross-01.pine @@ -0,0 +1,24 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF MTF probe — Weekly ("W") HTF SMA(10) close-cross. +// Isolates calendar WEEK aggregation end-to-end: ta.sma(close,10) is computed +// INSIDE request.security(..., "W", ...) so the SMA state runs on the weekly +// HTF series, then the LTF close crosses that weekly SMA. The "W" timeframe +// forces ISO Monday-based week-boundary bucketing in the engine; this is the +// only corpus probe driving trades off a calendar-WEEK security() chain, so a +// TV export adjudicates the week-open alignment + HTF→LTF roll-forward. +//@version=6 +strategy("PF MTF probe - weekly SMA(10) cross", shorttitle="MTF_WSMA", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float wsma = request.security(syminfo.tickerid, "W", ta.sma(close, 10), lookahead=barmerge.lookahead_off) + +if ta.crossover(close, wsma) + strategy.entry("L", strategy.long, comment="close x up weekly SMA") +if ta.crossunder(close, wsma) + strategy.entry("S", strategy.short, comment="close x dn weekly SMA") diff --git a/tests/gate-corpus/ok/validation__mtf-roll-state-60-240-d-minimal-01.pine b/tests/gate-corpus/ok/validation__mtf-roll-state-60-240-d-minimal-01.pine new file mode 100644 index 0000000..75ed7e4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-roll-state-60-240-d-minimal-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 54 - minimal MTF roll state +// +// Purpose: isolate request.security state across 60m, 240m, and D contexts, +// without the large community strategy filters. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 54 - MTF roll state", shorttitle="PF_P54_MTF", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +h60 = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) +h240 = request.security(syminfo.tickerid, "240", close, lookahead=barmerge.lookahead_off) +dHigh1 = request.security(syminfo.tickerid, "D", high[1], lookahead=barmerge.lookahead_off) + +roll60 = ta.change(h60) != 0 +roll240 = ta.change(h240) != 0 +dailyBreak = close > dHigh1 and close[1] <= dHigh1[1] + +if roll60 and h60 > h60[1] and h240 >= h240[1] and strategy.position_size == 0 + strategy.entry("R60", strategy.long, qty=1, comment="60 up with 240 state") + +if roll240 and h240 < h240[1] and strategy.position_size > 0 + strategy.close("R60", comment="240 down close") + +if dailyBreak and strategy.position_size == 0 + strategy.entry("D", strategy.long, qty=1, comment="daily high break") + +if strategy.position_size > 0 and close < dHigh1 * 0.995 + strategy.close_all(comment="daily ref close") diff --git a/tests/gate-corpus/ok/validation__mtf-triple-tf-close-confluence-01.pine b/tests/gate-corpus/ok/validation__mtf-triple-tf-close-confluence-01.pine new file mode 100644 index 0000000..168580a --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-triple-tf-close-confluence-01.pine @@ -0,0 +1,39 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 07 — triple HTF close confluence (60m + 240m + D) +// +// Purpose: isolate alignment of THREE simultaneous request.security feeds. +// Existing MTF probes cover one HTF feed (03/04/05/06) or a single dual-TF +// pair (02). The IES integration probe family queries three different HTFs +// at once and produced ~2x trades vs TradingView; this probe escalates to +// three TFs with the +// simplest possible expression (`close`) so any divergence here points to +// the multi-feed layout itself rather than to a derived TA expression. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF MTF probe 07 - triple TF close", shorttitle="MTF_p07_3TF", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +float h60 = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) +float h240 = request.security(syminfo.tickerid, "240", close, lookahead=barmerge.lookahead_off) +float hD = request.security(syminfo.tickerid, "D", close, lookahead=barmerge.lookahead_off) + +bool allUp = h60 > h60[1] and h240 > h240[1] and hD > hD[1] +bool allDown = h60 < h60[1] and h240 < h240[1] and hD < hD[1] + +if allUp and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip to long") + strategy.entry("L", strategy.long, comment="3-TF close up") + +if allDown and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip to short") + strategy.entry("S", strategy.short, comment="3-TF close down") diff --git a/tests/gate-corpus/ok/validation__mtf-triple-tf-macd-hist-confluence-01.pine b/tests/gate-corpus/ok/validation__mtf-triple-tf-macd-hist-confluence-01.pine new file mode 100644 index 0000000..03428d5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__mtf-triple-tf-macd-hist-confluence-01.pine @@ -0,0 +1,47 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// MTF probe 08 — triple HTF MACD histogram via user-defined function +// +// Purpose: replicate the IES integration probe family's bias pattern in the +// smallest possible strategy. That family wraps MACD histogram in +// `f_macd_hist(src, fast, slow, sig)` and calls it inside three +// request.security feeds (60m, 240m, D), then counts how many HTF buckets +// are bullish/bearish for entry. This combines "user-defined function +// inside request.security" + "three concurrent HTFs" + "ta.ema chain". +// MTF probes 03/05 cover ta.* inside security at a single HTF; probe 07 +// covers three HTFs with a flat expression. probe 08 covers the full +// triple-HTF user-function shape so the engine vs TV gap can be attributed +// to one of these specific axes. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF MTF probe 08 - triple TF MACD hist", shorttitle="MTF_p08_3MACD", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +f_macd_hist(series float src, int fast, int slow, int sig) => + float macd_val = ta.ema(src, fast) - ta.ema(src, slow) + float macd_sig = ta.ema(macd_val, sig) + macd_val - macd_sig + +float h1 = request.security(syminfo.tickerid, "60", f_macd_hist(close, 12, 26, 9), lookahead=barmerge.lookahead_off) +float h2 = request.security(syminfo.tickerid, "240", f_macd_hist(close, 12, 26, 9), lookahead=barmerge.lookahead_off) +float h3 = request.security(syminfo.tickerid, "D", f_macd_hist(close, 12, 26, 9), lookahead=barmerge.lookahead_off) + +bool allBull = h1 > 0 and h2 > 0 and h3 > 0 +bool allBear = h1 < 0 and h2 < 0 and h3 < 0 + +if allBull and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip to long") + strategy.entry("L", strategy.long, comment="3-TF MACD bull") + +if allBear and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip to short") + strategy.entry("S", strategy.short, comment="3-TF MACD bear") diff --git a/tests/gate-corpus/ok/validation__na-deep-history-int-na-01.pine b/tests/gate-corpus/ok/validation__na-deep-history-int-na-01.pine new file mode 100644 index 0000000..3413c23 --- /dev/null +++ b/tests/gate-corpus/ok/validation__na-deep-history-int-na-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// na-chain probe 01 — deep history + int-NA + na->bool cast +// +// Purpose: surface int-NA undefined behavior and `nz` chain truncation. +// We feed `ta.rsi` a `na`-gated source, request history depth >= 500, +// keep `nz` only at the leaf, use `time(session)` to produce out-of-session +// `na` values, and then cast `na` through to a bool used by the +// entry condition. If the C++ runtime's int-NA sentinel collides with +// `INT_MIN` in any reduction path, the trade list will diverge from TV. +// +// Trade shape: long-only. Every bar we compute a session-gated RSI on a +// source that is `na` outside session, only `nz`'d at the very last step +// (so intermediate RSI internals see `na`). Entry on RSI cross-up of 30 +// AND non-na session id; exit on cross-down of 70. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to tv_trades.csv. +//@version=6 +strategy("PF na-chain probe 01 - deep history int na", shorttitle="NA_p01_INT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false, + max_bars_back=500) + +// Out-of-session producer: time(timeframe, session, timezone) returns na outside the window +int sessId = time(timeframe.period, "0930-1600", "America/New_York") + +// Gated source — na outside session; nz only at the leaf +float gatedSrc = na(sessId) ? na : close + +// Deep history reach (500 bars back) on the gated source +float deepRef = nz(gatedSrc[499], close) + +// Mix gated + deep-history into RSI; nz applied at leaf only +float src = nz(gatedSrc, deepRef) +float rsiVal = ta.rsi(src, 14) + +// na -> bool cast path +bool sessOk = not na(sessId) + +bool entryCond = ta.crossover(rsiVal, 30.0) and sessOk +bool exitCond = ta.crossunder(rsiVal, 70.0) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__na-nz-fixnan-history-chain-01.pine b/tests/gate-corpus/ok/validation__na-nz-fixnan-history-chain-01.pine new file mode 100644 index 0000000..aa33222 --- /dev/null +++ b/tests/gate-corpus/ok/validation__na-nz-fixnan-history-chain-01.pine @@ -0,0 +1,29 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 49 - na/nz/fixnan history chain +// +// Purpose: isolate warmup behavior for history references, nz(), fixnan(), and chained series. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 49 - na nz history", shorttitle="PF_G49_NA", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rawDelta = close - close[1] +safeDelta = nz(rawDelta, 0) +heldDelta = fixnan(rawDelta) +score = ta.sma(safeDelta + nz(heldDelta, 0), 8) + +longSignal = not na(score) and score > 0 and nz(score[1], 0) <= 0 +flatSignal = not na(score) and score < 0 and nz(score[1], 0) >= 0 + +if longSignal and strategy.position_size == 0 + strategy.entry("N", strategy.long, qty=1, comment="na chain long") + +if flatSignal and strategy.position_size > 0 + strategy.close("N", comment="na chain flat") diff --git a/tests/gate-corpus/ok/validation__oca-exit-bracket-internal-cancel-01.pine b/tests/gate-corpus/ok/validation__oca-exit-bracket-internal-cancel-01.pine new file mode 100644 index 0000000..7ac8db0 --- /dev/null +++ b/tests/gate-corpus/ok/validation__oca-exit-bracket-internal-cancel-01.pine @@ -0,0 +1,39 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// OCA probe 01 — strategy.exit bracket (internal CANCEL semantics) +// +// Purpose: pin strategy.exit's built-in OCA-CANCEL bracket behavior. +// Pine v6 strategy.exit does not expose oca_type; the TP/SL pair it +// places is internally OCA-CANCEL (one fills → other cancels in same +// or next bar). This probe verifies engine bracket cancel timing +// matches TV. +// +// strategy.exit is intrinsically close-only, so once position is flat +// any unfilled bracket auto-cancels — no leaked-short trap. +// +// Trade shape: long-only RSI<30 cross-up, qty=1, ATR-wide bracket so +// volatile bars routinely sweep both sides. +// +// TV setup: 15m chart, ETH-USDT-USDT. +//@version=6 +strategy("PF OCA probe 01 - exit bracket cancel", shorttitle="OCA_p01_3WAY", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +atrVal = ta.atr(14) + +bool entryCond = ta.crossover(rsiVal, 30) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry") + +if strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.exit("X", from_entry="L", qty=1, + limit=entry + atrVal * 1.5, stop=entry - atrVal * 1.5, + oca_name="GRP") diff --git a/tests/gate-corpus/ok/validation__oca-multi-bracket-isolation-01.pine b/tests/gate-corpus/ok/validation__oca-multi-bracket-isolation-01.pine new file mode 100644 index 0000000..6268954 --- /dev/null +++ b/tests/gate-corpus/ok/validation__oca-multi-bracket-isolation-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// OCA probe 02 — multi-bracket via strategy.exit (internal CANCEL) +// +// Purpose: two strategy.exit brackets attached to same long entry, +// different ATR widths + different oca_name. Tests cross-bracket +// isolation: tight bracket fills first → only its own TP/SL cancel, +// the wider bracket continues running. Pine v6 strategy.exit uses +// internal OCA-CANCEL between TP and SL within the SAME oca_name. +// Different oca_name → independent. +// +// strategy.exit is close-only → no leaked-short trap. +// +// Trade shape: long-only RSI/EMA confluence, qty=2 entry, two brackets +// each qty=1. +// +// TV setup: 15m chart, ETH-USDT-USDT. +//@version=6 +strategy("PF OCA probe 02 - multi-bracket isolation", shorttitle="OCA_p02_MGP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=2, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +atrVal = ta.atr(14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and rsiVal < 60 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=2, comment="entry") + +if strategy.position_size > 0 + entry = strategy.position_avg_price + // Bracket A: tight ATR — hits more often + strategy.exit("X_A", from_entry="L", qty=1, + limit=entry + atrVal * 1.0, stop=entry - atrVal * 1.0, + oca_name="GRP_A") + // Bracket B: wider ATR — independent group + strategy.exit("X_B", from_entry="L", qty=1, + limit=entry + atrVal * 2.0, stop=entry - atrVal * 2.0, + oca_name="GRP_B") diff --git a/tests/gate-corpus/ok/validation__oca-raw-strategy-order-reduce-01.pine b/tests/gate-corpus/ok/validation__oca-raw-strategy-order-reduce-01.pine new file mode 100644 index 0000000..ab069a7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__oca-raw-strategy-order-reduce-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 44 - raw strategy.order OCA reduce +// +// Purpose: isolate strategy.order exits with shared oca_name and strategy.oca.reduce. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 44 - OCA reduce", shorttitle="PF_G44_OCA", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 7 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="oca source") + +if strategy.position_size > 0 + qty = math.abs(strategy.position_size) + entry = strategy.position_avg_price + strategy.order("TP", strategy.short, qty=qty, limit=entry * 1.004, + oca_name="BRACKET", oca_type=strategy.oca.reduce) + strategy.order("SL", strategy.short, qty=qty, stop=entry * 0.996, + oca_name="BRACKET", oca_type=strategy.oca.reduce) + +if strategy.position_size == 0 + strategy.cancel("TP") + strategy.cancel("SL") + +if strategy.position_size > 0 and hour == 13 and minute == 15 + strategy.close("L", comment="timeout") diff --git a/tests/gate-corpus/ok/validation__order-close-all-cancel-all-01.pine b/tests/gate-corpus/ok/validation__order-close-all-cancel-all-01.pine new file mode 100644 index 0000000..9bd2628 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-close-all-cancel-all-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 43 - cancel_all and close_all +// +// Purpose: isolate global pending-order cancellation and global position close. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 43 - cancel all close all", shorttitle="PF_G43_ALL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=2, + process_orders_on_close=false) + +// These pending orders should be removed before they can reasonably fill. +if hour == 4 and minute == 15 + strategy.entry("PENDING_L", strategy.long, qty=1, limit=close * 0.80, comment="pending low") + strategy.entry("PENDING_S", strategy.short, qty=1, limit=close * 1.20, comment="pending high") + +if hour == 4 and minute == 30 + strategy.cancel_all() + +// Open two entries, then flatten the whole position with close_all. +if hour == 5 and minute == 15 and strategy.position_size == 0 + strategy.entry("A", strategy.long, qty=1, comment="first") + +if hour == 5 and minute == 45 and strategy.position_size > 0 + strategy.entry("B", strategy.long, qty=1, comment="second") + +if hour == 6 and minute == 15 and strategy.position_size != 0 + strategy.close_all(comment="global flat", immediately=true) diff --git a/tests/gate-corpus/ok/validation__order-close-immediate-vs-next-bar-01.pine b/tests/gate-corpus/ok/validation__order-close-immediate-vs-next-bar-01.pine new file mode 100644 index 0000000..d101bd8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-close-immediate-vs-next-bar-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 42 - close immediately vs next-bar market close +// +// Purpose: compare strategy.close(..., immediately=true) with ordinary strategy.close(). +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 42 - close immediate", shorttitle="PF_G42_CLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 15 and strategy.position_size == 0 + strategy.entry("NEXT", strategy.long, qty=1, comment="normal close case") + +if hour == 3 and minute == 15 and strategy.position_size > 0 + strategy.close("NEXT", comment="normal close") + +if hour == 10 and minute == 15 and strategy.position_size == 0 + strategy.entry("IMM", strategy.long, qty=1, comment="immediate close case") + +if hour == 11 and minute == 15 and strategy.position_size > 0 + strategy.close("IMM", comment="immediate close", immediately=true) diff --git a/tests/gate-corpus/ok/validation__order-cross-entry-cancel-same-pass-01.pine b/tests/gate-corpus/ok/validation__order-cross-entry-cancel-same-pass-01.pine new file mode 100644 index 0000000..e1ccb07 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-cross-entry-cancel-same-pass-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 69 - entry and cancel same pass +// +// Purpose: compare source-order behavior for strategy.entry(... stop=...) and +// strategy.cancel(id) in the same Pine execution pass. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 69 - entry cancel same pass", shorttitle="PF_P69_ENTCANCEL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, stop=high + syminfo.mintick, comment="entry first cancel second") + strategy.cancel("L") + +if hour == 4 and minute == 15 and strategy.position_size == 0 + strategy.cancel("L2") + strategy.entry("L2", strategy.long, stop=high + syminfo.mintick, comment="cancel first entry second") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close_all(comment="long cleanup") + +if hour == 14 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, stop=low - syminfo.mintick, comment="short entry first cancel second") + strategy.cancel("S") + +if hour == 16 and minute == 15 and strategy.position_size == 0 + strategy.cancel("S2") + strategy.entry("S2", strategy.short, stop=low - syminfo.mintick, comment="short cancel first entry second") + +if hour == 18 and minute == 15 and strategy.position_size < 0 + strategy.close_all(comment="short cleanup") diff --git a/tests/gate-corpus/ok/validation__order-cross-entry-close-same-pass-01.pine b/tests/gate-corpus/ok/validation__order-cross-entry-close-same-pass-01.pine new file mode 100644 index 0000000..730b780 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-cross-entry-close-same-pass-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 68 - entry and close same pass +// +// Purpose: compare source-order behavior when strategy.entry and strategy.close +// for the same id are issued in the same Pine execution pass. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 68 - entry close same pass", shorttitle="PF_P68_ENTCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry first") + strategy.close("L", comment="close second") + +if hour == 6 and minute == 15 and strategy.position_size == 0 + strategy.close("L2", comment="close first") + strategy.entry("L2", strategy.long, qty=1, comment="entry second") + +if hour == 7 and minute == 15 and strategy.position_size > 0 + strategy.close_all(comment="cleanup") + +if hour == 12 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="short entry first") + strategy.close("S", comment="short close second") + +if hour == 18 and minute == 15 and strategy.position_size == 0 + strategy.close("S2", comment="short close first") + strategy.entry("S2", strategy.short, qty=1, comment="short entry second") + +if hour == 19 and minute == 15 and strategy.position_size < 0 + strategy.close_all(comment="short cleanup") diff --git a/tests/gate-corpus/ok/validation__order-cross-exit-close-same-pass-01.pine b/tests/gate-corpus/ok/validation__order-cross-exit-close-same-pass-01.pine new file mode 100644 index 0000000..a664d22 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-cross-exit-close-same-pass-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 70 - exit and close same pass +// +// Purpose: compare source-order behavior when a price-based strategy.exit and +// market strategy.close target the same open entry in the same pass. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 70 - exit close same pass", shorttitle="PF_P70_EXITCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.exit("X", "L", limit=entry * 1.02, stop=entry * 0.98, comment="exit first") + strategy.close("L", comment="close second") + +if hour == 6 and minute == 15 and strategy.position_size == 0 + strategy.entry("L2", strategy.long, qty=1, comment="long2") + +if hour == 6 and minute == 45 and strategy.position_size > 0 + entry = strategy.position_avg_price + strategy.close("L2", comment="close first") + strategy.exit("X2", "L2", limit=entry * 1.02, stop=entry * 0.98, comment="exit second") diff --git a/tests/gate-corpus/ok/validation__order-deferred-flip-guaranteed-gap-stops-01.pine b/tests/gate-corpus/ok/validation__order-deferred-flip-guaranteed-gap-stops-01.pine new file mode 100644 index 0000000..4f59143 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-deferred-flip-guaranteed-gap-stops-01.pine @@ -0,0 +1,63 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 95 - multi-cycle deferred-flip growth, sub-bar-precision-free +// +// Purpose: isolate whether the residual probes 52/63 PnL divergence is +// the deferred-flip growth rule itself or sub-bar fill precision. +// +// Probe 52 exercises the same mechanic but its SE/LE stops are placed at +// ``high[1] + tick`` / ``low[1] - tick``, which makes the fire price +// depend on TradingView's intra-bar 1m magnifier path. This probe replaces +// those stops with EXTREME levels (``high * 10`` for the short stop, +// ``low * 0.1`` for the long stop) that are GUARANTEED to gap through at +// the next bar's open — the fill price is always ``open``, regardless of +// any sub-bar path. Cross events are also time-based (deterministic), +// not EMA-derived, eliminating EMA precision drift. +// +// Each UTC day fires two cycles (down at 8:00, up at 14:00). The growth +// chain steps up by 1 every cycle (long N -> short N+1 -> long N+2 ...). +// A weekly forced-flat at Monday 00:00 caps the chain length at 14 +// cycles per week so the position notional stays well below +// initial_capital — without this, an unbounded chain on a 365-day +// window grows past TV's broker capital cap (~qty 288 with 1M capital) +// and TV starts splitting positions in ways unrelated to the +// deferred-flip rule we're testing. +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 95 - multi-cycle open-guaranteed stops", shorttitle="PF_P95_OG", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +isDown = hour == 8 and minute == 0 +isUp = hour == 14 and minute == 0 + +// Stop levels engineered so the fill price is always ``open`` of the next +// bar. SE short fires when ``low <= stop`` -> stop = ``high * 10`` is +// always >= low, fill = min(open, stop) = open. +// LE long fires when ``high >= stop`` -> stop = ``low * 0.1`` is always +// <= high, fill = max(open, stop) = open. +shortStop = high * 10.0 +longStop = low * 0.1 + +if isDown + strategy.entry("SE", strategy.short, qty=1, stop=shortStop, comment="open-guaranteed short") + strategy.cancel("LE") + +if isUp + strategy.entry("LE", strategy.long, qty=1, stop=longStop, comment="open-guaranteed long") + strategy.cancel("SE") + +if isDown and strategy.position_size > 0 + strategy.close("LE", comment="flip flat long") + +if isUp and strategy.position_size < 0 + strategy.close("SE", comment="flip flat short") + +if dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size != 0 + strategy.close_all(comment="weekly reset") diff --git a/tests/gate-corpus/ok/validation__order-deferred-flip-pooc-cross-bar-01.pine b/tests/gate-corpus/ok/validation__order-deferred-flip-pooc-cross-bar-01.pine new file mode 100644 index 0000000..a13feec --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-deferred-flip-pooc-cross-bar-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 96 - multi-cycle deferred-flip growth, cross-bar variant +// +// Purpose: same diagnostic as probe 95, but with ``process_orders_on_close=true`` +// so the ``strategy.close`` market exit fires on the SAME bar as the +// cross event (at that bar's close), while the stop entry still waits +// for the NEXT bar's open. This forces close and stop fire onto +// different bars — exercising the cross-bar deferred-flip path (probe 92's +// territory) rather than the same-bar paired-close path. +// +// If probe 95 (same-bar) and probe 96 (cross-bar) both reach excellent, +// the deferred-flip implementation is sound for both regimes and the +// residual probes 52 / 63 divergence is genuinely sub-bar fill +// precision, not the carry rule. +// +// Weekly forced-flat at Monday 00:00 caps the chain length at ~14 cycles +// per week so the position notional stays well below initial_capital +// (avoids TV's broker capital-cap split behaviour). +// +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 96 - multi-cycle POOC cross-bar", shorttitle="PF_P96_POOC", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=true) + +isDown = hour == 8 and minute == 0 +isUp = hour == 14 and minute == 0 + +// Open-guaranteed stop levels (see probe 95 for derivation). +shortStop = high * 10.0 +longStop = low * 0.1 + +if isDown + strategy.entry("SE", strategy.short, qty=1, stop=shortStop, comment="open-guaranteed short") + strategy.cancel("LE") + +if isUp + strategy.entry("LE", strategy.long, qty=1, stop=longStop, comment="open-guaranteed long") + strategy.cancel("SE") + +if isDown and strategy.position_size > 0 + strategy.close("LE", comment="flip flat long") + +if isUp and strategy.position_size < 0 + strategy.close("SE", comment="flip flat short") + +if dayofweek == 2 and hour == 0 and minute == 0 and strategy.position_size != 0 + strategy.close_all(comment="weekly reset") diff --git a/tests/gate-corpus/ok/validation__order-dual-four-bar-stop-no-close-01.pine b/tests/gate-corpus/ok/validation__order-dual-four-bar-stop-no-close-01.pine new file mode 100644 index 0000000..070f04e --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-four-bar-stop-no-close-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 78 - dual-side four-bar stop windows without close +// +// Purpose: isolate dual-side four-bar same-id stop modifications without market +// closes. This is the no-close control for probe 58. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 78 - dual four bar no close", shorttitle="PF_P78_DUAL4NC", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longWindow = hour == 2 and minute <= 45 and strategy.position_size == 0 +shortWindow = hour == 14 and minute <= 45 and strategy.position_size == 0 + +if longWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="dual four long stop") + +if shortWindow + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="dual four short stop") + +// Force a late flatten so TradingView exports closed trades, but keep it far +// away from the setup windows. +if strategy.position_size != 0 and hour == 23 and minute == 45 + strategy.close_all(comment="late flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-side-same-id-stop-no-cancel-01.pine b/tests/gate-corpus/ok/validation__order-dual-side-same-id-stop-no-cancel-01.pine new file mode 100644 index 0000000..0c7f77e --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-side-same-id-stop-no-cancel-01.pine @@ -0,0 +1,31 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 76 - dual-side same-id stop windows without cancel +// +// Purpose: isolate long and short pending stop entries coexisting while each +// side repeatedly modifies its own same-id stop price. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 76 - dual side no cancel", shorttitle="PF_P76_DUALNOCANCEL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longWindow = hour == 2 and minute <= 45 and strategy.position_size == 0 +shortWindow = hour == 14 and minute <= 45 and strategy.position_size == 0 + +if longWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="dual long stop") + +if shortWindow + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="dual short stop") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("LE", comment="long close") + +if strategy.position_size < 0 and hour == 18 and minute == 15 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-both-touch-priority-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-both-touch-priority-01.pine new file mode 100644 index 0000000..5076c58 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-both-touch-priority-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 80 - dual stop both-touch priority +// +// Purpose: isolate broker priority when both a long stop and a short stop +// entry are pending and the next bar touches both levels. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 80 - dual stop priority", shorttitle="PF_P80_STOPPRIO", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="near long stop") + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="near short stop") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="morning flat") + +if hour == 14 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE2", strategy.long, stop=close * 1.004, comment="farther long stop") + strategy.entry("SE2", strategy.short, stop=close * 0.996, comment="farther short stop") + +if strategy.position_size != 0 and hour == 18 and minute == 15 + strategy.close_all(comment="evening flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-cancel-rotation-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-cancel-rotation-01.pine new file mode 100644 index 0000000..307c746 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-cancel-rotation-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 63 - dual stop cancel rotation +// +// Purpose: isolate alternating long/short stop entries with explicit cancel of +// the opposite id, matching the order-lifecycle shape in parabolic-asr/52 while +// removing EMA/SAR state. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 63 - dual stop cancel rotation", shorttitle="PF_P63_DUALCANCEL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +useLongStop = hour < 12 + +if useLongStop + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="rotating long stop") + strategy.cancel("SE") +else + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="rotating short stop") + strategy.cancel("LE") + +if strategy.position_size > 0 and not useLongStop + strategy.close("LE", comment="long regime close") + +if strategy.position_size < 0 and useLongStop + strategy.close("SE", comment="short regime close") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-far-only-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-far-only-01.pine new file mode 100644 index 0000000..d753555 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-far-only-01.pine @@ -0,0 +1,22 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 82 - dual stop far only +// +// Purpose: isolate farther long/short stop competition from probe 80. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 82 - dual stop far only", shorttitle="PF_P82_FAR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 14 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=close * 1.004, comment="farther long stop") + strategy.entry("SE", strategy.short, stop=close * 0.996, comment="farther short stop") + +if strategy.position_size != 0 and hour == 18 and minute == 15 + strategy.close_all(comment="evening flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-near-only-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-near-only-01.pine new file mode 100644 index 0000000..4d81c20 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-near-only-01.pine @@ -0,0 +1,22 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 81 - dual stop near only +// +// Purpose: isolate near long/short stop competition from probe 80. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 81 - dual stop near only", shorttitle="PF_P81_NEAR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="near long stop") + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="near short stop") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="morning flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-open-high-first-path-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-open-high-first-path-01.pine new file mode 100644 index 0000000..e01b127 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-open-high-first-path-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 87 - dual stop open proximity, high first +// +// Purpose: collect natural bars where open is closer to high than low, so the +// expected TradingView path is O -> H -> L -> C. Long stop should win if path +// priority controls dual stop arbitration. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 87 - dual stop high first", shorttitle="PF_P87_HIGHPATH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +nearHigh = math.abs(high - open) < math.abs(open - low) + +if hour == 2 and minute == 45 and strategy.position_size == 0 and nearHigh + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="long stop") + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="short stop") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-open-low-first-path-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-open-low-first-path-01.pine new file mode 100644 index 0000000..053ab9a --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-open-low-first-path-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 86 - dual stop open proximity, low first +// +// Purpose: collect natural bars where open is closer to low than high, so the +// expected TradingView path is O -> L -> H -> C. Short stop should win if path +// priority controls dual stop arbitration. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 86 - dual stop low first", shorttitle="PF_P86_LOWPATH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +nearLow = math.abs(open - low) < math.abs(high - open) + +if hour == 2 and minute == 45 and strategy.position_size == 0 and nearLow + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="long stop") + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="short stop") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-open-tie-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-open-tie-01.pine new file mode 100644 index 0000000..6927df9 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-open-tie-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 83 - dual stop open/tie +// +// Purpose: isolate dual stop entries whose trigger levels are both effectively +// marketable on the next bar open. This tests open-phase/source-order behavior. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 83 - dual stop open tie", shorttitle="PF_P83_OPENTIE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 4 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=close, comment="long stop at close") + strategy.entry("SE", strategy.short, stop=close, comment="short stop at close") + +if strategy.position_size != 0 and hour == 8 and minute == 15 + strategy.close_all(comment="flat after open tie") + +if hour == 16 and minute == 45 and strategy.position_size == 0 + strategy.entry("SE2", strategy.short, stop=close, comment="short first at close") + strategy.entry("LE2", strategy.long, stop=close, comment="long second at close") + +if strategy.position_size != 0 and hour == 20 and minute == 15 + strategy.close_all(comment="flat after reverse tie") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-source-order-long-first-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-source-order-long-first-01.pine new file mode 100644 index 0000000..dbc3140 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-source-order-long-first-01.pine @@ -0,0 +1,23 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 84 - dual stop source order, long first +// +// Purpose: test whether source order affects dual stop priority when both +// stops are close to price. Long stop is submitted before short stop. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 84 - dual stop long first", shorttitle="PF_P84_LONGFIRST", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 45 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="long first") + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="short second") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-dual-stop-source-order-short-first-01.pine b/tests/gate-corpus/ok/validation__order-dual-stop-source-order-short-first-01.pine new file mode 100644 index 0000000..28bb5f3 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-dual-stop-source-order-short-first-01.pine @@ -0,0 +1,23 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 85 - dual stop source order, short first +// +// Purpose: test whether source order affects dual stop priority when both +// stops are close to price. Short stop is submitted before long stop. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 85 - dual stop short first", shorttitle="PF_P85_SHORTFIRST", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 45 and strategy.position_size == 0 + strategy.entry("SE", strategy.short, stop=close * 0.998, comment="short first") + strategy.entry("LE", strategy.long, stop=close * 1.002, comment="long second") + +if strategy.position_size != 0 and hour == 6 and minute == 15 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-entry-implicit-reversal-exit-01.pine b/tests/gate-corpus/ok/validation__order-entry-implicit-reversal-exit-01.pine new file mode 100644 index 0000000..891a766 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-entry-implicit-reversal-exit-01.pine @@ -0,0 +1,29 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 55 - strategy.entry implicit reversal exit +// +// Purpose: isolate TradingView's reversal behavior: an opposite strategy.entry +// order is sized as requested_qty + current_opposite_position_size, closes the +// prior position at the reversal fill, and opens the new side. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 55 - entry reversal implicit exit", shorttitle="PF_P55_REV", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("S", strategy.short, qty=1, comment="reverse short") + +if hour == 1 and minute == 15 and strategy.position_size < 0 + strategy.entry("L", strategy.long, qty=1, comment="reverse long") + +if hour == 1 and minute == 45 and strategy.position_size != 0 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-flip-stop-no-paired-close-01.pine b/tests/gate-corpus/ok/validation__order-flip-stop-no-paired-close-01.pine new file mode 100644 index 0000000..c71a8f4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-flip-stop-no-paired-close-01.pine @@ -0,0 +1,37 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 92 — flip via stop entry, NO paired strategy.close +// +// Purpose: probe 72 reproduces TV's |old|+qty growth quirk. Probe 72 places +// ``strategy.entry("S", short, stop=...)`` AND ``strategy.close("L")`` on the +// same bar. This probe removes the paired ``strategy.close`` so only the +// stop entry remains. If qty in TV's export is still 2 for the post-flip +// short round trip, the paired close is NOT the trigger -- the stop-entry +// flip alone causes growth. If qty stays 1, the paired +// (entry-stop + market-close-same-bar) is the discriminator. +// +// Trade shape: open long at 0:15 (market, fresh from flat). Place short +// stop at 0:45 — the stop level is just below the bar low so it almost +// always fires next bar. NO strategy.close call. Cleanup short at 12:00. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Properties dialog: Pyramiding=1, +// Order size = 1 contract. Export the trade list to trades.csv in this +// folder. +//@version=6 +strategy("PF probe 92 - flip stop no paired close", shorttitle="PF_P92_NOCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("S", strategy.short, qty=1, stop=low - syminfo.mintick, comment="flip via stop") + +if hour == 12 and minute == 0 and strategy.position_size != 0 + strategy.close_all(comment="cleanup") diff --git a/tests/gate-corpus/ok/validation__order-market-close-fill-basis-01.pine b/tests/gate-corpus/ok/validation__order-market-close-fill-basis-01.pine new file mode 100644 index 0000000..bed1118 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-market-close-fill-basis-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 59 - market close fill basis +// +// Purpose: isolate strategy.close(id) fill timing with process_orders_on_close=false. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 59 - market close fill basis", shorttitle="PF_P59_CLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 3 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="long") + +if hour == 5 and minute == 15 and strategy.position_size > 0 + strategy.close("L", comment="market close long") + +if hour == 15 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="short") + +if hour == 17 and minute == 15 and strategy.position_size < 0 + strategy.close("S", comment="market close short") diff --git a/tests/gate-corpus/ok/validation__order-one-side-four-bar-far-opposite-01.pine b/tests/gate-corpus/ok/validation__order-one-side-four-bar-far-opposite-01.pine new file mode 100644 index 0000000..0356b30 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-one-side-four-bar-far-opposite-01.pine @@ -0,0 +1,28 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 79 - one-side four-bar stop with far opposite pending +// +// Purpose: isolate whether merely having the opposite pending stop present +// changes same-id stop modification semantics for the active side. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 79 - far opposite pending", shorttitle="PF_P79_FAROPP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longWindow = hour == 2 and minute <= 45 and strategy.position_size == 0 + +// Far short stop should remain pending but should not be touched in normal data. +if hour == 2 and minute == 0 and strategy.position_size == 0 + strategy.entry("SE", strategy.short, stop=low * 0.5, comment="far opposite short") + +if longWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="four-bar long stop") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="long close") diff --git a/tests/gate-corpus/ok/validation__order-opposite-entry-close-same-pass-01.pine b/tests/gate-corpus/ok/validation__order-opposite-entry-close-same-pass-01.pine new file mode 100644 index 0000000..c01d5f9 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-opposite-entry-close-same-pass-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 72 - opposite entry and close same pass +// +// Purpose: isolate source-order behavior when an opposite strategy.entry and +// strategy.close target the current position in the same pass. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 72 - opposite entry close", shorttitle="PF_P72_OPPCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("S", strategy.short, qty=1, stop=low - syminfo.mintick, comment="opposite stop first") + strategy.close("L", comment="close long second") + +if hour == 6 and minute == 15 and strategy.position_size == 0 + strategy.entry("L2", strategy.long, qty=1, comment="open long2") + +if hour == 6 and minute == 45 and strategy.position_size > 0 + strategy.close("L2", comment="close long first") + strategy.entry("S2", strategy.short, qty=1, stop=low - syminfo.mintick, comment="opposite stop second") + +if hour == 12 and minute == 15 and strategy.position_size < 0 + strategy.close_all(comment="cleanup short") diff --git a/tests/gate-corpus/ok/validation__order-percent-equity-cash-commission-01.pine b/tests/gate-corpus/ok/validation__order-percent-equity-cash-commission-01.pine new file mode 100644 index 0000000..7f10e07 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-percent-equity-cash-commission-01.pine @@ -0,0 +1,21 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 45 - percent-of-equity sizing plus cash commission +// +// Purpose: isolate default_qty_type=strategy.percent_of_equity and cash_per_order commission. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 45 - commission sizing", shorttitle="PF_G45_SIZE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.cash_per_order, commission_value=1.5, slippage=0, + default_qty_type=strategy.percent_of_equity, default_qty_value=25, pyramiding=1, + process_orders_on_close=false) + +if hour == 8 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, comment="default percent sizing") + +if hour == 12 and minute == 15 and strategy.position_size > 0 + strategy.close("L", comment="sizing close") diff --git a/tests/gate-corpus/ok/validation__order-process-on-close-false-01.pine b/tests/gate-corpus/ok/validation__order-process-on-close-false-01.pine new file mode 100644 index 0000000..fdc3540 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-process-on-close-false-01.pine @@ -0,0 +1,35 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Process-orders probe 01 — process_orders_on_close=false (TV default) +// +// Purpose: pin TV-default order processing semantics. With +// process_orders_on_close=false, a market entry submitted on the +// signal bar must fill at the NEXT bar's open. Companion fixture +// process-orders-probe-02 has identical logic with =true; cross- +// compare reveals next-bar-open shift. +// +// Trade shape: simple long-only EMA crossover gated by RSI<70, +// exit on EMA cross-under. ≥10 cycles over 36k bars. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Manual TV capture later. +//@version=6 +strategy("PF process-orders 01 - on_close=false", shorttitle="POC_p01_FALSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and rsiVal < 70 +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit") diff --git a/tests/gate-corpus/ok/validation__order-process-on-close-true-01.pine b/tests/gate-corpus/ok/validation__order-process-on-close-true-01.pine new file mode 100644 index 0000000..5c80294 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-process-on-close-true-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Process-orders probe 02 — process_orders_on_close=true (corpus default) +// +// Purpose: companion to probe 01 with identical signal logic but +// process_orders_on_close=true. Market entry submitted on the +// signal bar fills at the SAME bar's close. Cross-compare to +// probe-01 reveals next-bar-open shift between TV-default and +// current corpus default. +// +// Trade shape: identical to probe 01 — long-only EMA crossover +// gated by RSI<70, exit on EMA cross-under. ≥10 cycles over 36k +// bars. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Manual TV capture later. +//@version=6 +strategy("PF process-orders 02 - on_close=true", shorttitle="POC_p02_TRUE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=true) + +rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and rsiVal < 70 +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit") diff --git a/tests/gate-corpus/ok/validation__order-range-expansion-pending-stop-01.pine b/tests/gate-corpus/ok/validation__order-range-expansion-pending-stop-01.pine new file mode 100644 index 0000000..35c2062 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-range-expansion-pending-stop-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 97c — range-expansion gated pending stop entry (isolated from probe 97) +// Clean-room: written from validation goal, not from source. +// +// Purpose: isolate the range-expansion predicate + pending stop entry +// lifecycle from probe 97. Drops bracket, drops cap. Uses +// `strategy.entry(..., stop=high[1])` for long arm and +// `strategy.entry(..., stop=low[1])` for short arm, gated by an +// ATR-relative range expansion test on the previous bar. Cancels the +// pending stop when the gate negates. If TV vs engine diverge here, +// either the range-expansion arithmetic (atr[1] history access) or +// the pending-stop placement / next-bar cancel lifecycle is the bug. +// +// Validation goal: bit-exact trade list. Pass = same set of bars trigger +// pending stop entries, same set get cancelled before fill, same fills. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults. Export "List of trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 97c - range-expansion pending-stop isolate", shorttitle="PF_p97c_RNG", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false, calc_on_order_fills=false) + +// ---- inputs -------------------------------------------------------- +int i_atr_len = input.int(14, "ATR length", minval=1) +float i_atr_mult = input.float(1.0, "Range/ATR threshold", minval=0.1) + +// ---- range-expansion detection (mirrors probe 97 logic) ------------ +float prev_range = high[1] - low[1] +float atr_now = ta.atr(i_atr_len) +float atr_prev = atr_now[1] +bool expansion = not na(atr_prev) and prev_range > atr_prev * i_atr_mult + +bool prev_up = close[1] > open[1] +bool prev_down = close[1] < open[1] + +bool arm_long = expansion and prev_up +bool arm_short = expansion and prev_down + +// ---- pending stop entries; cancelled when arm negates -------------- +if arm_long + strategy.entry("LongOnRng", strategy.long, stop=high[1]) +else + strategy.cancel("LongOnRng") + +if arm_short + strategy.entry("ShortOnRng", strategy.short, stop=low[1]) +else + strategy.cancel("ShortOnRng") diff --git a/tests/gate-corpus/ok/validation__order-same-id-entry-close-same-bar-01.pine b/tests/gate-corpus/ok/validation__order-same-id-entry-close-same-bar-01.pine new file mode 100644 index 0000000..26ea1c8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-entry-close-same-bar-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 65 - same-id entry and close on same bar +// +// Purpose: determine how TV resolves a same-pass same-id entry + close when an +// existing position with that id is already open. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 65 - same id entry close", shorttitle="PF_P65_ENTCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("L", strategy.long, qty=1, comment="same-pass add long") + strategy.close("L", comment="same-pass close long") + +if hour == 6 and minute == 15 and strategy.position_size != 0 + strategy.close_all(comment="cleanup long") + +if hour == 12 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="open short") + +if hour == 12 and minute == 45 and strategy.position_size < 0 + strategy.entry("S", strategy.short, qty=1, comment="same-pass add short") + strategy.close("S", comment="same-pass close short") + +if hour == 18 and minute == 15 and strategy.position_size != 0 + strategy.close_all(comment="cleanup short") diff --git a/tests/gate-corpus/ok/validation__order-same-id-market-entry-repeat-01.pine b/tests/gate-corpus/ok/validation__order-same-id-market-entry-repeat-01.pine new file mode 100644 index 0000000..46bfe7a --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-market-entry-repeat-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 64 - same-id market entry repeat +// +// Purpose: determine whether repeated same-id strategy.entry market calls on +// the same bar create one order, modify one order, or multiple entries. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 64 - same id market repeat", shorttitle="PF_P64_SAMEENTRY", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=3, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="first same-id") + strategy.entry("L", strategy.long, qty=1, comment="second same-id") + +if hour == 1 and minute == 15 and strategy.position_size > 0 + strategy.close("L", comment="close same-id") + +if hour == 12 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="first same-id short") + strategy.entry("S", strategy.short, qty=1, comment="second same-id short") + +if hour == 13 and minute == 15 and strategy.position_size < 0 + strategy.close("S", comment="close same-id short") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-after-flat-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-after-flat-01.pine new file mode 100644 index 0000000..a4ecec1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-after-flat-01.pine @@ -0,0 +1,31 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 75 - same-id stop after flat +// +// Purpose: isolate rebuilding a same-id stop entry after a prior position with +// the same id was closed. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 75 - stop after flat", shorttitle="PF_P75_AFTERFLAT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +firstWindow = hour == 2 and minute <= 45 and strategy.position_size == 0 +secondWindow = hour == 8 and minute <= 45 and strategy.position_size == 0 + +if firstWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="first window stop") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="first close") + +if secondWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="second window stop") + +if hour == 12 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="second close") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-cross-before-modify-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-cross-before-modify-01.pine new file mode 100644 index 0000000..31493c8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-cross-before-modify-01.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 62 - same-id stop cross before modify +// +// Purpose: isolate whether an already-touched pending stop entry fills before +// later same-id modifications can replace it. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 62 - stop cross before modify", shorttitle="PF_P62_STOPCROSS", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// First submit a stop close to the current market, then submit a farther stop +// with the same id on the next setup bar if no position exists yet. +longFirst = hour == 3 and minute == 15 and strategy.position_size == 0 +longModify = hour == 3 and minute == 30 and strategy.position_size == 0 +shortFirst = hour == 15 and minute == 15 and strategy.position_size == 0 +shortModify = hour == 15 and minute == 30 and strategy.position_size == 0 + +if longFirst + strategy.entry("LE", strategy.long, stop=close, comment="touchable long stop") + +if longModify + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="modified long stop") + +if shortFirst + strategy.entry("SE", strategy.short, stop=close, comment="touchable short stop") + +if shortModify + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="modified short stop") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("LE", comment="long close") + +if strategy.position_size < 0 and hour == 18 and minute == 15 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-minute-zero-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-minute-zero-01.pine new file mode 100644 index 0000000..7261a3f --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-minute-zero-01.pine @@ -0,0 +1,28 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 73 - same-id stop at minute zero only +// +// Purpose: isolate whether setup bars at minute == 0 behave differently from +// the 15/30/45 minute bars used by probe 61. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 73 - minute zero stop", shorttitle="PF_P73_MIN0", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 0 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="minute zero long stop") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="long close") + +if hour == 14 and minute == 0 and strategy.position_size == 0 + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="minute zero short stop") + +if hour == 18 and minute == 15 and strategy.position_size < 0 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-modification-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-modification-01.pine new file mode 100644 index 0000000..6cc4779 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-modification-01.pine @@ -0,0 +1,31 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 58 - same-id stop modification +// +// Purpose: isolate repeated strategy.entry calls with the same id and changing +// stop price. Only the latest pending stop should be live. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 58 - same id stop modify", shorttitle="PF_P58_MODSTOP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longSetup = hour == 2 and minute <= 45 and strategy.position_size == 0 +shortSetup = hour == 14 and minute <= 45 and strategy.position_size == 0 + +if longSetup + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="moving long stop") + +if shortSetup + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="moving short stop") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("LE", comment="long close") + +if strategy.position_size < 0 and hour == 18 and minute == 15 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-raise-only-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-raise-only-01.pine new file mode 100644 index 0000000..b900fa4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-raise-only-01.pine @@ -0,0 +1,31 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 61 - same-id stop raise only +// +// Purpose: isolate repeated strategy.entry calls with the same id where a stop +// order is only moved farther in the same direction before it fills. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 61 - same id stop raise", shorttitle="PF_P61_STOPRAISE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longSetup = hour == 2 and (minute == 15 or minute == 30 or minute == 45) and strategy.position_size == 0 +shortSetup = hour == 14 and (minute == 15 or minute == 30 or minute == 45) and strategy.position_size == 0 + +if longSetup + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="raised long stop") + +if shortSetup + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="lowered short stop") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("LE", comment="long close") + +if strategy.position_size < 0 and hour == 18 and minute == 15 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-same-id-stop-window-four-bars-01.pine b/tests/gate-corpus/ok/validation__order-same-id-stop-window-four-bars-01.pine new file mode 100644 index 0000000..cff2b18 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-same-id-stop-window-four-bars-01.pine @@ -0,0 +1,24 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 74 - same-id stop window four bars +// +// Purpose: isolate four consecutive same-id stop modifications in a single +// direction, including minute 0/15/30/45, without short-side interaction. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 74 - four bar stop window", shorttitle="PF_P74_WINDOW", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +longWindow = hour == 2 and minute <= 45 and strategy.position_size == 0 + +if longWindow + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="four-bar long stop") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="long close") diff --git a/tests/gate-corpus/ok/validation__order-stale-stop-after-close-no-cancel-01.pine b/tests/gate-corpus/ok/validation__order-stale-stop-after-close-no-cancel-01.pine new file mode 100644 index 0000000..24b719a --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-stale-stop-after-close-no-cancel-01.pine @@ -0,0 +1,29 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 77 - dual-side stop windows with close and no cancel +// +// Purpose: isolate stale opposite pending stops around market closes. This is +// closer to probe 58 than probe 76 because positions are explicitly closed, +// but it avoids four-bar repeated modification windows. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 77 - stale stop after close", shorttitle="PF_P77_STALESTOP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +if hour == 2 and minute == 15 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=high + syminfo.mintick, comment="long stop") + +if hour == 6 and minute == 15 and strategy.position_size > 0 + strategy.close("LE", comment="long close") + +if hour == 14 and minute == 15 and strategy.position_size == 0 + strategy.entry("SE", strategy.short, stop=low - syminfo.mintick, comment="short stop") + +if hour == 18 and minute == 15 and strategy.position_size < 0 + strategy.close("SE", comment="short close") diff --git a/tests/gate-corpus/ok/validation__order-stop-cancel-no-regime-close-01.pine b/tests/gate-corpus/ok/validation__order-stop-cancel-no-regime-close-01.pine new file mode 100644 index 0000000..32b7f0c --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-stop-cancel-no-regime-close-01.pine @@ -0,0 +1,47 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 94 — re-issued stop entry + paired cancel, NO regime close +// +// Purpose: probe 52 reproduces TV's |old|+qty growth quirk and adds +// repeated ``strategy.close(opposite_id)`` blocks that fire on regime +// change. This probe strips those regime closes so only the +// ``strategy.entry`` re-issuance and ``strategy.cancel(opposite)`` +// remain. The flip then has to come from the stop entry firing +// against an existing opposite position (no regime close to reset +// position to flat first). +// +// If qty in TV's export grows like probe 52 (1, 2, 3, 4, ...): the +// trigger lives in the (re-issue + cancel + flip-via-stop) combination, +// and regime closes are irrelevant. The engine fix can ignore them. +// +// If qty stays 1 (or 2 for clean flips): the regime close is part of +// the trigger -- in which case the discriminator involves +// ``strategy.close`` interacting with the next-bar pending stop. +// +// Trade shape: alternating MA crossover that places long stops when +// fast > slow and short stops when fast < slow, each side cancelling +// the opposite pending id every bar. NO ``strategy.close`` calls. +// +// TV setup: 15m chart, ETH-USDT-USDT. Properties dialog: Pyramiding=1, +// Order size = 1 contract. Export trades.csv. +//@version=6 +strategy("PF probe 94 - stop entry cancel no regime close", shorttitle="PF_P94_NORC", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +fast = ta.ema(close, 8) +slow = ta.ema(close, 21) + +longStop = high[1] +shortStop = low[1] + +if fast > slow + strategy.entry("LE", strategy.long, stop=longStop, comment="live long stop") + strategy.cancel("SE") +else + strategy.entry("SE", strategy.short, stop=shortStop, comment="live short stop") + strategy.cancel("LE") diff --git a/tests/gate-corpus/ok/validation__order-stop-entry-cancel-opposite-01.pine b/tests/gate-corpus/ok/validation__order-stop-entry-cancel-opposite-01.pine new file mode 100644 index 0000000..1217640 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-stop-entry-cancel-opposite-01.pine @@ -0,0 +1,35 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 52 - stop entry plus opposite cancel +// +// Purpose: isolate the Parabolic SAR pattern of placing one stop entry and +// cancelling the opposite pending id on confirmed bars. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 52 - stop entry cancel", shorttitle="PF_P52_CANCEL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +fast = ta.ema(close, 8) +slow = ta.ema(close, 21) + +longStop = high[1] +shortStop = low[1] + +if fast > slow + strategy.entry("LE", strategy.long, stop=longStop, comment="live long stop") + strategy.cancel("SE") +else + strategy.entry("SE", strategy.short, stop=shortStop, comment="live short stop") + strategy.cancel("LE") + +if strategy.position_size > 0 and fast < slow + strategy.close("LE", comment="flip flat long") + +if strategy.position_size < 0 and fast > slow + strategy.close("SE", comment="flip flat short") diff --git a/tests/gate-corpus/ok/validation__order-stop-entry-reversal-grouping-01.pine b/tests/gate-corpus/ok/validation__order-stop-entry-reversal-grouping-01.pine new file mode 100644 index 0000000..3e39dbb --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-stop-entry-reversal-grouping-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 57 - stop-entry reversal grouping +// +// Purpose: isolate stop-entry reversals after a small pyramided position. This +// separates reversal/report grouping from the larger cancel-opposite probe. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 57 - stop reversal grouping", shorttitle="PF_P57_STOPREV", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=2, + process_orders_on_close=false) + +// Build a two-lot long early in the day, then reverse via a short stop that is +// intentionally marketable on the next bar. Later reverse back via a long stop. +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L1", strategy.long, qty=1, comment="long lot 1") + +if hour == 0 and minute == 30 and strategy.position_size > 0 + strategy.entry("L2", strategy.long, qty=1, comment="long lot 2") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("SREV", strategy.short, qty=1, stop=high, comment="short stop reversal") + +if hour == 1 and minute == 15 and strategy.position_size < 0 + strategy.entry("LREV", strategy.long, qty=1, stop=low, comment="long stop reversal") + +if hour == 1 and minute == 45 and strategy.position_size != 0 + strategy.close_all(comment="flat") diff --git a/tests/gate-corpus/ok/validation__order-stop-entry-touch-boundary-01.pine b/tests/gate-corpus/ok/validation__order-stop-entry-touch-boundary-01.pine new file mode 100644 index 0000000..b3cff01 --- /dev/null +++ b/tests/gate-corpus/ok/validation__order-stop-entry-touch-boundary-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 53 - stop entry touch boundary +// +// Purpose: isolate whether stop entries are considered filled when the stop +// price is exactly at or one tick beyond recent high/low boundaries. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 53 - stop touch boundary", shorttitle="PF_P53_TOUCH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +tick = syminfo.mintick +longStop = high[1] + tick +shortStop = low[1] - tick + +// Alternate long/short stop probes by UTC hour without using bar_index. +longWindow = hour == 2 and minute == 15 +shortWindow = hour == 14 and minute == 15 + +if longWindow and strategy.position_size == 0 + strategy.entry("LE", strategy.long, stop=longStop, comment="one tick over high") + strategy.cancel("SE") + +if shortWindow and strategy.position_size == 0 + strategy.entry("SE", strategy.short, stop=shortStop, comment="one tick under low") + strategy.cancel("LE") + +if strategy.position_size > 0 and hour == 6 and minute == 15 + strategy.close("LE", comment="long timeout") + +if strategy.position_size < 0 and hour == 18 and minute == 15 + strategy.close("SE", comment="short timeout") diff --git a/tests/gate-corpus/ok/validation__pyramid-cash-fractional-commission-01.pine b/tests/gate-corpus/ok/validation__pyramid-cash-fractional-commission-01.pine new file mode 100644 index 0000000..ade9c61 --- /dev/null +++ b/tests/gate-corpus/ok/validation__pyramid-cash-fractional-commission-01.pine @@ -0,0 +1,37 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Pyramid carry probe 02 — cash sizing + fractional commission +// +// Purpose: default_qty_type=cash with cash_per_contract commission +// over fractional contract sizes. Each pyramid add re-evaluates +// cash → contract conversion at current price; commission is +// charged per (fractional) contract. Drives rounding-drift +// parity-anomaly between TV and runtime. +// +// Trade shape: pyramiding=3, long-only entries on EMA cross-up +// gated by RSI band; cash size 50000 yields fractional contract +// counts at ETH prices ($1k–$5k typical). Each session-end +// (hour==23, minute==45) closes all. ≥10 cycles over 36k bars. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Manual TV capture later. +//@version=6 +strategy("PF Pyramid carry 02 - cash fractional", shorttitle="PYR_p02_CASH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.cash_per_contract, commission_value=0.05, slippage=0, + default_qty_type=strategy.cash, default_qty_value=50000, pyramiding=3, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and rsiVal > 40 and rsiVal < 70 + +if entryCond + strategy.entry("L", strategy.long, comment="cash add") + +if hour == 23 and minute == 45 + strategy.close_all(comment="session close") diff --git a/tests/gate-corpus/ok/validation__pyramid-close-id-grouping-01.pine b/tests/gate-corpus/ok/validation__pyramid-close-id-grouping-01.pine new file mode 100644 index 0000000..2a245e5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__pyramid-close-id-grouping-01.pine @@ -0,0 +1,40 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 60 - pyramiding close by id grouping +// +// Purpose: isolate how strategy.close(id) reports trades when multiple entries +// with the same id have accumulated under pyramiding. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF probe 60 - pyramiding close id", shorttitle="PF_P60_PYRCLOSE", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=3, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="lot 1") + +if hour == 0 and minute == 30 and strategy.position_size > 0 + strategy.entry("L", strategy.long, qty=1, comment="lot 2") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("L", strategy.long, qty=1, comment="lot 3") + +if hour == 1 and minute == 15 and strategy.position_size > 0 + strategy.close("L", comment="close all L") + +if hour == 12 and minute == 15 and strategy.position_size == 0 + strategy.entry("S", strategy.short, qty=1, comment="short lot 1") + +if hour == 12 and minute == 30 and strategy.position_size < 0 + strategy.entry("S", strategy.short, qty=1, comment="short lot 2") + +if hour == 12 and minute == 45 and strategy.position_size < 0 + strategy.entry("S", strategy.short, qty=1, comment="short lot 3") + +if hour == 13 and minute == 15 and strategy.position_size < 0 + strategy.close("S", comment="close all S") diff --git a/tests/gate-corpus/ok/validation__pyramid-deferred-flip-close-all-01.pine b/tests/gate-corpus/ok/validation__pyramid-deferred-flip-close-all-01.pine new file mode 100644 index 0000000..2dc5d2a --- /dev/null +++ b/tests/gate-corpus/ok/validation__pyramid-deferred-flip-close-all-01.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Pyramid carry probe 01 — deferred flip across close_all +// +// Purpose: pyramiding=4 with alternating market and stop entries, +// punctuated by `strategy.close_all` mid-session. Tests deferred- +// flip carry: when a stop entry queued the prior bar would flip +// the position, but `close_all` fires the same bar, the runtime +// must correctly sequence (close-then-flip vs flip-then-close) +// across the pyramid stack. +// +// Trade shape: alternating long market entries (RSI cross-up) and +// short stop entries (break of prior low) up to pyramiding=4. Once +// per session (hour==21, minute==45) we close_all, expecting any +// pending stop entries queued by previous bar to carry into the +// next bar after the flat reset. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Manual TV capture later. +//@version=6 +strategy("PF Pyramid carry 01 - deferred flip", shorttitle="PYR_p01_FLIP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=4, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) +atrVal = ta.atr(14) + +bool longCond = ta.crossover(emaFast, emaSlow) and rsiVal < 70 +bool shortStop = ta.crossunder(rsiVal, 50) + +if longCond + strategy.entry("L", strategy.long, qty=1, comment="add long market") + +if shortStop + strategy.entry("S", strategy.short, qty=1, stop=low - atrVal * 0.25, comment="flip short stop") + +if hour == 21 and minute == 45 + strategy.close_all(comment="session close_all") diff --git a/tests/gate-corpus/ok/validation__pyramid-flip-stop-pyramiding-2-01.pine b/tests/gate-corpus/ok/validation__pyramid-flip-stop-pyramiding-2-01.pine new file mode 100644 index 0000000..21e2c2e --- /dev/null +++ b/tests/gate-corpus/ok/validation__pyramid-flip-stop-pyramiding-2-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 93 — same as probe 72 but with pyramiding=2 +// +// Purpose: probe 72 (pyramiding=1) reproduces TV's |old|+qty growth quirk +// with qty alternating 1/2. validation/57 (pyramiding=2) does NOT show +// growth despite using stop-entry flips. This probe holds probe 72's +// pattern constant and changes only the pyramiding value to 2 to test +// whether pyramiding=1 is part of the trigger condition. +// +// If qty in TV's export stays 1 (or 2 for legitimate flips that don't +// grow further across days), pyramiding=1 is part of the trigger and the +// engine fix can scope to "pyramiding==1 priced-entry flips". If qty +// still grows like probe 72, pyramiding is irrelevant. +// +// Trade shape: identical to probe 72 (open long at 0:15, place opposite +// stop at 0:45 with paired close, cleanup short at 12:00) except +// ``pyramiding=2``. +// +// TV setup: 15m chart, ETH-USDT-USDT. Properties dialog: Pyramiding=2, +// Order size = 1 contract. Export trades.csv. +//@version=6 +strategy("PF probe 93 - flip stop pyramiding 2", shorttitle="PF_P93_PYR2", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=2, + process_orders_on_close=false) + +if hour == 0 and minute == 15 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="open long") + +if hour == 0 and minute == 45 and strategy.position_size > 0 + strategy.entry("S", strategy.short, qty=1, stop=low - syminfo.mintick, comment="opposite stop first") + strategy.close("L", comment="close long second") + +if hour == 6 and minute == 15 and strategy.position_size == 0 + strategy.entry("L2", strategy.long, qty=1, comment="open long2") + +if hour == 6 and minute == 45 and strategy.position_size > 0 + strategy.close("L2", comment="close long first") + strategy.entry("S2", strategy.short, qty=1, stop=low - syminfo.mintick, comment="opposite stop second") + +if hour == 12 and minute == 15 and strategy.position_size < 0 + strategy.close_all(comment="cleanup short") diff --git a/tests/gate-corpus/ok/validation__recompute-alma-sar-corr-magnifier-01.pine b/tests/gate-corpus/ok/validation__recompute-alma-sar-corr-magnifier-01.pine new file mode 100644 index 0000000..9348937 --- /dev/null +++ b/tests/gate-corpus/ok/validation__recompute-alma-sar-corr-magnifier-01.pine @@ -0,0 +1,36 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// TA recompute probe 02 — untested TA classes under magnifier +// +// Purpose: exercise three TA classes the C++ gap report flagged as +// having zero save/restore coverage: ALMA, Parabolic SAR, and rolling +// Correlation. Magnifier is ON to drive the recompute paths on every +// sub-bar tick. Drift here points at first-emission landmines in +// classes whose internal state has never been round-tripped. +// +// Trade shape: long-only. Entry when ALMA crosses up through close, +// SAR flips below price, and price/volume correlation is positive. +// Exit on SAR flip back above close. +// +// TV setup: 15m chart, ETH-USDT-USDT, magnifier ON in TV settings. +// Export trades to trades.csv. +//@version=6 +strategy("PF TA recompute probe 02 - untested classes", shorttitle="TArec_p02_UNT", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=true, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +almaVal = ta.alma(close, 14, 0.85, 6) +sarVal = ta.sar(0.02, 0.02, 0.2) +corrVal = ta.correlation(close, volume, 20) + +bool bullEntry = ta.crossover(close, almaVal) and sarVal < close and corrVal > 0.0 +bool bearExit = sarVal > close + +if bullEntry and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if bearExit and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__recompute-mtf-rsi-macd-bb-01.pine b/tests/gate-corpus/ok/validation__recompute-mtf-rsi-macd-bb-01.pine new file mode 100644 index 0000000..0344d89 --- /dev/null +++ b/tests/gate-corpus/ok/validation__recompute-mtf-rsi-macd-bb-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// TA recompute probe 01 — MTF RSI + MACD + BBands +// +// Purpose: force per-class TA save/restore on every MTF tick. RSI, +// MACD-histogram and Bollinger Bands middle are evaluated INSIDE +// separate request.security callables on the 1H timeframe from a 15m +// chart. Each MTF advance makes the runtime push/pop the TA series +// state for three different classes within the same security context +// — drift here points at per-class saved-state corruption (C++ gap +// #5). +// +// Trade shape: long-only. Entry when all three HTF oscillators agree +// bullish: RSI > 55, MACD histogram > 0, close > BB middle. Exit on +// any disagreement. Combined-agreement gate keeps trade count modest +// and pins the consensus bar. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export trades to trades.csv. +//@version=6 +strategy("PF TA recompute probe 01 - MTF RSI MACD BB", shorttitle="TArec_p01_MTF", overlay=true, + initial_capital=1000000, currency=currency.USD, use_bar_magnifier=false, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +f_rsi(series float src, int len) => + ta.rsi(src, len) + +f_macd_hist(series float src, int fast, int slow, int sig) => + float macdLine = ta.ema(src, fast) - ta.ema(src, slow) + float sigLine = ta.ema(macdLine, sig) + macdLine - sigLine + +f_bb_mid(series float src, int len) => + ta.sma(src, len) + +float htfRsi = request.security(syminfo.tickerid, "60", f_rsi(close, 14), lookahead=barmerge.lookahead_off) +float htfHist = request.security(syminfo.tickerid, "60", f_macd_hist(close, 12, 26, 9), lookahead=barmerge.lookahead_off) +float htfBbM = request.security(syminfo.tickerid, "60", f_bb_mid(close, 20), lookahead=barmerge.lookahead_off) +float htfClose = request.security(syminfo.tickerid, "60", close, lookahead=barmerge.lookahead_off) + +bool bullAgree = htfRsi > 55.0 and htfHist > 0.0 and htfClose > htfBbM +bool bearAgree = htfRsi < 45.0 or htfHist < 0.0 or htfClose < htfBbM + +if bullAgree and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if bearAgree and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__risk-max-contracts-held-gate-pyramid-01.pine b/tests/gate-corpus/ok/validation__risk-max-contracts-held-gate-pyramid-01.pine new file mode 100644 index 0000000..271cc81 --- /dev/null +++ b/tests/gate-corpus/ok/validation__risk-max-contracts-held-gate-pyramid-01.pine @@ -0,0 +1,32 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: ETHUSDT.P (Binance USDT-M Perpetual) TF: 15m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. +// +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Probe: risk-max-contracts-held-gate-pyramid-01 +// Purpose: Verify strategy.max_contracts_held_all gates further pyramid entries. +// Enters long up to pyramiding=5 contracts via hourly pulses. +// Once max_contracts_held_all >= 5, no further entries are placed. +// Exits all at end of each daily session. +// +// TV setup: ETHUSDT.P 15m, 2024-01-01..2024-06-01 +//@version=6 +strategy("PF-F: max_contracts_held gate", shorttitle="PF_F_MCH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=10, + process_orders_on_close=false) + +// Gate further pyramid entries once lifetime max position size reached 5 +bool gate_open = strategy.max_contracts_held_all < 5 + +// Enter long at the start of each UTC hour (minute==0), capped by gate +if minute == 0 and gate_open + strategy.entry("LE", strategy.long, qty=1, comment="pyramid-add") + +// Exit all positions at end of each UTC day (hour==23, minute==45) +if hour == 23 and minute == 45 and strategy.position_size != 0 + strategy.close_all(comment="daily-exit") diff --git a/tests/gate-corpus/ok/validation__session-hour-minute-pulse-filter-01.pine b/tests/gate-corpus/ok/validation__session-hour-minute-pulse-filter-01.pine new file mode 100644 index 0000000..61fbbbc --- /dev/null +++ b/tests/gate-corpus/ok/validation__session-hour-minute-pulse-filter-01.pine @@ -0,0 +1,25 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TV golden 48 - session/time filter +// +// Purpose: isolate hour/minute gating and TV chart/feed timezone alignment. +// TV setup: 15m chart, same symbol/window as data/ohlcv_ETH-USDT-USDT_15m.csv. +// Use the exchange/chart timezone that makes Pine hour/minute match the OHLCV timestamps. +// Export TradingView "List of trades" to trades.csv in this folder. +//@version=6 +strategy("PF TV golden 48 - time filter", shorttitle="PF_G48_TIME", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +entryPulse = hour == 0 and minute == 15 +exitPulse = hour == 0 and minute == 45 + +if entryPulse and strategy.position_size == 0 + strategy.entry("T", strategy.long, qty=1, comment="time entry") + +if exitPulse and strategy.position_size > 0 + strategy.close("T", comment="time exit") diff --git a/tests/gate-corpus/ok/validation__session-ny-spring-forward-dst-01.pine b/tests/gate-corpus/ok/validation__session-ny-spring-forward-dst-01.pine new file mode 100644 index 0000000..4260b78 --- /dev/null +++ b/tests/gate-corpus/ok/validation__session-ny-spring-forward-dst-01.pine @@ -0,0 +1,40 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// session-dst probe 01 — NY spring-forward weekend +// +// Purpose: pin session-time + DST behavior. The NY equity session +// (09:30-16:00 America/New_York) crosses a spring-forward weekend in the +// corpus window. We count time-window entries pre/post DST and verify +// that `time(session)` returns `na` on Sunday (no NY session). +// Surfaces: tz-db version drift, ScopedTimezone mutex contention, +// DST hour-skip handling. +// +// Trade shape: long-only. Entry at first in-session bar of each day +// (session_id transitions from na -> non-na), exit at last in-session +// bar (transitions non-na -> na). Counts both pre- and post-DST entries +// on a 15m chart so we get >=10 closed trades over the corpus window. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. The window must include the second +// Sunday of March for spring-forward coverage. Export trade list to +// tv_trades.csv. +//@version=6 +strategy("PF session-dst probe 01 - NY spring forward", shorttitle="SDST_p01_NY", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +int sessId = time(timeframe.period, "0930-1600", "America/New_York") +bool inSess = not na(sessId) +bool wasInSess = not na(sessId[1]) + +bool sessOpen = inSess and not wasInSess +bool sessClose = wasInSess and not inSess + +if sessOpen and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="session open") +if sessClose and strategy.position_size > 0 + strategy.close("L", comment="session close") diff --git a/tests/gate-corpus/ok/validation__stats-eventrades-zero-pnl-count-01.pine b/tests/gate-corpus/ok/validation__stats-eventrades-zero-pnl-count-01.pine new file mode 100644 index 0000000..5cb46e4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__stats-eventrades-zero-pnl-count-01.pine @@ -0,0 +1,48 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: ETHUSDT.P (Binance USDT-M Perpetual) TF: 15m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. +// +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Probe: stats-eventrades-zero-pnl-count-01 +// Purpose: Verify strategy.eventrades counts trades with net P&L == 0. +// +// Engineering even trades: +// Commission = 0 (set in strategy() declaration). +// Entry fills at bar N close; exit fills at bar N+1 open (= same value as +// bar N close when open == close, i.e., a flat OHLC bar). However, since +// real ETHUSDT.P bars are rarely flat, we instead use limit orders set at +// bar.close so both entry and exit prices match exactly, producing 0 P&L. +// +// Simpler approach used here: enter at bar close (market order, fills next +// bar open). Exit with limit order at ENTRY price, which fills when the +// next bar's high >= entry_price (for a long). This guarantees exit price +// == entry price, so P&L = qty * (exit - entry) - commission = 0. +// +// Observation: strategy.eventrades should increment once per such trade. +// The probe confirms the counter is correct vs TradingView's reported value. +// +// TV setup: ETHUSDT.P 15m, 2024-01-01..2024-06-01 +//@version=6 +strategy("PF-F: eventrades count", shorttitle="PF_F_EVTRADES", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// Enter long at midnight UTC (hour==0, minute==0) +// Exit immediately at entry price (limit order) → guarantees 0 P&L with zero commission +var float entry_price = na + +if hour == 0 and minute == 0 and strategy.position_size == 0 + strategy.entry("LE", strategy.long, qty=1, comment="even-trade entry") + entry_price := close + +// Exit at the exact entry price via limit order: entry_price reached → net P&L == 0 +if strategy.position_size > 0 and not na(entry_price) + strategy.exit("LX", "LE", limit=entry_price, comment="even-trade exit @ entry") + +// Plot eventrades counter so TradingView table can be compared +plot(strategy.eventrades, title="eventrades", display=display.none) diff --git a/tests/gate-corpus/ok/validation__ta-accdist-ema-cross-01.pine b/tests/gate-corpus/ok/validation__ta-accdist-ema-cross-01.pine new file mode 100644 index 0000000..758c4b4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-accdist-ema-cross-01.pine @@ -0,0 +1,24 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — Accumulation/Distribution line vs its EMA(21) signal. +// Isolates ta.accdist (the no-arg A/D running-sum SERIES variable, built from +// the close-location-value * volume money-flow). A/D is a cumulative-over-all-bars +// accumulator like ta.obv, so it is sensitive to warmup/first-bar seeding and to +// the exact CLV formula ((2*close-low-high)/(high-low), 0 when high==low). Only a +// TV "List of trades" export can adjudicate the engine's accdist accumulator. +//@version=6 +strategy("PF TA isolate - accdist EMA(21) cross", shorttitle="TAI_AD", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +ad = ta.accdist +sig = ta.ema(ad, 21) + +if ta.crossover(ad, sig) + strategy.entry("L", strategy.long, comment="ad cross up") +if ta.crossunder(ad, sig) + strategy.entry("S", strategy.short, comment="ad cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-bb-kc-squeeze-breakout-01.pine b/tests/gate-corpus/ok/validation__ta-bb-kc-squeeze-breakout-01.pine new file mode 100644 index 0000000..9f31030 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-bb-kc-squeeze-breakout-01.pine @@ -0,0 +1,57 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 02 — BB / KC squeeze breakout +// Purpose: parity test for ta.bb() + ta.kc() tuple returns and the BB-inside-KC +// squeeze-state derivation. +// +//@version=6 +strategy("BB Squeeze Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +bbLen = input.int(20, "BB Length", minval=5) +bbMult = input.float(2.0, "BB Multiplier", step=0.1) +kcLen = input.int(20, "KC Length", minval=5) +kcMult = input.float(1.5, "KC Multiplier", step=0.1) +src = input.source(close, "Source") + +// === Bollinger Bands (tuple) === +[bbMid, bbUpper, bbLower] = ta.bb(src, bbLen, bbMult) + +// === Keltner Channels (tuple) — now using ta.kc() directly === +[kcMid, kcUpper, kcLower] = ta.kc(src, kcLen, kcMult) + +// === Squeeze detection: BB inside KC === +sqzOn = bbLower > kcLower and bbUpper < kcUpper +sqzOff = bbLower < kcLower or bbUpper > kcUpper + +// === Momentum === +mom = ta.linreg(src - bbMid, bbLen, 0) + +// === Track squeeze state === +var bool wasSqueezed = false +if sqzOn + wasSqueezed := true + +// === Squeeze fires when BB expands outside KC === +sqzFired = wasSqueezed and sqzOff + +// === Entry signals === +if sqzFired and mom > 0 + strategy.entry("Long", strategy.long) + wasSqueezed := false +if sqzFired and mom < 0 + strategy.entry("Short", strategy.short) + wasSqueezed := false + +// === Exit on opposite momentum === +if strategy.position_size > 0 and mom < 0 + strategy.close("Long") +if strategy.position_size < 0 and mom > 0 + strategy.close("Short") + +plot(bbUpper, "BB Upper", color=color.blue) +plot(bbLower, "BB Lower", color=color.blue) +plot(kcUpper, "KC Upper", color=color.orange) +plot(kcLower, "KC Lower", color=color.orange) diff --git a/tests/gate-corpus/ok/validation__ta-bb-rsi-mean-reversion-01.pine b/tests/gate-corpus/ok/validation__ta-bb-rsi-mean-reversion-01.pine new file mode 100644 index 0000000..76b34aa --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-bb-rsi-mean-reversion-01.pine @@ -0,0 +1,41 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 16 — Bollinger mean-reversion with RSI confirm +// Purpose: parity test for combined ta.bb() boundary breach + ta.rsi() OB/OS +// confirmation entry. +// +//@version=6 +strategy("Mean Reversion Bollinger", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +bbLen = input.int(20, "BB Length", minval=5) +bbMult = input.float(2.0, "BB Multiplier", step=0.1) +rsiLen = input.int(14, "RSI Length", minval=1) +rsiOB = input.int(70, "RSI Overbought") +rsiOS = input.int(30, "RSI Oversold") + +// === Indicators === +[bbMid, bbUpper, bbLower] = ta.bb(close, bbLen, bbMult) +rsiVal = ta.rsi(close, rsiLen) + +// === Mean reversion: price at extremes + RSI confirmation === +longCond = close < bbLower and rsiVal < rsiOS +shortCond = close > bbUpper and rsiVal > rsiOB + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit at midline === +if strategy.position_size > 0 and close > bbMid + strategy.close("Long") +if strategy.position_size < 0 and close < bbMid + strategy.close("Short") + +plot(bbUpper, "Upper", color=color.red) +plot(bbMid, "Mid", color=color.gray) +plot(bbLower, "Lower", color=color.green) diff --git a/tests/gate-corpus/ok/validation__ta-cci-threshold-cross-01.pine b/tests/gate-corpus/ok/validation__ta-cci-threshold-cross-01.pine new file mode 100644 index 0000000..bc891bf --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-cci-threshold-cross-01.pine @@ -0,0 +1,39 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 06 — CCI momentum cross +// Purpose: parity test for ta.cci() and crossover/under against fixed +// overbought/oversold thresholds. +// +//@version=6 +strategy("CCI Momentum", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +cciLen = input.int(20, "CCI Length", minval=5) +obLevel = input.int(100, "Overbought") +osLevel = input.int(-100, "Oversold") + +// === CCI Calculation === +cciVal = ta.cci(close, cciLen) + +// === Signals === +longCond = ta.crossover(cciVal, osLevel) +shortCond = ta.crossunder(cciVal, obLevel) + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// Exit on zero cross +if strategy.position_size > 0 and ta.crossunder(cciVal, 0) + strategy.close("Long") +if strategy.position_size < 0 and ta.crossover(cciVal, 0) + strategy.close("Short") + +plot(cciVal, "CCI", color=color.blue) +hline(obLevel, "Overbought", color=color.red) +hline(osLevel, "Oversold", color=color.green) +hline(0, "Zero", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-chandelier-exit-direction-01.pine b/tests/gate-corpus/ok/validation__ta-chandelier-exit-direction-01.pine new file mode 100644 index 0000000..48761e9 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-chandelier-exit-direction-01.pine @@ -0,0 +1,45 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 12 — Chandelier-Exit direction tracker +// Purpose: parity test for ta.atr × ta.highest/lowest band + `var int direction` +// stateful flip tracking. +// +//@version=6 +strategy("Chandelier Exit", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +atrLen = input.int(22, "ATR Length", minval=1) +atrMult = input.float(3.0, "ATR Multiplier", step=0.1) +lookback = input.int(22, "Lookback", minval=1) + +// === ATR === +atrVal = ta.atr(atrLen) + +// === Chandelier Exit levels === +highestHigh = ta.highest(high, lookback) +lowestLow = ta.lowest(low, lookback) + +chandLong = highestHigh - atrVal * atrMult +chandShort = lowestLow + atrVal * atrMult + +// === Track direction with var === +var int direction = 0 + +if close > chandShort[1] + direction := 1 +if close < chandLong[1] + direction := -1 + +// === Entry on direction change === +longCond = direction == 1 and direction[1] != 1 +shortCond = direction == -1 and direction[1] != -1 + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(chandLong, "Chandelier Long", color=color.green, linewidth=2) +plot(chandShort, "Chandelier Short", color=color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-closedtrades-risk-introspection-01.pine b/tests/gate-corpus/ok/validation__ta-closedtrades-risk-introspection-01.pine new file mode 100644 index 0000000..6120edd --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-closedtrades-risk-introspection-01.pine @@ -0,0 +1,37 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 35 — strategy.closedtrades / strategy.risk introspection +// Purpose: parity test for `strategy.risk.max_drawdown(...)` plus +// `strategy.closedtrades` / `strategy.closedtrades.profit(N)` lookback driving +// per-trade qty sizing. +// +//@version=6 +strategy("Advanced Trade Metrics", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +strategy.risk.max_drawdown(20, strategy.percent_of_equity) + +// Basic Donchian breakout +hh = ta.highest(high, 20) +ll = ta.lowest(low, 20) + +// Risk sizing based on past performance +qty = 1.0 +if strategy.closedtrades > 0 + last_profit = strategy.closedtrades.profit(strategy.closedtrades - 1) + if last_profit < 0 + qty := 0.5 + else + qty := 2.0 + +if close > hh[1] + strategy.entry("Long", strategy.long, qty=qty) +if close < ll[1] + strategy.entry("Short", strategy.short, qty=qty) + +if strategy.opentrades > 0 + open_dd = strategy.opentrades.max_drawdown(0) + if open_dd > 1000 + strategy.close("Long") + strategy.close("Short") diff --git a/tests/gate-corpus/ok/validation__ta-cmo-9-zero-cross-01.pine b/tests/gate-corpus/ok/validation__ta-cmo-9-zero-cross-01.pine new file mode 100644 index 0000000..1bbf07d --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-cmo-9-zero-cross-01.pine @@ -0,0 +1,23 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — CMO(9) zero-line cross. +// Isolates ta.cmo (Chande Momentum Oscillator): a momentum oscillator built +// from summed up/down moves over `length` bars, bounded [-100, +100]. This +// probe drives a flat long/short flip purely off the CMO sign change so a TV +// export adjudicates the engine's CMO warmup window and up/down accumulation +// against TradingView with no other moving parts. +//@version=6 +strategy("PF TA isolate - CMO(9) zero cross", shorttitle="TAI_CMO", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +cmo = ta.cmo(close, 9) + +if ta.crossover(cmo, 0) + strategy.entry("L", strategy.long, comment="cmo cross up 0") +if ta.crossunder(cmo, 0) + strategy.entry("S", strategy.short, comment="cmo cross dn 0") diff --git a/tests/gate-corpus/ok/validation__ta-cog-10-signal-cross-01.pine b/tests/gate-corpus/ok/validation__ta-cog-10-signal-cross-01.pine new file mode 100644 index 0000000..1b4621e --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-cog-10-signal-cross-01.pine @@ -0,0 +1,25 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — COG(10) vs its EMA(5) signal cross. +// Isolates ta.cog (Ehlers Center of Gravity oscillator): +// cog = -sum(src[i] * (i+1)) / sum(src[i]) over a length window. +// COG is a SMA-weighted ratio with the distinctive negative sign and the +// 1-based position weighting; this probe adjudicates the engine's COG window +// seeding, weight indexing, and divide-by-sum normalization against a TV +// export. The EMA(5) signal cross is the trigger only. +//@version=6 +strategy("PF TA isolate - COG(10) signal cross", shorttitle="TAI_COG", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +cog = ta.cog(close, 10) +sig = ta.ema(cog, 5) + +if ta.crossover(cog, sig) + strategy.entry("L", strategy.long, comment="cog cross up") +if ta.crossunder(cog, sig) + strategy.entry("S", strategy.short, comment="cog cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-dmi-adx-di-cross-01.pine b/tests/gate-corpus/ok/validation__ta-dmi-adx-di-cross-01.pine new file mode 100644 index 0000000..c6307c9 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-dmi-adx-di-cross-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 03 — DMI / ADX trend-following +// Purpose: parity test for ta.dmi() triple-tuple + DI crossover entry. +// +//@version=6 +strategy("DMI ADX Trend Following", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +diLen = input.int(14, "DI Length", minval=1) +adxSmooth = input.int(14, "ADX Smoothing", minval=1) +adxThresh = input.int(20, "ADX Threshold", minval=10) + +// === DMI Calculation (triple tuple) === +[diPlus, diMinus, adxVal] = ta.dmi(diLen, adxSmooth) + +// === DI crossovers === +bullCross = ta.crossover(diPlus, diMinus) +bearCross = ta.crossunder(diPlus, diMinus) + +// === Strategy === +if bullCross + strategy.entry("Long", strategy.long) +if bearCross + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__ta-donchian-channel-breakout-01.pine b/tests/gate-corpus/ok/validation__ta-donchian-channel-breakout-01.pine new file mode 100644 index 0000000..b837a0b --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-donchian-channel-breakout-01.pine @@ -0,0 +1,42 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 10 — Donchian channel breakout +// Purpose: parity test for ta.highest / ta.lowest band construction with the +// `[1]`-shifted entry comparison (yesterday's channel, today's close). +// +//@version=6 +strategy("Donchian Channel Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +entryLen = input.int(20, "Entry Channel Length", minval=5) +exitLen = input.int(10, "Exit Channel Length", minval=3) + +// === Donchian Channels === +entryUpper = ta.highest(high, entryLen) +entryLower = ta.lowest(low, entryLen) +entryMid = (entryUpper + entryLower) / 2 + +exitUpper = ta.highest(high, exitLen) +exitLower = ta.lowest(low, exitLen) + +// === Breakout signals === +longCond = close > entryUpper[1] +shortCond = close < entryLower[1] + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit on opposite channel === +if strategy.position_size > 0 and close < exitLower[1] + strategy.close("Long") +if strategy.position_size < 0 and close > exitUpper[1] + strategy.close("Short") + +plot(entryUpper, "Entry Upper", color=color.green) +plot(entryLower, "Entry Lower", color=color.red) +plot(entryMid, "Mid", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-dual-ma-switch-dispatch-01.pine b/tests/gate-corpus/ok/validation__ta-dual-ma-switch-dispatch-01.pine new file mode 100644 index 0000000..2d1a7d0 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-dual-ma-switch-dispatch-01.pine @@ -0,0 +1,42 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 17 — dual-MA with `switch` MA-type dispatch +// Purpose: parity test for a user-defined function whose body is a `switch` +// over MA family (ema/sma/wma/hma), dispatched dynamically per call. +// +//@version=6 +strategy("Dual MA with Switch", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +maType = input.string("EMA", "MA Type", options=["EMA", "SMA", "WMA", "HMA"]) +fastLen = input.int(10, "Fast Length", minval=2) +slowLen = input.int(30, "Slow Length", minval=5) +src = input.source(close, "Source") + +// === User-defined function with switch === +getMA(source, length) => + switch maType + "EMA" => ta.ema(source, length) + "SMA" => ta.sma(source, length) + "WMA" => ta.wma(source, length) + "HMA" => ta.hma(source, length) + => ta.sma(source, length) + +// === Calculate MAs === +fastMA = getMA(src, fastLen) +slowMA = getMA(src, slowLen) + +// === Signals === +longCond = ta.crossover(fastMA, slowMA) +shortCond = ta.crossunder(fastMA, slowMA) + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(fastMA, "Fast", color=color.blue, linewidth=2) +plot(slowMA, "Slow", color=color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-dual-thrust-open-anchored-range-01.pine b/tests/gate-corpus/ok/validation__ta-dual-thrust-open-anchored-range-01.pine new file mode 100644 index 0000000..19f61f6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-dual-thrust-open-anchored-range-01.pine @@ -0,0 +1,47 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 23 — Dual-Thrust open-anchored range breakout +// Purpose: parity test for max-of-(hh-lc, hc-ll) range computation anchored +// to the bar's `open` to derive intraday upper/lower bounds. +// +//@version=6 +strategy("Dual Thrust Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +lookback = input.int(4, "Lookback Period", minval=1) +kUp = input.float(0.5, "Upper K Factor", step=0.1) +kDn = input.float(0.5, "Lower K Factor", step=0.1) + +// === Range calculation === +hh = ta.highest(high, lookback) +lc = ta.lowest(close, lookback) +hc = ta.highest(close, lookback) +ll = ta.lowest(low, lookback) + +range1 = hh - lc +range2 = hc - ll +dualRange = math.max(range1, range2) + +// === Upper and lower bounds === +upperBound = open + kUp * dualRange +lowerBound = open - kDn * dualRange + +// === Signals === +longCond = close > upperBound +shortCond = close < lowerBound + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit on opposite === +if strategy.position_size > 0 and close < lowerBound + strategy.close("Long") +if strategy.position_size < 0 and close > upperBound + strategy.close("Short") + +plot(upperBound, "Upper", color=color.green) +plot(lowerBound, "Lower", color=color.red) diff --git a/tests/gate-corpus/ok/validation__ta-elder-ray-bull-bear-power-01.pine b/tests/gate-corpus/ok/validation__ta-elder-ray-bull-bear-power-01.pine new file mode 100644 index 0000000..e64b3b5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-elder-ray-bull-bear-power-01.pine @@ -0,0 +1,45 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 11 — Elder Ray bull/bear power +// Purpose: parity test for ta.ema() + (high|low - ema) bull/bear power chain +// with `[1]`-history rising/falling memory and trend-EMA gate. +// +//@version=6 +strategy("Elder Ray Index", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +emaLen = input.int(13, "EMA Length", minval=1) + +// === Elder Ray: Bull/Bear Power === +emaVal = ta.ema(close, emaLen) +bullPower = high - emaVal +bearPower = low - emaVal + +// === Trend filter === +emaTrend = ta.ema(close, 50) +upTrend = close > emaTrend +dnTrend = close < emaTrend + +// === Signals === +// Long: uptrend + bear power negative but rising +longCond = upTrend and bearPower < 0 and bearPower > bearPower[1] +// Short: downtrend + bull power positive but falling +shortCond = dnTrend and bullPower > 0 and bullPower < bullPower[1] + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit === +if strategy.position_size > 0 and bearPower > 0 and bearPower < bearPower[1] + strategy.close("Long") +if strategy.position_size < 0 and bullPower < 0 and bullPower > bullPower[1] + strategy.close("Short") + +plot(bullPower, "Bull Power", color=color.green, style=plot.style_histogram) +plot(bearPower, "Bear Power", color=color.red, style=plot.style_histogram) +hline(0, "Zero") diff --git a/tests/gate-corpus/ok/validation__ta-ema-ribbon-stack-transition-01.pine b/tests/gate-corpus/ok/validation__ta-ema-ribbon-stack-transition-01.pine new file mode 100644 index 0000000..501102b --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-ema-ribbon-stack-transition-01.pine @@ -0,0 +1,40 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 18 — EMA-ribbon stack + var-int transition tracker +// Purpose: parity test for 3-EMA stacking (fast > mid > slow / fast < mid < slow) +// with `var int prevDir` to suppress duplicate same-direction entries. +// +//@version=6 +strategy("EMA Ribbon", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +fastLen = input.int(8, "Fast EMA", minval=2) +midLen = input.int(21, "Mid EMA", minval=5) +slowLen = input.int(55, "Slow EMA", minval=10) + +// === Three EMAs === +emaFast = ta.ema(close, fastLen) +emaMid = ta.ema(close, midLen) +emaSlow = ta.ema(close, slowLen) + +// === Check stack order === +bullStack = emaFast > emaMid and emaMid > emaSlow +bearStack = emaFast < emaMid and emaMid < emaSlow + +// === Track transitions === +var int prevDir = 0 + +if bullStack and prevDir != 1 + strategy.entry("Long", strategy.long) + prevDir := 1 +if bearStack and prevDir != -1 + strategy.entry("Short", strategy.short) + prevDir := -1 +if not bullStack and not bearStack + prevDir := 0 + +plot(emaFast, "Fast", color=color.blue) +plot(emaMid, "Mid", color=color.orange) +plot(emaSlow, "Slow", color=color.red) diff --git a/tests/gate-corpus/ok/validation__ta-engulfing-candle-pattern-01.pine b/tests/gate-corpus/ok/validation__ta-engulfing-candle-pattern-01.pine new file mode 100644 index 0000000..4fcca62 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-engulfing-candle-pattern-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 22 — bullish/bearish engulfing candle-pattern detection +// Purpose: parity test for inter-bar pattern recognition using `close[1]` / +// `open[1]` history references and `math.abs/min/max` on body & wick sizes, +// gated by a trend-MA filter. +// +//@version=6 +strategy("Candlestick Patterns", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +trendLen = input.int(20, "Trend MA Length", minval=5) + +// === Trend filter === +trendMA = ta.sma(close, trendLen) +upTrend = close > trendMA +dnTrend = close < trendMA + +// === Candle body and wick calculations === +bodySize = math.abs(close - open) +upperWick = high - math.max(open, close) +lowerWick = math.min(open, close) - low +totalRange = high - low + +// === Pattern: Bullish Engulfing === +bullEngulf = close > open and close[1] < open[1] and close > open[1] and open < close[1] + +// === Pattern: Bearish Engulfing === +bearEngulf = close < open and close[1] > open[1] and close < open[1] and open > close[1] + +// === Pattern: Hammer (bullish) === +hammer = lowerWick > bodySize * 2 and upperWick < bodySize * 0.5 and bodySize > 0 + +// === Pattern: Shooting Star (bearish) === +shootStar = upperWick > bodySize * 2 and lowerWick < bodySize * 0.5 and bodySize > 0 + +// === Pattern: Doji === +doji = bodySize < totalRange * 0.1 and totalRange > 0 + +// === Pattern: Morning Star (3-bar bullish reversal) === +morningStar = close[2] < open[2] and bodySize[1] < bodySize[2] * 0.3 and close > open and close > (open[2] + close[2]) / 2 + +// === Entry Signals === +bullSignal = (bullEngulf or hammer or morningStar) and dnTrend +bearSignal = (bearEngulf or shootStar) and upTrend + +if bullSignal + strategy.entry("Long", strategy.long) +if bearSignal + strategy.entry("Short", strategy.short) + +// === Exit === +if strategy.position_size > 0 and bearSignal + strategy.close("Long") +if strategy.position_size < 0 and bullSignal + strategy.close("Short") + +plot(trendMA, "Trend MA", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-highestbars-lowestbars-breakout-01.pine b/tests/gate-corpus/ok/validation__ta-highestbars-lowestbars-breakout-01.pine new file mode 100644 index 0000000..b0eab7a --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-highestbars-lowestbars-breakout-01.pine @@ -0,0 +1,47 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 09 — highest/lowest breakout (was Aroon) +// Purpose: parity test for ta.highestbars / ta.lowestbars offset semantics +// (negative-offset == 0 marks current bar as the new extreme) plus EMA gate. +// +//@version=6 +strategy("Highest Lowest Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +lookback = input.int(20, "Lookback", minval=5) + +// === Using highestbars and lowestbars === +barsToHigh = ta.highestbars(high, lookback) +barsToLow = ta.lowestbars(low, lookback) +hh = ta.highest(high, lookback) +ll = ta.lowest(low, lookback) + +// === Recent new high/low === +// barsToHigh returns negative offset, so 0 means current bar is highest +newHigh = barsToHigh == 0 +newLow = barsToLow == 0 + +// === Trend confirmation with MA === +maVal = ta.ema(close, 50) + +// === Signals === +longCond = newHigh and close > maVal +shortCond = newLow and close < maVal + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// Exit at mid channel +midChan = (hh + ll) / 2 +if strategy.position_size > 0 and close < midChan + strategy.close("Long") +if strategy.position_size < 0 and close > midChan + strategy.close("Short") + +plot(hh, "Highest", color=color.green) +plot(ll, "Lowest", color=color.red) +plot(midChan, "Mid", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-hma-55-close-cross-01.pine b/tests/gate-corpus/ok/validation__ta-hma-55-close-cross-01.pine new file mode 100644 index 0000000..e23346d --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-hma-55-close-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 04 — close crosses HMA(55) +// +// Purpose: isolate ta.hma(close, 55). HMA internally uses +// wma(2*wma(src, n/2) - wma(src, n), sqrt(n)). +// Validates engine WMA partial-window seed + nested WMA composition + sqrt +// rounding (sqrt(55) = 7.416... → floor 7). +// +// Targets gap probe in a community-style market-structure-shift strategy +// which uses HMA(close, 55). +//@version=6 +strategy("PF TA isolate 04 - close x HMA(55)", shorttitle="TAI_04_HMA55", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +h = ta.hma(close, 55) + +if ta.crossover(close, h) + strategy.entry("L", strategy.long, comment="close cross up HMA55") +if ta.crossunder(close, h) + strategy.entry("S", strategy.short, comment="close cross dn HMA55") diff --git a/tests/gate-corpus/ok/validation__ta-hma-fast-slow-cross-01.pine b/tests/gate-corpus/ok/validation__ta-hma-fast-slow-cross-01.pine new file mode 100644 index 0000000..ab5cc95 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-hma-fast-slow-cross-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 05 — HMA fast/slow crossover +// Purpose: parity test for ta.hma() (nested WMA composition) at fast/slow lengths. +// +//@version=6 +strategy("HMA Crossover", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +fastLen = input.int(9, "Fast HMA", minval=2) +slowLen = input.int(21, "Slow HMA", minval=2) + +// === HMA Calculations === +hmaFast = ta.hma(close, fastLen) +hmaSlow = ta.hma(close, slowLen) + +// === Crossover signals === +longCond = ta.crossover(hmaFast, hmaSlow) +shortCond = ta.crossunder(hmaFast, hmaSlow) + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(hmaFast, "Fast HMA", color=color.blue, linewidth=2) +plot(hmaSlow, "Slow HMA", color=color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-inside-bar-engulfing-01.pine b/tests/gate-corpus/ok/validation__ta-inside-bar-engulfing-01.pine new file mode 100644 index 0000000..c198a8c --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-inside-bar-engulfing-01.pine @@ -0,0 +1,48 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 98 — inside-bar engulfing entries +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises the simplest relational price-pattern surface — a +// 1-bar inside-range gate (`high < high[1] and low > low[1]`) plus the +// candle-direction tiebreaker (`close > open` vs `close < open`). Every +// term references the prior bar via the `[1]` history operator on raw +// OHLC series, so any engine that mishandles the boundary between +// "current-bar value" and "prior-bar value" — or that resolves OHLC +// history with a different lag than TradingView — will desync trade +// timestamps here. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every long/short entry +// fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 98 - inside-bar engulf", shorttitle="PF_p98_INBAR", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inside-bar relational pattern --------------------------------- +bool inside_bar = high < high[1] and low > low[1] + +// ---- candle-direction tiebreaker ----------------------------------- +bool bull_inside = inside_bar and close > open +bool bear_inside = inside_bar and close < open + +// ---- entries on next bar's open ------------------------------------ +if bull_inside and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="bull inside-bar") + +if bear_inside and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="bear inside-bar") diff --git a/tests/gate-corpus/ok/validation__ta-kama-style-efficiency-ratio-01.pine b/tests/gate-corpus/ok/validation__ta-kama-style-efficiency-ratio-01.pine new file mode 100644 index 0000000..c32b0a8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-kama-style-efficiency-ratio-01.pine @@ -0,0 +1,50 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 21 — KAMA-style adaptive MA via user-defined efficiency-ratio function +// Purpose: parity test for a user-defined function (`calcEfficiencyRatio`) that +// chains math.abs + math.sum and a `[length]` history reference inside the body. +// +//@version=6 +strategy("Adaptive MA Function", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +len = input.int(14, "Length", minval=2) +fastLen = input.int(2, "Fast Alpha Period", minval=1) +slowLen = input.int(30, "Slow Alpha Period", minval=5) + +// === User-defined function: Kaufman Adaptive MA (KAMA-style) === +calcEfficiencyRatio(src, length) => + direction = math.abs(src - src[length]) + volatilitySum = math.sum(math.abs(src - src[1]), length) + er = volatilitySum != 0 ? direction / volatilitySum : 0 + er + +// === Calculate KAMA === +er = calcEfficiencyRatio(close, len) + +fastAlpha = 2.0 / (fastLen + 1) +slowAlpha = 2.0 / (slowLen + 1) + +// Scaled smoothing constant +sc = math.pow(er * (fastAlpha - slowAlpha) + slowAlpha, 2) + +// KAMA +var float kama = close +kama := nz(kama[1]) + sc * (close - nz(kama[1])) + +// === Trend direction === +kamaUp = kama > kama[1] +kamaDown = kama < kama[1] + +// === Signals === +longCond = kamaUp and not kamaUp[1] +shortCond = kamaDown and not kamaDown[1] + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(kama, "KAMA", color=kamaUp ? color.green : color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-keltner-channel-break-01.pine b/tests/gate-corpus/ok/validation__ta-keltner-channel-break-01.pine new file mode 100644 index 0000000..fcb1151 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-keltner-channel-break-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 99 — Keltner channel breakout +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises a compact composite of three v6 surfaces in one +// strategy — `ta.ema(close, 20)` for the channel midline, `ta.atr(10)` +// for the half-width, and `ta.crossover` / `ta.crossunder` of close +// against the upper/lower band for the entry/exit triggers. The +// channel construction `mid ± atr * mult` is a TA-on-TA expression; +// any tiny seed drift in either the EMA or the ATR shifts the band, +// which in turn shifts WHICH bar the close-vs-band cross resolves on. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every entry/exit fires +// on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 99 - keltner channel break", shorttitle="PF_p99_KCH", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_ema_len = input.int(20, "Channel EMA length", minval=2, maxval=200) +int i_atr_len = input.int(10, "Channel ATR length", minval=2, maxval=100) +float i_mult = input.float(2.0, "Channel multiple", minval=0.5, maxval=10.0, step=0.1) + +// ---- channel construction ----------------------------------------- +float mid_line = ta.ema(close, i_ema_len) +float half_w = ta.atr(i_atr_len) * i_mult +float upper = mid_line + half_w +float lower = mid_line - half_w + +// ---- entries / exits on cross of close vs band -------------------- +bool long_break = ta.crossover(close, upper) +bool short_break = ta.crossunder(close, lower) +bool long_exit = ta.crossunder(close, mid_line) +bool short_exit = ta.crossover(close, mid_line) + +if long_break and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="upper break") + +if short_break and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="lower break") + +if long_exit and strategy.position_size > 0 + strategy.close("L", comment="back to mid") + +if short_exit and strategy.position_size < 0 + strategy.close("S", comment="back to mid") diff --git a/tests/gate-corpus/ok/validation__ta-linreg-stdev-channel-revert-01.pine b/tests/gate-corpus/ok/validation__ta-linreg-stdev-channel-revert-01.pine new file mode 100644 index 0000000..dfd5c75 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-linreg-stdev-channel-revert-01.pine @@ -0,0 +1,40 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 08 — linear-regression channel mean-reversion +// Purpose: parity test for ta.linreg() + ta.stdev() band construction and +// inside-bar reversal entry. +// +//@version=6 +strategy("Linear Regression Channel", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +regLen = input.int(50, "Regression Length", minval=10) +devMult = input.float(2.0, "Deviation Multiplier", step=0.1) + +// === Linear Regression === +regLine = ta.linreg(close, regLen, 0) +regDev = ta.stdev(close, regLen) * devMult +upperBand = regLine + regDev +lowerBand = regLine - regDev + +// === Mean reversion signals === +longCond = close < lowerBand and close > close[1] +shortCond = close > upperBand and close < close[1] + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit at regression line (mean) === +if strategy.position_size > 0 and close > regLine + strategy.close("Long") +if strategy.position_size < 0 and close < regLine + strategy.close("Short") + +plot(regLine, "Regression", color=color.yellow, linewidth=2) +plot(upperBand, "Upper", color=color.red) +plot(lowerBand, "Lower", color=color.green) diff --git a/tests/gate-corpus/ok/validation__ta-macd-12-26-9-line-signal-cross-01.pine b/tests/gate-corpus/ok/validation__ta-macd-12-26-9-line-signal-cross-01.pine new file mode 100644 index 0000000..0240a55 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-macd-12-26-9-line-signal-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 03 — MACD(12, 26, 9) signal cross +// +// Purpose: isolate ta.macd(close, 12, 26, 9). MACD = EMA(12) - EMA(26); signal +// = EMA(macd, 9). Validates EMA chain (depth 2) and tuple-return dispatch. +// Distinct from existing validation/01-macd-histogram probe by trade shape +// (this one uses crossover/crossunder of macd vs signal directly; that one uses +// histogram-zero crossings). +// +// Targets gap probe `validation/37-regex-string-filter` MACD path. +//@version=6 +strategy("PF TA isolate 03 - MACD(12,26,9) signal cross", shorttitle="TAI_03_MACDX", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +[macd, sig, _hist] = ta.macd(close, 12, 26, 9) + +if ta.crossover(macd, sig) + strategy.entry("L", strategy.long, comment="macd cross up") +if ta.crossunder(macd, sig) + strategy.entry("S", strategy.short, comment="macd cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-macd-histogram-reversal-01.pine b/tests/gate-corpus/ok/validation__ta-macd-histogram-reversal-01.pine new file mode 100644 index 0000000..66bdaf7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-macd-histogram-reversal-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 01 — MACD histogram reversal +// Purpose: parity test for ta.macd() tuple-return + crossover-driven entry. +// +//@version=6 +strategy("MACD Histogram Reversal", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +fastLen = input.int(12, "Fast Length", minval=1) +slowLen = input.int(26, "Slow Length", minval=1) +signalLen = input.int(9, "Signal Length", minval=1) +src = input.source(close, "Source") + +// === MACD Calculation (tuple return) === +[macdLine, signalLine, histLine] = ta.macd(src, fastLen, slowLen, signalLen) + +// === Signal: MACD line crosses signal line === +longCond = ta.crossover(macdLine, signalLine) +shortCond = ta.crossunder(macdLine, signalLine) + +// === Entries === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(histLine, "Histogram", style=plot.style_histogram, color=histLine >= 0 ? color.green : color.red) +plot(macdLine, "MACD", color=color.blue) +plot(signalLine, "Signal", color=color.orange) diff --git a/tests/gate-corpus/ok/validation__ta-macd-line-gt-signal-continuous-state-01.pine b/tests/gate-corpus/ok/validation__ta-macd-line-gt-signal-continuous-state-01.pine new file mode 100644 index 0000000..d47c39b --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-macd-line-gt-signal-continuous-state-01.pine @@ -0,0 +1,23 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 07 — MACD line > signal continuous-state pattern +// +// Purpose: contrast vs probe 03 (ta.crossover MACD vs signal — event trigger). +// This probe uses VALUE-comparison (`macd > sig`) every bar with the +// continuous-state pattern. Isolates the MACD path of validation/37-regex. +//@version=6 +strategy("PF TA isolate 07 - MACD > sig continuous", shorttitle="TAI_07_MACDGTSIG", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +[macd, sig, _hist] = ta.macd(close, 12, 26, 9) +bool cond = macd > sig + +if cond + strategy.entry("L", strategy.long, comment="macd > sig") +else + strategy.close("L", comment="macd <= sig") diff --git a/tests/gate-corpus/ok/validation__ta-map-regime-threshold-lookup-01.pine b/tests/gate-corpus/ok/validation__ta-map-regime-threshold-lookup-01.pine new file mode 100644 index 0000000..8cf2e03 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-map-regime-threshold-lookup-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 34 — map regime-keyed threshold lookup +// Purpose: parity test for `var map.new()` initialised on +// barstate.isfirst, with per-bar `map.contains` / `map.get` driving entries. +// +//@version=6 +strategy("Map Regime Tracker", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +var myMap = map.new() +if barstate.isfirst + myMap.put("bullish_limit", 1.5) + myMap.put("bearish_limit", -1.5) + +trend = ta.ema(close, 20) > ta.sma(close, 50) +regime_str = trend ? "bullish" : "bearish" +currentKey = regime_str + "_limit" + +limit = myMap.contains(currentKey) ? myMap.get(currentKey) : 0.0 +mom = ta.mom(close, 10) + +if trend and mom > limit + strategy.entry("Long", strategy.long) +if not trend and mom < limit + strategy.entry("Short", strategy.short) diff --git a/tests/gate-corpus/ok/validation__ta-median-vs-ema-cross-01.pine b/tests/gate-corpus/ok/validation__ta-median-vs-ema-cross-01.pine new file mode 100644 index 0000000..82e8101 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-median-vs-ema-cross-01.pine @@ -0,0 +1,30 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 29 — ta.median vs ta.ema crossover +// Purpose: parity test for ta.median() (robust central-tendency) crossing +// ta.ema() (mean-tendency) — divergent smoothing families on the same input. +// +//@version=6 +strategy("Median Crossover", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +medLen = input.int(20, "Median Length", minval=5) +emaLen = input.int(20, "EMA Length", minval=5) + +// === Indicators === +medVal = ta.median(close, medLen) +emaVal = ta.ema(close, emaLen) + +// === Signals: median vs EMA crossover === +longCond = ta.crossover(medVal, emaVal) +shortCond = ta.crossunder(medVal, emaVal) + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(medVal, "Median", color=color.blue, linewidth=2) +plot(emaVal, "EMA", color=color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-mfi-14-bands-20-80-01.pine b/tests/gate-corpus/ok/validation__ta-mfi-14-bands-20-80-01.pine new file mode 100644 index 0000000..6a06384 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-mfi-14-bands-20-80-01.pine @@ -0,0 +1,24 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — MFI(hlc3, 14) crossing static 20 / 80 bands. +// Isolates ta.mfi (Money Flow Index): a volume-weighted RSI variant whose +// .compute() takes an explicit source AND an implicitly-appended volume. +// Long on cross UP through oversold 20, short on cross DOWN through +// overbought 80. Static int bands make the trigger entirely dependent on the +// MFI value itself, so any TV-parity drift implicates the money-flow / +// positive-negative-flow accumulation, not the band logic. +//@version=6 +strategy("PF TA isolate - MFI(14) bands 20/80", shorttitle="TAI_MFI", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +mfi = ta.mfi(hlc3, 14) + +if ta.crossover(mfi, 20) + strategy.entry("L", strategy.long, comment="mfi x up 20") +if ta.crossunder(mfi, 80) + strategy.entry("S", strategy.short, comment="mfi x dn 80") diff --git a/tests/gate-corpus/ok/validation__ta-momentum-roc-zero-cross-01.pine b/tests/gate-corpus/ok/validation__ta-momentum-roc-zero-cross-01.pine new file mode 100644 index 0000000..d98f712 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-momentum-roc-zero-cross-01.pine @@ -0,0 +1,38 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 15 — momentum + ROC zero-cross combo +// Purpose: parity test for ta.mom() and ta.roc() (price-difference indicators +// rather than smoothings) with paired zero-cross + sign-confirm entries. +// +//@version=6 +strategy("Momentum ROC Combo", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +momLen = input.int(10, "Momentum Length", minval=1) +rocLen = input.int(10, "ROC Length", minval=1) + +// === Momentum & ROC === +momVal = ta.mom(close, momLen) +rocVal = ta.roc(close, rocLen) + +// === Signals: both cross zero together === +longCond = ta.crossover(momVal, 0) and rocVal > 0 +shortCond = ta.crossunder(momVal, 0) and rocVal < 0 + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// Exit when momentum fades +if strategy.position_size > 0 and momVal < 0 + strategy.close("Long") +if strategy.position_size < 0 and momVal > 0 + strategy.close("Short") + +plot(momVal, "Momentum", color=color.blue) +plot(rocVal, "ROC", color=color.green) +hline(0, "Zero") diff --git a/tests/gate-corpus/ok/validation__ta-multi-indicator-score-composite-01.pine b/tests/gate-corpus/ok/validation__ta-multi-indicator-score-composite-01.pine new file mode 100644 index 0000000..22a3577 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-multi-indicator-score-composite-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 30 — multi-indicator scoring composite +// Purpose: parity test for additive `score` accumulator driven by RSI / EMA / +// BB indicator branches (`if/else if` mutation of an int) for entry threshold. +// +//@version=6 +strategy("Multi Indicator Score", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +rsiLen = input.int(14, "RSI Length", minval=1) +maLen = input.int(20, "MA Length", minval=1) +bbLen = input.int(20, "BB Length", minval=5) +bbMult = input.float(2.0, "BB Mult", step=0.1) + +// === Indicators === +rsiVal = ta.rsi(close, rsiLen) +emaVal = ta.ema(close, maLen) +[bbMid, bbUpper, bbLower] = ta.bb(close, bbLen, bbMult) +atrVal = ta.atr(14) + +// === Score system: -3 to +3 === +score = 0 + +// RSI +if rsiVal > 50 + score += 1 +else if rsiVal < 50 + score -= 1 + +// Price vs EMA +if close > emaVal + score += 1 +else + score -= 1 + +// BB position +if close > bbMid + score += 1 +else + score -= 1 + +// === Signals based on score threshold === +longCond = score >= 2 and score[1] < 2 +shortCond = score <= -2 and score[1] > -2 + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// Exit when score neutralizes +if strategy.position_size > 0 and score <= 0 + strategy.close("Long") +if strategy.position_size < 0 and score >= 0 + strategy.close("Short") + +plot(emaVal, "EMA", color=color.blue) +plot(bbUpper, "BB Upper", color=color.gray) +plot(bbLower, "BB Lower", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-nvi-pvi-cross-01.pine b/tests/gate-corpus/ok/validation__ta-nvi-pvi-cross-01.pine new file mode 100644 index 0000000..da119d1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-nvi-pvi-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — Negative/Positive Volume Index (ta.nvi / ta.pvi). +// Isolates the two cumulative volume-conditioned built-in SERIES properties +// ta.nvi and ta.pvi. Both update only on bars where volume falls (NVI) or +// rises (PVI) vs the prior bar — a stateful path with no TradingView-parity +// coverage in the corpus. Long when PVI crosses over its EMA(255); short when +// NVI crosses under its EMA(255). +//@version=6 +strategy("PF TA isolate - NVI/PVI vs EMA255 cross", shorttitle="TAI_NVIPVI", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +pvi = ta.pvi +nvi = ta.nvi +pviEma = ta.ema(pvi, 255) +nviEma = ta.ema(nvi, 255) + +if ta.crossover(pvi, pviEma) + strategy.entry("L", strategy.long, comment="pvi cross up") +if ta.crossunder(nvi, nviEma) + strategy.entry("S", strategy.short, comment="nvi cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-obv-ema-cross-01.pine b/tests/gate-corpus/ok/validation__ta-obv-ema-cross-01.pine new file mode 100644 index 0000000..55c1c70 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-obv-ema-cross-01.pine @@ -0,0 +1,24 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — On-Balance Volume (ta.obv) vs its EMA(21) signal cross. +// Isolates ta.obv, the cumulative volume-flow SERIES built-in (used WITHOUT +// parens, as a property — obv = ta.obv). OBV is an unbounded running sum that +// adds/subtracts the full bar volume on every close tick, so the raw magnitude +// and the cumulative-from-first-bar seeding are exactly the kind of thing that +// only a TradingView export can adjudicate against the engine's ta::OBV. +//@version=6 +strategy("PF TA isolate - OBV EMA(21) cross", shorttitle="TAI_OBV", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +obv = ta.obv +sig = ta.ema(obv, 21) + +if ta.crossover(obv, sig) + strategy.entry("L", strategy.long, comment="obv cross up") +if ta.crossunder(obv, sig) + strategy.entry("S", strategy.short, comment="obv cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-percentrank-mean-reversion-01.pine b/tests/gate-corpus/ok/validation__ta-percentrank-mean-reversion-01.pine new file mode 100644 index 0000000..aec3f63 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-percentrank-mean-reversion-01.pine @@ -0,0 +1,43 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 25 — percent-rank mean reversion +// Purpose: parity test for ta.percentrank() over price-change series with +// extreme-then-rising / extreme-then-falling reversion entries. +// +//@version=6 +strategy("Percent Rank Reversion", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +prLen = input.int(50, "Percentile Rank Length", minval=10) +obPR = input.float(90, "Overbought PR") +osPR = input.float(10, "Oversold PR") + +// === Percent Rank of price changes === +priceChange = close - close[1] +pr = ta.percentrank(priceChange, prLen) + +// === Smoothed signal === +prSmooth = ta.sma(pr, 5) + +// === Mean reversion signals === +longCond = pr < osPR and pr > pr[1] +shortCond = pr > obPR and pr < pr[1] + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit at middle === +if strategy.position_size > 0 and pr > 50 + strategy.close("Long") +if strategy.position_size < 0 and pr < 50 + strategy.close("Short") + +plot(pr, "Percent Rank", color=color.blue) +plot(prSmooth, "Smooth PR", color=color.orange) +hline(obPR, "OB", color=color.red) +hline(osPR, "OS", color=color.green) +hline(50, "Mid", color=color.gray) diff --git a/tests/gate-corpus/ok/validation__ta-pivot-array-unshift-pop-01.pine b/tests/gate-corpus/ok/validation__ta-pivot-array-unshift-pop-01.pine new file mode 100644 index 0000000..2b22f97 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-pivot-array-unshift-pop-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 19 — pivot-array breakout with array.unshift / pop maintenance +// Purpose: parity test for ta.pivothigh / ta.pivotlow detection paired with a +// bounded `var array` retention buffer (unshift on new pivot, pop when +// over capacity). +// +//@version=6 +strategy("Pivot Array Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +pivotLen = input.int(5, "Pivot Length", minval=2) +maxPivots = input.int(5, "Max Stored Pivots", minval=2) + +// === Arrays to track pivot levels === +var array resistanceLevels = array.new(0) +var array supportLevels = array.new(0) + +// === Detect pivots === +pvtHigh = ta.pivothigh(high, pivotLen, pivotLen) +pvtLow = ta.pivotlow(low, pivotLen, pivotLen) + +// === Store new pivots === +if not na(pvtHigh) + array.unshift(resistanceLevels, pvtHigh) + if array.size(resistanceLevels) > maxPivots + array.pop(resistanceLevels) + +if not na(pvtLow) + array.unshift(supportLevels, pvtLow) + if array.size(supportLevels) > maxPivots + array.pop(supportLevels) + +// === Use most recent pivot as key level === +var float keyRes = na +var float keySup = na + +if array.size(resistanceLevels) > 0 + keyRes := array.get(resistanceLevels, 0) +if array.size(supportLevels) > 0 + keySup := array.get(supportLevels, 0) + +// === Breakout signals === +longCond = not na(keyRes) and close > keyRes and close[1] <= keyRes +shortCond = not na(keySup) and close < keySup and close[1] >= keySup + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit === +if strategy.position_size > 0 and not na(keySup) and close < keySup + strategy.close("Long") +if strategy.position_size < 0 and not na(keyRes) and close > keyRes + strategy.close("Short") + +plot(keyRes, "Resistance", color=color.red, style=plot.style_stepline) +plot(keySup, "Support", color=color.green, style=plot.style_stepline) diff --git a/tests/gate-corpus/ok/validation__ta-pivot-atr-stop-target-01.pine b/tests/gate-corpus/ok/validation__ta-pivot-atr-stop-target-01.pine new file mode 100644 index 0000000..25e1fab --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-pivot-atr-stop-target-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 28 — pivot-anchored ATR stop/target +// Purpose: parity test for ta.pivothigh / ta.pivotlow detection paired with +// `var float lastPvtH/L` retention and ATR-multiple stop & target sizing. +// +//@version=6 +strategy("Swing Pivot ATR", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +pivotLen = input.int(5, "Pivot Length", minval=2) +atrLen = input.int(14, "ATR Length", minval=1) +atrMult = input.float(1.5, "ATR SL Multiplier", step=0.1) +tpMult = input.float(2.0, "ATR TP Multiplier", step=0.1) + +// === Pivots === +pvtHigh = ta.pivothigh(high, pivotLen, pivotLen) +pvtLow = ta.pivotlow(low, pivotLen, pivotLen) +atrVal = ta.atr(atrLen) + +// === Track last pivot levels === +var float lastPvtH = na +var float lastPvtL = na + +if not na(pvtHigh) + lastPvtH := pvtHigh +if not na(pvtLow) + lastPvtL := pvtLow + +// === Breakout of pivot levels === +longCond = not na(lastPvtH) and close > lastPvtH and close[1] <= lastPvtH +shortCond = not na(lastPvtL) and close < lastPvtL and close[1] >= lastPvtL + +// === Strategy with exit targets === +if longCond + strategy.entry("Long", strategy.long) + strategy.exit("XL", "Long", stop=close - atrVal * atrMult, limit=close + atrVal * tpMult) + +if shortCond + strategy.entry("Short", strategy.short) + strategy.exit("XS", "Short", stop=close + atrVal * atrMult, limit=close - atrVal * tpMult) + +plot(lastPvtH, "Last Pivot High", color=color.red, style=plot.style_stepline) +plot(lastPvtL, "Last Pivot Low", color=color.green, style=plot.style_stepline) diff --git a/tests/gate-corpus/ok/validation__ta-pivot-confirmed-break-01.pine b/tests/gate-corpus/ok/validation__ta-pivot-confirmed-break-01.pine new file mode 100644 index 0000000..105d552 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-pivot-confirmed-break-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 102 — confirmed-pivot break extension +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises `ta.pivothigh(left, right)` / `ta.pivotlow(left, right)` +// at left==right==4, then carries the most-recent CONFIRMED pivot price +// across bars in `var float` storage and fires entries when the close +// extends past it. Two engine surfaces are pinned: +// (1) Pivot confirmation latency: TV reports `ta.pivothigh` `right` +// bars AFTER the actual swing; engines that resolve confirmation +// earlier or later will desync the carry-state. +// (2) `var` initialiser-once semantics: `last_ph` / `last_pl` must +// hold their value across bars and update only on confirmation. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every breakout entry +// fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 102 - pivot extension break", shorttitle="PF_p102_PIV", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_left = input.int(4, "Pivot left bars", minval=1, maxval=20) +int i_right = input.int(4, "Pivot right bars", minval=1, maxval=20) + +// ---- pivot detection ---------------------------------------------- +float ph = ta.pivothigh(high, i_left, i_right) +float pl = ta.pivotlow(low, i_left, i_right) + +// ---- carry the most-recent confirmed pivot ----------------------- +var float last_ph = na +var float last_pl = na + +if not na(ph) + last_ph := ph +if not na(pl) + last_pl := pl + +// ---- entries on close extending past the carried pivot ----------- +bool long_break = not na(last_ph) and close > last_ph +bool short_break = not na(last_pl) and close < last_pl + +if long_break and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="pivot-high break") + +if short_break and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="pivot-low break") diff --git a/tests/gate-corpus/ok/validation__ta-pivot-point-levels-break-01.pine b/tests/gate-corpus/ok/validation__ta-pivot-point-levels-break-01.pine new file mode 100644 index 0000000..3c47cad --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-pivot-point-levels-break-01.pine @@ -0,0 +1,28 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — ta.pivot_point_levels("Traditional") R1/S1 break. +// Isolates the free-function pivot_point_levels intrinsic and its array return +// shape: index 0 = P, index 1 = R1, index 2 = S1 (Traditional method). With a +// per-bar anchor (anchor=true) every bar is its own period, so each bar's pivot +// set is computed from the PREVIOUS bar's HLC — exactly how the codegen lowers +// the call (_s_high[1]/_s_low[1]/_s_close[1]). Trigger is a pure price-vs-level +// crossover/crossunder, so any divergence isolates the pivot math + array +// indexing rather than entry/sizing logic. +//@version=6 +strategy("PF TA isolate - pivot_point_levels R1/S1 break", shorttitle="TAI_PPL", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +pivots = ta.pivot_point_levels("Traditional", true) +P = array.get(pivots, 0) +R1 = array.get(pivots, 1) +S1 = array.get(pivots, 2) + +if ta.crossover(close, R1) + strategy.entry("L", strategy.long, comment="close x> R1") +if ta.crossunder(close, S1) + strategy.entry("S", strategy.short, comment="close x< S1") diff --git a/tests/gate-corpus/ok/validation__ta-pvt-ema-cross-01.pine b/tests/gate-corpus/ok/validation__ta-pvt-ema-cross-01.pine new file mode 100644 index 0000000..c8a27d1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-pvt-ema-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — PVT vs its EMA(21) signal cross. +// Isolates ta.pvt (Price-Volume Trend), a cumulative volume-state SERIES +// built-in: pvt += volume * (close - close[1]) / close[1], seeded from the +// first valid bar. It is one of the running volume accumulators (alongside +// ta.obv/accdist/nvi/pvi) and its cumulative seeding + division-by-prev-close +// path has no TradingView-parity coverage in the corpus. Crossing the raw +// cumulative PVT against a 21-EMA of itself drives the only logic so any +// drift is attributable to the ta.pvt series alone. +//@version=6 +strategy("PF TA isolate - PVT EMA(21) cross", shorttitle="TAI_PVT", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +pvt = ta.pvt +sig = ta.ema(pvt, 21) + +if ta.crossover(pvt, sig) + strategy.entry("L", strategy.long, comment="pvt cross up") +if ta.crossunder(pvt, sig) + strategy.entry("S", strategy.short, comment="pvt cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-range-filter-var-band-01.pine b/tests/gate-corpus/ok/validation__ta-range-filter-var-band-01.pine new file mode 100644 index 0000000..46bdc5b --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-range-filter-var-band-01.pine @@ -0,0 +1,53 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 20 — range-filter with var-float band drift +// Purpose: parity test for `var float rangeFilter` cumulative band that drifts +// up by smoothed range when close breaks above hi-target (and vice versa) — a +// stateful filter whose value depends on every prior bar's update path. +// +//@version=6 +strategy("Range Filter", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +filterLen = input.int(50, "Filter Length", minval=5) +filterMult = input.float(2.5, "Range Multiplier", step=0.1) + +// === Smooth Range calculation === +avgRange = ta.ema(high - low, filterLen) +smoothRng = avgRange * filterMult + +// === Range Filter using var === +var float rangeFilter = close +var int filterDir = 0 + +// Upward filter +hiTarget = rangeFilter + smoothRng +loTarget = rangeFilter - smoothRng + +if close > hiTarget + rangeFilter := close - smoothRng + filterDir := 1 +else if close < loTarget + rangeFilter := close + smoothRng + filterDir := -1 +else + if filterDir == 1 + newFilter = close - smoothRng + rangeFilter := math.max(rangeFilter, newFilter) + else if filterDir == -1 + newFilter = close + smoothRng + rangeFilter := math.min(rangeFilter, newFilter) + +// === Direction change signals === +longCond = filterDir == 1 and filterDir[1] != 1 +shortCond = filterDir == -1 and filterDir[1] != -1 + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(rangeFilter, "Range Filter", color=filterDir == 1 ? color.green : color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-rci-14-zero-cross-01.pine b/tests/gate-corpus/ok/validation__ta-rci-14-zero-cross-01.pine new file mode 100644 index 0000000..3f79bcc --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rci-14-zero-cross-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — RCI(14) zero-cross. +// Isolates ta.rci (Rank Correlation Index): the Spearman rank correlation +// between bar time-order and price ranks, scaled x100. The engine computes +// rho via the d^2 shortcut (1 - 6*sum(d^2)/(n*(n^2-1))), which is only exact +// when there are NO tied prices in the window. On ties TV may instead use +// Pearson correlation on average-ranks (the tie-correct form). This probe +// drives entries purely off the sign of RCI so any d^2-vs-Pearson-on-ties +// divergence surfaces as a trade-count / fill-price drift that only a TV +// export can adjudicate. +//@version=6 +strategy("PF TA isolate - RCI(14) zero cross", shorttitle="TAI_RCI", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rci = ta.rci(close, 14) + +if ta.crossover(rci, 0) + strategy.entry("L", strategy.long, comment="rci cross up") +if ta.crossunder(rci, 0) + strategy.entry("S", strategy.short, comment="rci cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-rsi-bb-self-bands-01.pine b/tests/gate-corpus/ok/validation__ta-rsi-bb-self-bands-01.pine new file mode 100644 index 0000000..b41dbb6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi-bb-self-bands-01.pine @@ -0,0 +1,44 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 31 — Bollinger bands wrapped around RSI itself +// Purpose: parity test for ta.sma + ta.stdev applied directly to ta.rsi +// (TA-on-TA chain) with band-cross + bar-direction confirm entries. +// +//@version=6 +strategy("RSI with Bollinger Bands", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +rsiLen = input.int(14, "RSI Length", minval=1) +bbLen = input.int(50, "BB Length on RSI", minval=5) +bbMult = input.float(2.0, "BB Mult", step=0.1) + +// === RSI === +rsiVal = ta.rsi(close, rsiLen) + +// === Bollinger Bands on RSI itself === +rsiBBMid = ta.sma(rsiVal, bbLen) +rsiDev = ta.stdev(rsiVal, bbLen) * bbMult +rsiBBUp = rsiBBMid + rsiDev +rsiBBDn = rsiBBMid - rsiDev + +// === Signals: RSI crosses its own BB === +longCond = rsiVal < rsiBBDn and rsiVal > rsiVal[1] +shortCond = rsiVal > rsiBBUp and rsiVal < rsiVal[1] + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit at RSI midline === +if strategy.position_size > 0 and rsiVal > rsiBBMid + strategy.close("Long") +if strategy.position_size < 0 and rsiVal < rsiBBMid + strategy.close("Short") + +plot(rsiVal, "RSI", color=color.blue, linewidth=2) +plot(rsiBBUp, "RSI BB Upper", color=color.red) +plot(rsiBBMid, "RSI BB Mid", color=color.gray) +plot(rsiBBDn, "RSI BB Lower", color=color.green) diff --git a/tests/gate-corpus/ok/validation__ta-rsi-ema-signal-cross-01.pine b/tests/gate-corpus/ok/validation__ta-rsi-ema-signal-cross-01.pine new file mode 100644 index 0000000..0d56bfb --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi-ema-signal-cross-01.pine @@ -0,0 +1,32 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 07 — RSI / EMA signal cross +// Purpose: parity test for ta.rsi() smoothed by ta.ema() with a 50-line gate. +// +//@version=6 +strategy("RSI EMA Signal", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +rsiLen = input.int(14, "RSI Length", minval=1) +signalLen = input.int(9, "Signal EMA Length", minval=1) + +// === RSI with signal line === +rsiVal = ta.rsi(close, rsiLen) +rsiSignal = ta.ema(rsiVal, signalLen) + +// === Signals === +longCond = ta.crossover(rsiVal, rsiSignal) and rsiVal < 50 +shortCond = ta.crossunder(rsiVal, rsiSignal) and rsiVal > 50 + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(rsiVal, "RSI", color=color.blue, linewidth=2) +plot(rsiSignal, "Signal", color=color.orange) +hline(50, "Mid", color=color.gray) +hline(70, "OB", color=color.red) +hline(30, "OS", color=color.green) diff --git a/tests/gate-corpus/ok/validation__ta-rsi-macd-and-continuous-state-01.pine b/tests/gate-corpus/ok/validation__ta-rsi-macd-and-continuous-state-01.pine new file mode 100644 index 0000000..17ab93f --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi-macd-and-continuous-state-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 08 — RSI > 50 AND MACD > sig (37-regex without str.match) +// +// Purpose: this is `validation/37-regex-string-filter` with str.match REMOVED. +// useRsi and useMacd are hard-coded true. Same continuous-state entry/close +// pattern. If 37-regex is moderate but this is excellent, the bug is in +// engine's str.match() implementation or in the codegen path that lowers +// str.match. If this also drifts, the bug is in the AND-combination of two +// value-comparison conds OR in continuous-state pattern (cf probe 06). +//@version=6 +strategy("PF TA isolate 08 - RSI>50 AND MACD>sig", shorttitle="TAI_08_AND", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +[macdLine, sigLine, _] = ta.macd(close, 12, 26, 9) + +// Mirror 37-regex's branchy formulation exactly (don't simplify the AND) +bool longCond = true +if rsiVal <= 50 + longCond := false +if macdLine <= sigLine + longCond := false + +if longCond + strategy.entry("Long", strategy.long, comment="Long") +else + strategy.close("Long") diff --git a/tests/gate-corpus/ok/validation__ta-rsi14-bands-30-70-01.pine b/tests/gate-corpus/ok/validation__ta-rsi14-bands-30-70-01.pine new file mode 100644 index 0000000..40a3450 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi14-bands-30-70-01.pine @@ -0,0 +1,26 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 02 — RSI(14) bands 30/70 +// +// Purpose: isolate ta.rsi(close, 14) at threshold extrema (30 oversold, 70 +// overbought). Targets the same RSI math as probe 01 but with mean-reversion +// trade shape (long on RSI cross-up through 30, short on RSI cross-down +// through 70), so signal frequency differs. Cross-checks that engine RSI matches +// TV at low-volatility deep-band crossings (typed-matrix-01 uses RSI > 60 / < 45 +// internally, this probe shifts the threshold band wider). +//@version=6 +strategy("PF TA isolate 02 - RSI(14) bands 30/70", shorttitle="TAI_02_RSI3070", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsi = ta.rsi(close, 14) + +// Mean-reversion: long on cross-up out of oversold, short on cross-down out of overbought. +if ta.crossover(rsi, 30.0) + strategy.entry("L", strategy.long, comment="rsi cross up 30") +if ta.crossunder(rsi, 70.0) + strategy.entry("S", strategy.short, comment="rsi cross dn 70") diff --git a/tests/gate-corpus/ok/validation__ta-rsi14-cross-50-01.pine b/tests/gate-corpus/ok/validation__ta-rsi14-cross-50-01.pine new file mode 100644 index 0000000..687c2f1 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi14-cross-50-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 01 — RSI(14) crosses 50 +// +// Purpose: isolate ta.rsi(close, 14) seed + Wilder smoothing. No other indicators, +// no risk mgmt, no MTF, no session filters. Any TV/engine drift on this probe is +// either (a) RSI math drift OR (b) OHLCV input drift between TV chart and shipped +// data/ohlcv_ETH-USDT-USDT_15m_warmup6m.csv. Use README's first-bar-OHLC check +// before exporting trades to disambiguate. +// +// Trade shape: long when RSI(14) crosses up through 50, short when it crosses +// down through 50. Position flips on opposite signal. Single-direction at a time. +//@version=6 +strategy("PF TA isolate 01 - RSI(14) cross 50", shorttitle="TAI_01_RSI50", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsi = ta.rsi(close, 14) + +if ta.crossover(rsi, 50.0) + strategy.entry("L", strategy.long, comment="rsi cross up 50") +if ta.crossunder(rsi, 50.0) + strategy.entry("S", strategy.short, comment="rsi cross dn 50") diff --git a/tests/gate-corpus/ok/validation__ta-rsi14-gt-50-continuous-state-01.pine b/tests/gate-corpus/ok/validation__ta-rsi14-gt-50-continuous-state-01.pine new file mode 100644 index 0000000..bb2831f --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi14-gt-50-continuous-state-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 06 — RSI(14) > 50 continuous-state pattern +// +// Purpose: contrast vs probe 01 (which uses ta.crossover, an EVENT trigger). +// This probe uses VALUE-comparison (`rsi > 50`) every bar combined with the +// `if cond entry else close` continuous-state pattern from validation/37-regex. +// If probe 01 is excellent but this drifts, the engine bug is in the +// continuous-state strategy.entry/strategy.close call sequence, NOT in RSI math. +// +// Targets diagnosing gap probe `validation/37-regex-string-filter`. +//@version=6 +strategy("PF TA isolate 06 - RSI(14) > 50 continuous", shorttitle="TAI_06_RSIGT50", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsi = ta.rsi(close, 14) +bool cond = rsi > 50.0 + +if cond + strategy.entry("L", strategy.long, comment="rsi > 50") +else + strategy.close("L", comment="rsi <= 50") diff --git a/tests/gate-corpus/ok/validation__ta-rsi14-gt60-lt45-no-matrix-01.pine b/tests/gate-corpus/ok/validation__ta-rsi14-gt60-lt45-no-matrix-01.pine new file mode 100644 index 0000000..1fc9d17 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-rsi14-gt60-lt45-no-matrix-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 09 — typed-matrix-01 trigger MINUS matrix bool ops +// +// Purpose: typed-matrix-probe-01 uses RSI(14) > 60 entry and RSI(14) < 45 +// exit, gated additionally by a matrix.transpose round-trip + hotCount counter. +// This probe ISOLATES just the RSI > 60 / RSI < 45 trigger pair, no matrix. +// If this is excellent and typed-matrix-01 still drifts, root cause is in +// matrix.new / matrix.set / matrix.get / matrix.transpose dispatch +// (the X7 typed-matrix codepath). +//@version=6 +strategy("PF TA isolate 09 - RSI>60 entry, RSI<45 exit", shorttitle="TAI_09_RSI60_45", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +rsiVal = ta.rsi(close, 14) +bool entryCond = not na(rsiVal) and rsiVal > 55.0 // mirrors typed-matrix-01's `rsiVal > 55.0` second gate +bool exitCond = not na(rsiVal) and rsiVal < 45.0 + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__ta-sar-flip-entry-01.pine b/tests/gate-corpus/ok/validation__ta-sar-flip-entry-01.pine new file mode 100644 index 0000000..335af35 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-sar-flip-entry-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 101 — parabolic SAR flip entries +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises `ta.sar(start, increment, max)` — a deeply +// stateful indicator whose acceleration factor RAMPS within a trend +// (start → start+inc → start+2*inc → ... up to max) and RESETS to +// `start` on every flip. Reproducing the TV trade list requires the +// engine's SAR to (a) seed its initial trend the same way TV does +// (typically from the first directional bar), (b) ramp the AF on +// the same bars, and (c) flip on the same close-vs-SAR cross. Any +// drift in any of those three points cascades. This probe routes +// entries through the flip event itself (SAR moves from above the +// close to below = bullish flip; symmetric for bearish), which +// makes the trade list a direct readout of flip-bar agreement. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every flip-driven +// entry fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 101 - parabolic sar flip", shorttitle="PF_p101_SAR", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +float i_start = input.float(0.02, "SAR start", minval=0.001, maxval=1.0, step=0.01) +float i_inc = input.float(0.02, "SAR increment", minval=0.001, maxval=1.0, step=0.01) +float i_max = input.float(0.20, "SAR max AF", minval=0.01, maxval=1.0, step=0.01) + +// ---- SAR + flip detection ----------------------------------------- +float sar_val = ta.sar(i_start, i_inc, i_max) + +// SAR sits BELOW price when trend is up, ABOVE price when trend is +// down. Flip events: prior bar SAR was on opposite side of close. +bool sar_below_now = sar_val < close +bool sar_below_prior = sar_val[1] < close[1] + +bool bull_flip = sar_below_now and not sar_below_prior +bool bear_flip = not sar_below_now and sar_below_prior + +// ---- entries on flip ---------------------------------------------- +if bull_flip and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="sar bull flip") + +if bear_flip and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="sar bear flip") diff --git a/tests/gate-corpus/ok/validation__ta-sma-152-close-cross-01.pine b/tests/gate-corpus/ok/validation__ta-sma-152-close-cross-01.pine new file mode 100644 index 0000000..114d75e --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-sma-152-close-cross-01.pine @@ -0,0 +1,27 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe 05 — close crosses SMA(152) +// +// Purpose: isolate ta.sma(close, 152) at a long-window length (152 ≈ ~38h on +// 15m TF). Validates engine sliding-window sum precision: at length 152, naive +// running-sum drift is meaningful (~1.5e-13 per bar, accumulating over thousands +// of bars). Pine's TV runtime maintains a deque + recompute periodicity that +// engine must match to stay bit-exact. +// +// Targets gap probe in a community-style market-structure-shift strategy +// which uses SMA(close, 152). +//@version=6 +strategy("PF TA isolate 05 - close x SMA(152)", shorttitle="TAI_05_SMA152", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +s = ta.sma(close, 152) + +if ta.crossover(close, s) + strategy.entry("L", strategy.long, comment="close cross up SMA152") +if ta.crossunder(close, s) + strategy.entry("S", strategy.short, comment="close cross dn SMA152") diff --git a/tests/gate-corpus/ok/validation__ta-sma-dual-cross-01.pine b/tests/gate-corpus/ok/validation__ta-sma-dual-cross-01.pine new file mode 100644 index 0000000..596232e --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-sma-dual-cross-01.pine @@ -0,0 +1,54 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 100 — dual-SMA crossover entries +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises the canonical dual-MA crossover surface with +// `ta.sma(close, fast)` and `ta.sma(close, slow)` plus +// `ta.crossover` / `ta.crossunder` for entry triggers. SMA is the +// cleanest TA building block (no recursive seed), so any TV/engine +// drift here points at how the engine's `crossover` helper detects +// the strict inequality flip from one bar to the next — and at how +// the SMA-windowing handles its first `slow` bars of warmup +// (during which TradingView's chart, which has bars before the CSV +// start, may already report a non-na SMA where the engine reports +// `na`). +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every long/short entry +// fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 100 - dual sma cross", shorttitle="PF_p100_DSMA", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_fast = input.int(9, "Fast SMA length", minval=2, maxval=100) +int i_slow = input.int(21, "Slow SMA length", minval=3, maxval=200) + +// ---- the two SMAs -------------------------------------------------- +float fast_ma = ta.sma(close, i_fast) +float slow_ma = ta.sma(close, i_slow) + +// ---- crossover-driven entries ------------------------------------- +bool golden_cross = ta.crossover(fast_ma, slow_ma) +bool death_cross = ta.crossunder(fast_ma, slow_ma) + +if golden_cross and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="golden cross") + +if death_cross and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="death cross") diff --git a/tests/gate-corpus/ok/validation__ta-stdev-sma-expansion-break-01.pine b/tests/gate-corpus/ok/validation__ta-stdev-sma-expansion-break-01.pine new file mode 100644 index 0000000..089f273 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-stdev-sma-expansion-break-01.pine @@ -0,0 +1,56 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 105 — volatility-expansion close break +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises the rolling `ta.stdev(close, 20)` pair with +// `ta.sma(close, 20)` as a Bollinger-style expansion band, then +// fires entries when the close itself breaks past the upper or +// lower band. Two engine surfaces are pinned: +// (1) `ta.stdev` denominator convention — sample (n-1) vs +// population (n) divergence shifts the band slightly and +// flips a few borderline-bar break events. +// (2) Strict close-vs-band comparison ordering relative to the +// band's own update on the same bar (the band is computed +// from the same close that's being compared to it). +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every break-driven +// entry fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 105 - volty expansion close", shorttitle="PF_p105_VEXP", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_len = input.int(20, "Baseline / stdev length", minval=2, maxval=200) +float i_mult = input.float(2.0,"Expansion multiple", minval=0.5, maxval=10.0, step=0.1) + +// ---- baseline + expansion bands ----------------------------------- +float baseline = ta.sma(close, i_len) +float vol = ta.stdev(close,i_len) +float upper = baseline + vol * i_mult +float lower = baseline - vol * i_mult + +// ---- close-vs-band breakout entries ------------------------------- +bool bull_break = close > upper +bool bear_break = close < lower + +if bull_break and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="upper-band break") + +if bear_break and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="lower-band break") diff --git a/tests/gate-corpus/ok/validation__ta-stoch-slow-k-d-cross-01.pine b/tests/gate-corpus/ok/validation__ta-stoch-slow-k-d-cross-01.pine new file mode 100644 index 0000000..b566ac5 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-stoch-slow-k-d-cross-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 103 — slow stochastic %K/%D crossover +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises `ta.stoch(close, high, low, length)` (which +// returns the raw fast %K) plus the standard slow-stochastic +// smoothing chain: `slowK = ta.sma(rawK, smooth)` and then +// `slowD = ta.sma(slowK, dLen)` (D is an SMA of the slow K, not of +// the raw K — otherwise slowK and D would be the same series and +// never cross). Trade trigger uses `ta.crossover` / `ta.crossunder` +// of slowK against slowD, with a band gate to avoid entries at +// extreme overbought/oversold. The engine surface under test is the +// stacked-SMA recurrence and `ta.crossover`/`ta.crossunder` ordering +// against TradingView's reference. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every long/short entry +// fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 103 - stoch slow cross", shorttitle="PF_p103_STO", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +int i_k_len = input.int(14, "Stoch %K length", minval=1, maxval=100) +int i_smooth = input.int(3, "Slow %K smoothing", minval=1, maxval=20) +int i_d_len = input.int(3, "%D smoothing", minval=1, maxval=20) + +// ---- raw %K from ta.stoch, then slow-smoothed -------------------- +// ta.stoch in v6 returns the raw fast %K. Slow-stochastic recipe: +// slowK = SMA(rawK, smooth) (3 by default) +// slowD = SMA(slowK, dLen) (3 by default) +// D is computed from slowK, NOT from rawK, so the two series can +// actually cross. +float raw_k = ta.stoch(close, high, low, i_k_len) +float slow_k = ta.sma(raw_k, i_smooth) +float slow_d = ta.sma(slow_k, i_d_len) + +// ---- crossover entries with band gate ------------------------------ +// Band gate prevents entries at extreme readings (long while already +// at overbought ≥ 80, short while at oversold ≤ 20). Entries flip +// position so longs and shorts can alternate. +bool bull_cross = ta.crossover(slow_k, slow_d) and slow_k < 80 +bool bear_cross = ta.crossunder(slow_k, slow_d) and slow_k > 20 + +if bull_cross + strategy.entry("L", strategy.long, comment="slowK cross above D") + +if bear_cross + strategy.entry("S", strategy.short, comment="slowK cross below D") diff --git a/tests/gate-corpus/ok/validation__ta-stochastic-rsi-cross-01.pine b/tests/gate-corpus/ok/validation__ta-stochastic-rsi-cross-01.pine new file mode 100644 index 0000000..b33463b --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-stochastic-rsi-cross-01.pine @@ -0,0 +1,39 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 04 — stochastic-RSI cross +// Purpose: parity test for the ta.rsi → ta.stoch → ta.sma double-smoothing chain +// with overbought/oversold-gated crossover entries. +// +//@version=6 +strategy("Stochastic RSI", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +rsiLen = input.int(14, "RSI Length", minval=1) +stochLen = input.int(14, "Stoch Length", minval=1) +kSmooth = input.int(3, "K Smoothing", minval=1) +dSmooth = input.int(3, "D Smoothing", minval=1) +obLevel = input.int(80, "Overbought") +osLevel = input.int(20, "Oversold") + +// === Calculations === +rsiVal = ta.rsi(close, rsiLen) +stochK = ta.sma(ta.stoch(rsiVal, rsiVal, rsiVal, stochLen), kSmooth) +stochD = ta.sma(stochK, dSmooth) + +// === Signals === +longCond = ta.crossover(stochK, stochD) and stochK < osLevel +shortCond = ta.crossunder(stochK, stochD) and stochK > obLevel + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit === +if strategy.position_size > 0 and stochK > obLevel and ta.crossunder(stochK, stochD) + strategy.close("Long") +if strategy.position_size < 0 and stochK < osLevel and ta.crossover(stochK, stochD) + strategy.close("Short") diff --git a/tests/gate-corpus/ok/validation__ta-str-match-regex-filter-01.pine b/tests/gate-corpus/ok/validation__ta-str-match-regex-filter-01.pine new file mode 100644 index 0000000..15dfe1a --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-str-match-regex-filter-01.pine @@ -0,0 +1,34 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 37 — str.lower / str.match rule-string parser +// Purpose: parity test for input-string parsing via `str.match` regex tests +// driving boolean rule flags that gate signal composition. +// +//@version=6 +strategy("Regex String Filter", overlay=false, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +rules = input.string("MACD=LONG,RSI>50", "Rule Set") +lrules = str.lower(rules) +useRsi = false +useMacd = false + +if str.match(lrules, "rsi>50") != "" + useRsi := true +if str.match(lrules, "macd=long") != "" + useMacd := true + +rsiVal = ta.rsi(close, 14) +[macdLine, sigLine, _] = ta.macd(close, 12, 26, 9) + +longCond = true +if useRsi and rsiVal <= 50 + longCond := false +if useMacd and macdLine <= sigLine + longCond := false + +if longCond + strategy.entry("Long", strategy.long) +else + strategy.close("Long") diff --git a/tests/gate-corpus/ok/validation__ta-supertrend-adx-filter-01.pine b/tests/gate-corpus/ok/validation__ta-supertrend-adx-filter-01.pine new file mode 100644 index 0000000..7275413 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-supertrend-adx-filter-01.pine @@ -0,0 +1,45 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 32 — Supertrend direction + ADX-strength filter +// Purpose: parity test for ta.supertrend() tuple-return (value+direction) +// combined with ta.dmi() ADX gate. +// +//@version=6 +strategy("Supertrend ADX Filter", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +stFactor = input.float(3.0, "Supertrend Factor", step=0.1) +stPeriod = input.int(10, "Supertrend ATR Period", minval=1) +diLen = input.int(14, "DI Length", minval=1) +adxSmooth = input.int(14, "ADX Smoothing", minval=1) +adxThresh = input.int(20, "ADX Threshold") + +// === Supertrend (tuple return) === +[stValue, stDirection] = ta.supertrend(stFactor, stPeriod) + +// === DMI (triple tuple return) === +[diPlus, diMinus, adxVal] = ta.dmi(diLen, adxSmooth) + +// === Combined signals === +// Supertrend direction change + ADX confirms trend strength +stBull = stDirection < 0 +stBear = stDirection > 0 +adxStrong = adxVal > adxThresh + +longCond = stBull and not stBull[1] and adxStrong +shortCond = stBear and not stBear[1] and adxStrong + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit on supertrend flip === +if strategy.position_size > 0 and stBear + strategy.close("Long") +if strategy.position_size < 0 and stBull + strategy.close("Short") + +plot(stValue, "Supertrend", color=stBull ? color.green : color.red, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-supertrend-direction-flip-01.pine b/tests/gate-corpus/ok/validation__ta-supertrend-direction-flip-01.pine new file mode 100644 index 0000000..be13b2e --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-supertrend-direction-flip-01.pine @@ -0,0 +1,65 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 104 — supertrend direction flip +// Clean-room: written from validation goal, not from source. +// +// Purpose: exercises `ta.supertrend(factor, atrPeriod)` — a v6 +// built-in that returns a `[trail, dir]` tuple. The `dir` value is +// `-1` when supertrend is below price (uptrend) and `+1` when above +// (downtrend); a flip happens when the close crosses the trailing +// stop. Reproducing the TV trade list requires the engine to +// match TV on (a) the ATR seeding inside the supertrend call +// (Wilder smoothing, length 10), (b) the trailing-stop carry-state +// across bars, and (c) the dir polarity convention (TV's is +// `-1 = up, +1 = down`). This probe routes entries through the +// `dir` flip event so the trade list is a direct readout of +// flip-bar agreement. +// +// Validation goal: bit-exact trade list against TradingView's broker +// emulator on the reference 15m feed. Pass = every flip-driven +// entry fires on the same bar TradingView places it. +// +// TV setup: 15m chart, ETH-USDT-USDT (Binance USDT-M Perpetual). Full +// window matching corpus/data/ohlcv_ETH-USDT-USDT_15m.csv. Strategy +// properties: leave defaults from script header. Export "List of +// trades" → tv_trades.csv. +//@version=6 +strategy("PF probe 104 - supertrend flip", shorttitle="PF_p104_ST", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, + slippage=0, default_qty_type=strategy.fixed, default_qty_value=1, + pyramiding=1, process_orders_on_close=false) + +// ---- inputs -------------------------------------------------------- +float i_factor = input.float(3.0, "Supertrend factor", minval=0.5, maxval=10.0, step=0.1) +int i_atr_len = input.int(10, "Supertrend ATR length", minval=2, maxval=100) + +// ---- supertrend tuple --------------------------------------------- +[st_trail, st_dir] = ta.supertrend(i_factor, i_atr_len) + +// dir convention: -1 = up (price above trail), +1 = down. Flip events +// compare current dir to prior-bar dir captured in a `var int` carrier +// rather than `st_dir[1]` history access — the latter exercises a +// codegen path on tuple-destructured int members that is currently +// not supported by the transpiler. The validation surface (ta.supertrend +// tuple return + flip-driven entry) is unchanged. +var float prev_dir = 0.0 +bool bull_flip = st_dir == -1 and prev_dir == 1 +bool bear_flip = st_dir == 1 and prev_dir == -1 + +// ---- entries on flip ---------------------------------------------- +if bull_flip and strategy.position_size <= 0 + if strategy.position_size < 0 + strategy.close("S", comment="flip flat") + strategy.entry("L", strategy.long, comment="st bull flip") + +if bear_flip and strategy.position_size >= 0 + if strategy.position_size > 0 + strategy.close("L", comment="flip flat") + strategy.entry("S", strategy.short, comment="st bear flip") + +// Carry current dir for the next bar's flip comparison. Must be the +// last write so the comparisons above see the prior bar's value. +prev_dir := st_dir diff --git a/tests/gate-corpus/ok/validation__ta-triple-sma-stack-latch-01.pine b/tests/gate-corpus/ok/validation__ta-triple-sma-stack-latch-01.pine new file mode 100644 index 0000000..c89ef3e --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-triple-sma-stack-latch-01.pine @@ -0,0 +1,46 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 27 — 3-MA stack with var-bool transition latch +// Purpose: parity test for triple-SMA stack with `var bool prevBull / prevBear` +// latch suppressing duplicate same-direction entries. +// +//@version=6 +strategy("MA Stack Array", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +ma1Len = input.int(10, "MA 1 Length", minval=2) +ma2Len = input.int(20, "MA 2 Length", minval=2) +ma3Len = input.int(50, "MA 3 Length", minval=2) + +// === Calculate MAs === +ma1 = ta.sma(close, ma1Len) +ma2 = ta.sma(close, ma2Len) +ma3 = ta.sma(close, ma3Len) + +// Check stack order directly (no array crash risk) +bullStack = ma1 > ma2 and ma2 > ma3 +bearStack = ma1 < ma2 and ma2 < ma3 + +// === Track transitions === +var bool prevBull = false +var bool prevBear = false + +if bullStack and not prevBull + strategy.entry("Long", strategy.long) +if bearStack and not prevBear + strategy.entry("Short", strategy.short) + +prevBull := bullStack +prevBear := bearStack + +// === Exit when stack breaks === +if strategy.position_size > 0 and not bullStack + strategy.close("Long") +if strategy.position_size < 0 and not bearStack + strategy.close("Short") + +plot(ma1, "MA 10", color=color.blue) +plot(ma2, "MA 20", color=color.green) +plot(ma3, "MA 50", color=color.red) diff --git a/tests/gate-corpus/ok/validation__ta-tsi-25-13-signal-cross-01.pine b/tests/gate-corpus/ok/validation__ta-tsi-25-13-signal-cross-01.pine new file mode 100644 index 0000000..d30874c --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-tsi-25-13-signal-cross-01.pine @@ -0,0 +1,22 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — TSI(25,13) vs its EMA(13) signal cross. +// Isolates ta.tsi (double-smoothed momentum). One of the volume/state TA +// built-ins with ZERO TradingView-parity coverage in the corpus; the engine's +// TSI carries a known x100-scaling suspect that only a TV export can adjudicate. +//@version=6 +strategy("PF TA isolate - TSI(25,13) signal cross", shorttitle="TAI_TSI", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +tsi = ta.tsi(close, 25, 13) +sig = ta.ema(tsi, 13) + +if ta.crossover(tsi, sig) + strategy.entry("L", strategy.long, comment="tsi cross up") +if ta.crossunder(tsi, sig) + strategy.entry("S", strategy.short, comment="tsi cross dn") diff --git a/tests/gate-corpus/ok/validation__ta-volume-spike-atr-breakout-01.pine b/tests/gate-corpus/ok/validation__ta-volume-spike-atr-breakout-01.pine new file mode 100644 index 0000000..95b62a8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-volume-spike-atr-breakout-01.pine @@ -0,0 +1,40 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 26 — volume-spike + ATR breakout +// Purpose: parity test for combined ta.sma(volume) spike detection and +// ta.atr-derived close-distance breakout confirmation. +// +//@version=6 +strategy("Volume Breakout", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +volLen = input.int(20, "Volume MA Length", minval=5) +volMult = input.float(1.5, "Volume Spike Multiplier", step=0.1) +atrLen = input.int(14, "ATR Length", minval=1) +atrMult = input.float(1.0, "ATR Breakout Multiplier", step=0.1) + +// === Indicators === +volMA = ta.sma(volume, volLen) +volSpike = volume > volMA * volMult +atrVal = ta.atr(atrLen) + +// === Price breakout with volume confirmation === +priceBreakUp = close > close[1] + atrVal * atrMult +priceBreakDown = close < close[1] - atrVal * atrMult + +// === Signals === +longCond = priceBreakUp and volSpike +shortCond = priceBreakDown and volSpike + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +// === Exit when volume dries up === +if strategy.position_size > 0 and close < close[1] and volume < volMA + strategy.close("Long") +if strategy.position_size < 0 and close > close[1] and volume < volMA + strategy.close("Short") diff --git a/tests/gate-corpus/ok/validation__ta-vwma-vs-sma-divergence-01.pine b/tests/gate-corpus/ok/validation__ta-vwma-vs-sma-divergence-01.pine new file mode 100644 index 0000000..36a4b5c --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-vwma-vs-sma-divergence-01.pine @@ -0,0 +1,33 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 14 — VWMA vs SMA divergence +// Purpose: parity test for ta.vwma() (volume-weighted MA) against ta.sma() with +// zero-cross of their difference as the entry trigger. +// +//@version=6 +strategy("VWMA vs SMA Divergence", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// === Inputs === +len = input.int(20, "Length", minval=5) + +// === VWMA vs SMA === +vwmaVal = ta.vwma(close, len) +smaVal = ta.sma(close, len) + +// === Divergence: VWMA > SMA = volume-weighted buying pressure === +vwmaDiff = vwmaVal - smaVal + +// === Signals === +longCond = ta.crossover(vwmaDiff, 0) +shortCond = ta.crossunder(vwmaDiff, 0) + +// === Strategy === +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +plot(vwmaVal, "VWMA", color=color.blue, linewidth=2) +plot(smaVal, "SMA", color=color.orange, linewidth=2) diff --git a/tests/gate-corpus/ok/validation__ta-wpr-14-bands-01.pine b/tests/gate-corpus/ok/validation__ta-wpr-14-bands-01.pine new file mode 100644 index 0000000..7e9d4fb --- /dev/null +++ b/tests/gate-corpus/ok/validation__ta-wpr-14-bands-01.pine @@ -0,0 +1,22 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF TA-isolate probe — Williams %R (14) crossing the -80 / -20 bands. +// Isolates ta.wpr(length): %R = (highest(high,len) - close) / (highest - lowest) * -100, +// a -100..0 oscillator with close/high/lowest implicit sources. Drives entries off the +// classic oversold/overbought band crosses so a TV export can adjudicate the engine's +// %R range, sign, and rolling highest/lowest window alignment. +//@version=6 +strategy("PF TA isolate - WPR(14) bands", shorttitle="TAI_WPR", + overlay=false, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +w = ta.wpr(14) + +if ta.crossover(w, -80.0) + strategy.entry("L", strategy.long, comment="wpr cross up -80") +if ta.crossunder(w, -20.0) + strategy.entry("S", strategy.short, comment="wpr cross dn -20") diff --git a/tests/gate-corpus/ok/validation__timeframe-main-period-self-adaptive-01.pine b/tests/gate-corpus/ok/validation__timeframe-main-period-self-adaptive-01.pine new file mode 100644 index 0000000..1a7f197 --- /dev/null +++ b/tests/gate-corpus/ok/validation__timeframe-main-period-self-adaptive-01.pine @@ -0,0 +1,34 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// Parity probe: timeframe.main_period gates entries on 15-minute chart +// +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: ETHUSDT.P (Binance USDT-M Perpetual) TF: 15m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. +// +// Purpose: verify timeframe.main_period returns the chart's timeframe string +// (e.g. "15" for a 15-minute chart). Strategy gates entries on the +// condition timeframe.main_period == "15" to confirm the value is +// correct. On a 15m chart all entries should fire; on any other +// timeframe no entries fire. +//@version=6 +strategy("PF timeframe.main_period adaptive", shorttitle="PF_TF_MAINPERIOD", + overlay=true, initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// Only enter when running on 15-minute data (corpus default). +// Use timeframe.main_period directly in comparison to avoid type-inference issues. +bool on_15m = timeframe.main_period == "15" + +fast = ta.sma(close, 10) +slow = ta.sma(close, 30) + +if on_15m and ta.crossover(fast, slow) and strategy.position_size <= 0 + strategy.entry("L", strategy.long) + +if on_15m and ta.crossunder(fast, slow) and strategy.position_size >= 0 + strategy.entry("S", strategy.short) diff --git a/tests/gate-corpus/ok/validation__udt-method-calls-sibling-cumulative-01.pine b/tests/gate-corpus/ok/validation__udt-method-calls-sibling-cumulative-01.pine new file mode 100644 index 0000000..0c5d2c8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-calls-sibling-cumulative-01.pine @@ -0,0 +1,98 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 18 (stretch) — method body calls another method +// on the same UDT +// +// Purpose: a UDT method can invoke a sibling method on ``self`` — +// e.g. ``method refresh(Box self) => self.update(close); self.normalize()``. +// The C++ runtime must resolve the inner ``self.update`` and +// ``self.normalize`` to the right ``_udt_Box_update(self, ...)`` +// free-function calls and pass ``self`` (the reference) through. +// Stretch probe: this is a deeper nesting than the basic dispatch +// tests in test_udt_methods.py — the chain here is +// refreshAndScore -> feed +// refreshAndScore -> zscore -> mean +// refreshAndScore -> zscore -> variance -> mean +// so ``self`` has to thread through three live frames. +// +// Design note (rev 2): the previous revision used a CUMULATIVE Stat +// (``self.sum`` / ``self.sumsq`` / ``self.n`` accumulating from bar 0). +// That made the trade list sensitive to chart history — TV's chart +// reaches further back than our reference OHLCV CSV's warmup region, +// so the cumulative variance — and therefore the entry gate ``z > 1`` +// — diverged by months between the engine and TV. This rev keeps the +// same multi-level method-calls-method surface but routes Pine's +// bounded-window ``ta.sma`` / ``ta.stdev`` THROUGH the method chain +// instead of accumulating an unbounded stat. The engine's UDT +// dispatch is still the contract under test (probe 21 covers the +// shallow variant); the math is just bounded so the trade list is +// reproducible from the OHLCV CSV alone — any TV/engine drift here +// is attributable to nested-method dispatch and not to pre-CSV +// chart history. +// +// Trade shape: long-only EMA crossover. Each bar we call +// ``stat.refreshAndScore(close, sma, sd)`` which internally calls +// ``self.feed(close, sma, sd)`` and then ``self.zscore()``; +// ``zscore`` itself dispatches into ``self.mean()`` and +// ``self.variance()`` (which dispatches into ``self.mean()`` again). +// Entry fires on z > 1 with EMA crossover, exit on the opposing cross. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 18 - method calls method", shorttitle="UDT_p18_M2M", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Stat + float last = na + float xMean = na + float xSd = na + int n = 0 + +method feed(Stat self, float v, float m, float s) => + self.last := v + self.xMean := m + self.xSd := s + self.n := self.n + 1 + self.n + +method mean(Stat self) => + self.xMean + +// Calls ``mean`` only to keep the nested-dispatch contract live — +// the actual variance comes from the externally-provided sd. +method variance(Stat self) => + float _m = self.mean() + self.xSd * self.xSd + +method zscore(Stat self) => + float m = self.mean() + float v2 = self.variance() + float sd = math.sqrt(v2) + na(sd) or sd == 0.0 ? 0.0 : (self.last - m) / sd + +method refreshAndScore(Stat self, float v, float m, float s) => + int _ = self.feed(v, m, s) + self.zscore() + +var Stat stat = Stat.new(na, na, na, 0) + +int statLen = 50 +float smaVal = ta.sma(close, statLen) +float sdVal = ta.stdev(close, statLen) + +float z = stat.refreshAndScore(close, smaVal, sdVal) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and z > 1.0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-default-param-kwargs-01.pine b/tests/gate-corpus/ok/validation__udt-method-default-param-kwargs-01.pine new file mode 100644 index 0000000..a1aace6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-default-param-kwargs-01.pine @@ -0,0 +1,64 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 04 — default parameters and kwargs +// +// **EXPECTED-FAIL probe (C++ runtime gap)**: UDT method default +// values are NOT materialized at the call site. PineForge currently +// emits ``_udt_Cfg_threshold(Cfg& self, double mult, double base)`` +// but lowers ``cfg.threshold()`` and ``cfg.threshold(2.0)`` with too +// few arguments — clang errors with "too few arguments to function +// call". When the codegen learns to substitute declared defaults +// for missing args (and to thread kwargs through the merged-arg +// helper for UDT methods), this probe should compile. +// +// Purpose: UDT methods may declare default values on extra parameters +// and callers may invoke them positionally, by kwarg, or omit defaults +// entirely. The C++ runtime must materialize defaults at the call +// site (PineScript has no overloading) and the codegen must merge +// kwargs in declaration order. +// +// Trade shape (when fixed): long-only EMA crossover. We probe three +// call styles — ``cfg.threshold()``, ``cfg.threshold(atrVal)``, and +// ``cfg.threshold(mult=2.0, base=rsiVal)`` — and feed each result +// into the entry gate. Because the second and third call sites +// inject series values (atr, rsi) the resulting ``trigger`` actually +// varies bar-to-bar and the gate filters trades non-trivially. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 04 - default param", shorttitle="UDT_p04_DEF", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Cfg + float factor = 1.0 + +method threshold(Cfg self, float mult = 1.0, float base = 0.0) => + base + self.factor * mult + +var Cfg cfg = Cfg.new(1.5) + +rsiVal = ta.rsi(close, 14) +atrVal = ta.atr(14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +float t0 = cfg.threshold() // 0 + 1.5 * 1 = 1.5 +float t1 = cfg.threshold(atrVal) // 0 + 1.5 * atr (varies) +float t2 = cfg.threshold(mult=2.0, base=rsiVal) // rsi + 1.5 * 2 (varies) + +float trigger = t0 + t1 + t2 + +bool entryCond = ta.crossover(emaFast, emaSlow) and trigger > 80.0 and t1 > t0 +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-drives-strategy-entry-01.pine b/tests/gate-corpus/ok/validation__udt-method-drives-strategy-entry-01.pine new file mode 100644 index 0000000..531f7a9 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-drives-strategy-entry-01.pine @@ -0,0 +1,62 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 14 — method return drives strategy.entry directly +// +// Purpose: ``if signal.shouldEnterLong()`` must evaluate the UDT +// method, then immediately invoke ``strategy.entry`` only when the +// returned bool is true. The C++ runtime must guarantee evaluation +// happens once per bar inside the if-condition, not duplicated by +// the order block. Conversely the method's side effects (mutating +// internal counters) must persist exactly once. +// +// Trade shape: long + short. Signal toggles direction based on EMA +// regime; methods both decide the signal AND maintain a debounce +// counter to prevent flip-flopping in the same direction within 5 bars. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 14 - method drives entry", shorttitle="UDT_p14_ENT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Signal + int last_long_bar = -1000 + int last_short_bar = -1000 + int debounce = 5 + +method shouldEnterLong(Signal self, bool cross) => + bool spaced = (bar_index - self.last_long_bar) >= self.debounce + bool ok = cross and spaced + if ok + self.last_long_bar := bar_index + ok + +method shouldEnterShort(Signal self, bool cross) => + bool spaced = (bar_index - self.last_short_bar) >= self.debounce + bool ok = cross and spaced + if ok + self.last_short_bar := bar_index + ok + +var Signal sig = Signal.new(-1000, -1000, 5) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) +bool xUp = ta.crossover(emaFast, emaSlow) +bool xDown = ta.crossunder(emaFast, emaSlow) + +if sig.shouldEnterLong(xUp) + if strategy.position_size < 0 + strategy.close("S", comment="flip to long") + strategy.entry("L", strategy.long, qty=1, comment="entry long") + +if sig.shouldEnterShort(xDown) + if strategy.position_size > 0 + strategy.close("L", comment="flip to short") + strategy.entry("S", strategy.short, qty=1, comment="entry short") diff --git a/tests/gate-corpus/ok/validation__udt-method-extra-primitive-args-01.pine b/tests/gate-corpus/ok/validation__udt-method-extra-primitive-args-01.pine new file mode 100644 index 0000000..5e487ab --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-extra-primitive-args-01.pine @@ -0,0 +1,42 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 03 — extra primitive args (int / float / bool) +// +// Purpose: UDT methods can take additional primitive parameters after +// ``self``. The C++ runtime must lower the signature to +// ``_udt_Box_score(Box& self, int n, double mult, bool flip)`` and +// pass arguments in the right order (positional + kwargs supported). +// Drift here means parameter binding for non-self args is broken. +// +// Trade shape: bracketed long entry whose offset depends on ATR(n) +// scaled by the method's ``mult`` and ``flip`` parameters. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 03 - extra args", shorttitle="UDT_p03_ARGS", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Cfg + float base = 100.0 + float bias = 0.0 + +method offset(Cfg self, int n, float mult, bool flip) => + int sign = flip ? -1 : 1 + self.base + (mult * float(n) * float(sign)) + self.bias + +var Cfg cfg = Cfg.new(0.0, 0.0) +cfg.base := close + +float upOff = cfg.offset(5, 0.10, false) +float downOff = cfg.offset(n=5, mult=0.10, flip=true) + +if ta.crossover(close, ta.sma(close, 20)) and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", limit=upOff, stop=downOff, comment="bracket long") diff --git a/tests/gate-corpus/ok/validation__udt-method-feeds-strategy-exit-prices-01.pine b/tests/gate-corpus/ok/validation__udt-method-feeds-strategy-exit-prices-01.pine new file mode 100644 index 0000000..bf8506d --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-feeds-strategy-exit-prices-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 15 — method outputs feed strategy.exit prices +// +// Purpose: ``strategy.exit(stop=, limit=)`` must accept the result of +// a UDT method call as the price level. The C++ runtime must evaluate +// the method exactly once per call site and pass the materialized +// double through to the runtime's exit handler. This also exercises +// the ``Box& self`` reference path when the method's body reads state +// initialized at entry time. +// +// Trade shape: long-only. At entry we capture entry price + atr into +// the bracket UDT, then on every bar while in position we recompute +// stop and limit via methods (so they "set once at entry, but expressed +// each bar as a function of the captured snapshot"). Mirrors +// validation/88's "exit set once at entry" shape but through methods. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 15 - exit prices via method", shorttitle="UDT_p15_EXP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Bracket + float entry_px = na + float entry_atr = na + float stop_mult = 1.5 + float limit_mult = 3.0 + +method stopPrice(Bracket self) => + self.entry_px - self.entry_atr * self.stop_mult + +method limitPrice(Bracket self) => + self.entry_px + self.entry_atr * self.limit_mult + +var Bracket br = Bracket.new(na, na, 1.5, 3.0) + +float atrVal = ta.atr(14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and strategy.position_size == 0 + br.entry_px := close + br.entry_atr := atrVal + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=br.stopPrice(), + limit=br.limitPrice(), + comment="bracket from method") diff --git a/tests/gate-corpus/ok/validation__udt-method-in-for-loop-01.pine b/tests/gate-corpus/ok/validation__udt-method-in-for-loop-01.pine new file mode 100644 index 0000000..ff14fa2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-in-for-loop-01.pine @@ -0,0 +1,55 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 07 — method called inside a for loop +// +// Purpose: the C++ runtime must dispatch a UDT method once per loop +// iteration, with the same receiver instance, accumulating side +// effects in self. If the codegen accidentally hoists the call or +// re-initializes the receiver, the accumulator drifts. +// +// Trade shape: long-only. ``acc.add(price)`` is called in a 5-iter +// loop over the most recent 5 closes; we enter long when the +// computed mean exceeds the slow EMA. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 07 - method in for", shorttitle="UDT_p07_FOR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Acc + float total = 0.0 + int n = 0 + +method add(Acc self, float v) => + self.total := self.total + v + self.n := self.n + 1 + self.total + +method mean(Acc self) => + self.n > 0 ? self.total / float(self.n) : 0.0 + +method reset(Acc self) => + self.total := 0.0 + self.n := 0 + 0 + +var Acc acc = Acc.new(0.0, 0) + +int _r = acc.reset() +for i = 0 to 4 + float _t = acc.add(close[i]) + +float avg5 = acc.mean() +emaSlow = ta.ema(close, 21) + +if avg5 > emaSlow and ta.crossover(close, emaSlow) and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(close, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-in-if-else-branch-01.pine b/tests/gate-corpus/ok/validation__udt-method-in-if-else-branch-01.pine new file mode 100644 index 0000000..f14ec33 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-in-if-else-branch-01.pine @@ -0,0 +1,65 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 05 — method called inside if/else if branches +// +// Purpose: a UDT method invoked inside a conditional must only be +// called when the branch is taken. Side-effecting methods (mutating +// self) reveal extra/missing calls. The C++ runtime should not +// hoist the call out of the if/else if into the global scope; the +// streak counter must only advance on the active branch. +// +// Trade shape: long-only. ``regime.tag(int)`` mutates an internal +// streak field for the matching regime (bull / bear / flat). Entry +// fires when bull streak reaches 3 in a row. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 05 - method in if", shorttitle="UDT_p05_IF", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Regime + int bull = 0 + int bear = 0 + int flat = 0 + +method tag(Regime self, int bucket) => + if bucket == 1 + self.bull := self.bull + 1 + self.bear := 0 + self.flat := 0 + else if bucket == -1 + self.bear := self.bear + 1 + self.bull := 0 + self.flat := 0 + else + self.flat := self.flat + 1 + self.bull := 0 + self.bear := 0 + self.bull + +var Regime regime = Regime.new(0, 0, 0) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +int bullStreak = na +if emaFast > emaSlow * 1.001 + bullStreak := regime.tag(1) +else if emaFast < emaSlow * 0.999 + int _ignored = regime.tag(-1) + bullStreak := regime.bull +else + int _ignored = regime.tag(0) + bullStreak := regime.bull + +if not na(bullStreak) and bullStreak >= 3 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if regime.bear >= 3 and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-in-switch-arms-01.pine b/tests/gate-corpus/ok/validation__udt-method-in-switch-arms-01.pine new file mode 100644 index 0000000..e9c8ddb --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-in-switch-arms-01.pine @@ -0,0 +1,64 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 06 — method called inside switch arms +// +// Purpose: switch arms must dispatch to the correct UDT method call +// only when the matching arm is taken. The C++ runtime emits switch +// arms as if/else if blocks, so this overlaps with probe 05 but adds +// the switch-with-expression form (string discriminator). A bug here +// would mean the wrong arm runs (or all arms run) for a given regime. +// +// Trade shape: long-only. The regime string controls which UDT +// method updates internal state; we enter long when bull streak +// reaches 3. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 06 - method in switch", shorttitle="UDT_p06_SW", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Counters + int bull_run = 0 + int bear_run = 0 + int flat_run = 0 + +method bumpBull(Counters self) => + self.bull_run := self.bull_run + 1 + self.bear_run := 0 + self.flat_run := 0 + self.bull_run + +method bumpBear(Counters self) => + self.bear_run := self.bear_run + 1 + self.bull_run := 0 + self.flat_run := 0 + self.bear_run + +method bumpFlat(Counters self) => + self.flat_run := self.flat_run + 1 + self.bull_run := 0 + self.bear_run := 0 + self.flat_run + +var Counters c = Counters.new(0, 0, 0) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) +string regime = emaFast > emaSlow * 1.001 ? "bull" : emaFast < emaSlow * 0.999 ? "bear" : "flat" + +int active = switch regime + "bull" => c.bumpBull() + "bear" => c.bumpBear() + => c.bumpFlat() + +if regime == "bull" and c.bull_run >= 3 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if regime == "bear" and c.bear_run >= 3 and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-in-while-loop-01.pine b/tests/gate-corpus/ok/validation__udt-method-in-while-loop-01.pine new file mode 100644 index 0000000..2c7bf1a --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-in-while-loop-01.pine @@ -0,0 +1,58 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 08 — method called inside a while loop +// +// Purpose: while-loop dispatch with bounded iteration. The C++ runtime +// must call the UDT method on every loop iteration until the +// termination condition flips. A break from the loop must stop +// dispatching. Combined with mutating-self this validates the loop +// guard is evaluated against the post-mutation state, not a stale +// snapshot. +// +// Trade shape: long-only. ``ramp.tick()`` walks backward through the +// last 10 bars: each iteration looks at ``close[counter-1]`` vs +// ``open[counter-1]``, increments ``green_count`` if the bar was +// green, and decrements the counter. Entry fires only when at least +// 7 of the last 10 bars were green AND there is an EMA cross — a +// real momentum filter that meaningfully narrows the trade list. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 08 - method in while", shorttitle="UDT_p08_WHL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Ramp + int counter = 0 + int green_count = 0 + +method tick(Ramp self) => + int idx = self.counter - 1 + bool greenBar = idx >= 0 and close[idx] > open[idx] + if greenBar + self.green_count := self.green_count + 1 + self.counter := self.counter - 1 + self.counter + +var Ramp ramp = Ramp.new(0, 0) + +ramp.counter := 10 +ramp.green_count := 0 +int safety = 0 +while ramp.counter > 0 and safety < 12 + int _c = ramp.tick() + safety := safety + 1 + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ramp.green_count >= 7 and ta.crossover(emaFast, emaSlow) and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-mutating-self-ref-01.pine b/tests/gate-corpus/ok/validation__udt-method-mutating-self-ref-01.pine new file mode 100644 index 0000000..a2f57c4 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-mutating-self-ref-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 02 — mutating self (Box& reference semantics) +// +// Purpose: a method that uses ``self.field := ...`` must mutate the +// underlying ``var`` instance, not a local copy. The C++ runtime +// emits the receiver as ``Box& self``, so each call should persist +// the new field value across bars. If we ever lose the reference +// (e.g. emit ``Box self``), the streak counter resets every bar and +// no entries fire. +// +// Trade shape: long-only. ``streak.bump(condition)`` increments an +// internal counter when ``condition`` is true and resets to zero +// otherwise. We enter once per bar when streak.count >= 3, then close +// the next bar. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 02 - mutating self", shorttitle="UDT_p02_MUT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Streak + int count = 0 + int reset_count = 0 + +method bump(Streak self, bool advance) => + if advance + self.count := self.count + 1 + else + if self.count > 0 + self.reset_count := self.reset_count + 1 + self.count := 0 + self.count + +var Streak streak = Streak.new(0, 0) + +bool greenBar = close > open +int updated = streak.bump(greenBar) + +bool entryCond = updated >= 3 and strategy.position_size == 0 +if entryCond + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if not greenBar and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-on-array-element-01.pine b/tests/gate-corpus/ok/validation__udt-method-on-array-element-01.pine new file mode 100644 index 0000000..140ea02 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-on-array-element-01.pine @@ -0,0 +1,98 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 19 (stretch) — array push + element method +// +// **EXPECTED-FAIL probe (C++ runtime gap)**: validation/38 already +// flags ``array`` and "UDT methods on array elements" as known +// gaps. This probe surfaces the concrete clang errors: +// 1. Non-var ``Sample current = Sample.new(close, rsiVal)`` is +// lowered as ``Sample current = 0.0;`` — the constructor call +// is dropped and a default-constructed double is assigned to +// a struct slot, which clang rejects. +// 2. ``array.get(window, i)`` returns ``std::vector::value_type`` +// but the codegen tracks the bound variable ``s`` as ``double``, +// so ``s.score()`` lowers to ``s::score()`` — namespace dispatch +// instead of method dispatch on a struct instance. +// When the codegen learns to (a) honour explicit UDT type +// annotations on non-var vars, and (b) propagate element types from +// ``array::get`` into the local symbol table, this probe should +// compile. +// +// Purpose: array stores struct instances; calling a method on an +// element pulled with ``arr.get(i)`` must dispatch to the correct +// ``_udt_TypeName_method(...)`` and the result must reflect the +// element's current field values, not a default-constructed copy. +// +// Trade shape (when fixed): long-only. We push a ``Sample`` with +// ``(close, rsi)`` once per bar and keep a sliding window of 5. +// We exercise BOTH access patterns of ``array``: +// - iterate the window with ``for i = 0 to len-1`` and call +// ``s.score()`` on each element, summing into ``total`` so the +// UDT method dispatches once per loop iteration, +// - read the newest element directly with ``array.get(window, len-1)`` +// and call ``newest.score()`` for the entry filter. +// Entry gate uses the LATEST element's score (rsi-rising momentum) +// rather than the window average, so it actually fires when EMA +// crosses up at the start of an uptrend (the avg-score gate at +// ``> 1.0`` fired zero times in the original draft because RSI +// is typically below 60 at the moment of a fresh crossover up). +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 19 - array of UDT method", shorttitle="UDT_p19_AUDT", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Sample + float price = na + float rsi = na + +method score(Sample self) => + if na(self.price) or na(self.rsi) + 0.0 + else + // Signed deviation from neutral RSI. Range ~[-5, +5]. + (self.rsi - 50.0) * 0.1 + +var array window = array.new() + +float rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +Sample current = Sample.new(close, rsiVal) +array.push(window, current) +if array.size(window) > 5 + array.shift(window) + +// Pattern 1: iterate the window and dispatch s.score() per iteration. +float total = 0.0 +int len = array.size(window) +for i = 0 to len - 1 + Sample s = array.get(window, i) + total := total + s.score() + +float avgScore = len > 0 ? total / float(len) : 0.0 + +// Pattern 2: index a specific element (newest), dispatch its method. +float latestScore = 0.0 +if len > 0 + Sample newest = array.get(window, len - 1) + latestScore := newest.score() + +// Entry gate uses BOTH method-dispatch results. +// - latestScore > 0 => current RSI > 50 (bullish momentum NOW) +// - avgScore > -0.5 => mean RSI of last 5 bars > 45 (gentle floor) +// The conjunction filters trades to crossovers that coincide with a +// recent run of bullish RSI. +bool entryCond = ta.crossover(emaFast, emaSlow) and latestScore > 0.0 and avgScore > -0.5 +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-reads-strategy-state-01.pine b/tests/gate-corpus/ok/validation__udt-method-reads-strategy-state-01.pine new file mode 100644 index 0000000..49b9a65 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-reads-strategy-state-01.pine @@ -0,0 +1,53 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 13 — method reads strategy.* state +// +// Purpose: a UDT method may read ``strategy.position_size``, +// ``strategy.equity``, ``strategy.netprofit``, and +// ``strategy.initial_capital`` from inside its body. The C++ runtime +// exposes these as engine accessors; the codegen must lower the +// dotted member access correctly when seen inside a method body, not +// just at top level. +// +// Trade shape: long-only EMA crossover. ``risk.allowsEntry()`` reads +// strategy state to decide if a new entry is permitted: must be +// flat, must be at least ``cooldown_bars`` bars after the last exit, +// and current drawdown must be smaller than 5% of starting capital. +// The cooldown + drawdown gates are real filters and produce a +// trade list distinct from a plain crossover baseline. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 13 - strategy state in method", shorttitle="UDT_p13_STR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Risk + int cooldown_bars = 5 + int last_exit_bar = -1000 + float max_drawdown_pct = 0.05 + +method allowsEntry(Risk self) => + bool flatOk = strategy.position_size == 0 + bool cooldownOk = (bar_index - self.last_exit_bar) >= self.cooldown_bars + float ddLimit = strategy.initial_capital * self.max_drawdown_pct + bool ddOk = strategy.netprofit >= -ddLimit + bool equityOk = strategy.equity > 0.0 + flatOk and cooldownOk and ddOk and equityOk + +var Risk risk = Risk.new(5, -1000, 0.05) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and risk.allowsEntry() + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") + risk.last_exit_bar := bar_index diff --git a/tests/gate-corpus/ok/validation__udt-method-receives-ta-series-param-01.pine b/tests/gate-corpus/ok/validation__udt-method-receives-ta-series-param-01.pine new file mode 100644 index 0000000..0397733 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-receives-ta-series-param-01.pine @@ -0,0 +1,57 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 12 — method receives ta.* result as a parameter +// +// Purpose: method parameters can be ``series float`` values from +// ``ta.*`` indicators. The C++ runtime must accept them as plain +// ``double`` arguments, not produce a per-callsite TA materialization. +// (Note: TA functions themselves are kept at GLOBAL scope per Pine +// best practice; only the resulting series is passed to the method.) +// +// Trade shape: long-only. ``filter.confirms(rsi, ema, atr)`` evaluates +// a multi-criteria condition and returns bool; entry uses it directly. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 12 - ta param", shorttitle="UDT_p12_TAP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Filter + float rsi_min = 40.0 + float rsi_max = 90.0 + float atr_min = 0.0 + +method confirms(Filter self, float rsi, float ema, float atr) => + bool rsiOk = rsi >= self.rsi_min and rsi <= self.rsi_max + bool emaOk = close > ema + bool atrOk = atr > self.atr_min + rsiOk and emaOk and atrOk + +var Filter filt = Filter.new(40.0, 90.0, 0.0) + +float rsiVal = ta.rsi(close, 14) +float emaVal = ta.ema(close, 21) +float atrVal = ta.atr(14) + +// Compute ta.crossover/crossunder at global scope BEFORE the method +// call. C++ ``&&`` is short-circuit and ``ta.crossover()`` requires +// per-bar evaluation to maintain its internal state — putting the +// method call first would skip the TA update on bars where the +// method returns false. +bool xUp = ta.crossover(close, emaVal) +bool xDown = ta.crossunder(close, emaVal) + +bool entryCond = xUp and filt.confirms(rsiVal, emaVal, atrVal) +bool exitCond = xDown + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-scalar-return-01.pine b/tests/gate-corpus/ok/validation__udt-method-scalar-return-01.pine new file mode 100644 index 0000000..b89c71c --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-scalar-return-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 01 — scalar-return method (foundational dispatch) +// +// Purpose: smallest possible UDT method probe. The C++ runtime must +// emit a free function ``_udt_Box_score(Box& self)`` that returns a +// scalar, then call it with ``_udt_Box_score(b)`` from on_bar(). Any +// drift here points at the basic UDT method dispatch, not at any +// downstream nesting. +// +// Trade shape: long-only. Each bar we store ``ta.rsi`` into the UDT's +// ``bias`` field, then ``b.score()`` returns ``self.k * (self.bias - 50)`` +// — a signed RSI offset. Entry only fires when EMA crosses up AND the +// score is meaningfully bullish (>5, i.e. RSI > ~53), which filters +// out crossovers in weak momentum and produces a trade list that +// differs from a plain crossover baseline. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 01 - scalar return", shorttitle="UDT_p01_SCAL", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Box + float k = 1.0 + float bias = 50.0 + +method score(Box self) => + self.k * (self.bias - 50.0) + +var Box b = Box.new(1.0, 50.0) + +rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +b.bias := rsiVal + +bool entryCond = ta.crossover(emaFast, emaSlow) and b.score() > 5.0 +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-tuple-return-destructure-01.pine b/tests/gate-corpus/ok/validation__udt-method-tuple-return-destructure-01.pine new file mode 100644 index 0000000..65b18d8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-tuple-return-destructure-01.pine @@ -0,0 +1,61 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 17 — method returns tuple, caller destructures +// +// **EXPECTED-FAIL probe (C++ runtime gap)**: tuple-return inference +// for UDT methods is broken in two places. The method body returns +// ``[stopPx, limitPx, self.base_qty]`` but the analyzer infers the +// method's return type as scalar ``double`` (clang: "no viable +// conversion from tuple<...> to function return type 'double'"). +// The destructuring assignment ``[stopL, limitL, qtyL] = br.levels(...)`` +// also fails to declare the local variables — they're referenced +// but never bound. Tuple returns + destructuring already work for +// regular Pine user functions; the gap is specifically in the UDT +// method codegen path. +// +// Purpose: Pine allows ``[a, b, c]`` tuple returns and ``[x, y, z] = expr`` +// destructuring. The C++ runtime must lower the method's return as +// ``std::tuple<...>`` and the destructuring as ``std::tie(...)`` or +// equivalent at the call site. +// +// Trade shape (when fixed): long + short. Method returns +// ``[stop, limit, qty]``; caller destructures and feeds straight +// into entry + bracketed exit. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 17 - tuple return", shorttitle="UDT_p17_TUP", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Bracket + float stop_mult = 1.5 + float limit_mult = 3.0 + float base_qty = 1.0 + +method levels(Bracket self, float price, float atr, bool isLong) => + float stopPx = isLong ? price - self.stop_mult * atr : price + self.stop_mult * atr + float limitPx = isLong ? price + self.limit_mult * atr : price - self.limit_mult * atr + [stopPx, limitPx, self.base_qty] + +var Bracket br = Bracket.new(1.5, 3.0, 1.0) + +float atrVal = ta.atr(14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and strategy.position_size == 0 + [stopL, limitL, qtyL] = br.levels(close, atrVal, true) + strategy.entry("L", strategy.long, qty=qtyL, comment="entry long") + strategy.exit("LX", "L", stop=stopL, limit=limitL, comment="bracket long") + +if ta.crossunder(emaFast, emaSlow) and strategy.position_size == 0 + [stopS, limitS, qtyS] = br.levels(close, atrVal, false) + strategy.entry("S", strategy.short, qty=qtyS, comment="entry short") + strategy.exit("SX", "S", stop=stopS, limit=limitS, comment="bracket short") diff --git a/tests/gate-corpus/ok/validation__udt-method-udt-return-from-func-01.pine b/tests/gate-corpus/ok/validation__udt-method-udt-return-from-func-01.pine new file mode 100644 index 0000000..b3b54f8 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-udt-return-from-func-01.pine @@ -0,0 +1,67 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 20 (stretch) — user function returns a UDT, then +// methods are called on the returned value +// +// **EXPECTED-FAIL probe (C++ runtime gap)**: validation/38 already +// flags "UDT returns from user functions" as a known gap. This probe +// surfaces the concrete clang errors: +// 1. ``build_sample(...) =>`` is emitted with return type +// ``double`` even though its body returns ``Sample.new(...)``. +// Clang rejects "no viable conversion from Sample to double". +// 2. The caller binding ``Sample s = build_sample(...)`` then has +// ``s`` typed as ``double`` (from the function's return type), +// so ``s.score()`` becomes member-access on a double. +// When the analyzer infers UDT returns for user-defined functions +// and threads the type back through to the call site's symbol table, +// this probe should compile. +// +// Purpose: ``f() => Sample.new(...)`` returns a UDT instance; the +// caller binds it to a local and immediately calls a method on it. +// The C++ runtime must lower the user function's return type as the +// matching struct (e.g. ``Sample``) and the call site as +// ``_udt_Sample_score(_tmp)``. +// +// Trade shape (when fixed): long-only EMA crossover gated on score +// returned via the user function -> method chain. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 20 - udt from user func", shorttitle="UDT_p20_UFR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Sample + float price = na + float rsi = na + float weight = 1.0 + +method score(Sample self) => + if na(self.price) or na(self.rsi) + 0.0 + else + // Signed RSI deviation from 50, weighted. Can be negative. + (self.rsi - 50.0) * 0.1 * self.weight + +build_sample(float price, float rsi, float weight) => + Sample.new(price, rsi, weight) + +float rsiVal = ta.rsi(close, 14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +Sample s = build_sample(close, rsiVal, 1.0) +float sc = s.score() + +// Gate: RSI must be > ~60 (sc > 1.0 => rsi - 50 > 10). Filters +// crossovers in weak momentum. +if ta.crossover(emaFast, emaSlow) and sc > 1.0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-uses-history-globals-01.pine b/tests/gate-corpus/ok/validation__udt-method-uses-history-globals-01.pine new file mode 100644 index 0000000..01f6852 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-uses-history-globals-01.pine @@ -0,0 +1,60 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 11 — method body uses [] history on globals +// +// Purpose: a method that references ``close[N]``, ``high[N]`` etc. +// inside its body must compile, and the C++ runtime must serve the +// per-bar series ring buffer correctly when accessed through a UDT +// method (not just inline). The history offset is driven by a UDT +// field (``self.lookback``) so the call site exercises both UDT +// member access AND series history within a single expression. +// Pine treats UDT field history (``self.x[1]``) as not supported in +// PineForge — this probe deliberately uses GLOBAL series history, +// which is supported. +// +// Naming note: TradingView's CE10236 requires the first method +// argument (``self``) to be referenced in the body — otherwise +// it should be a plain function. Both methods here read +// ``self.lookback`` so they pass the check. +// +// Trade shape: long-only. ``trend.changeRate()`` returns +// ``(close - close[lookback]) / close[lookback]``; ``trend.swingRange()`` +// returns ``high[lookback] - low[lookback]``. Entry fires when +// changeRate > 1% and EMA cross. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 11 - history in method", shorttitle="UDT_p11_HIST", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Trend + int lookback = 3 + +method changeRate(Trend self) => + float ref = close[self.lookback] + ref == 0.0 ? 0.0 : (close - ref) / ref + +method swingRange(Trend self) => + float hi = high[self.lookback] + float lo = low[self.lookback] + hi - lo + +var Trend trend = Trend.new(3) + +float r = trend.changeRate() +float swing = trend.swingRange() + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and r > 0.01 and swing > 0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-uses-math-funcs-01.pine b/tests/gate-corpus/ok/validation__udt-method-uses-math-funcs-01.pine new file mode 100644 index 0000000..65cec4e --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-uses-math-funcs-01.pine @@ -0,0 +1,59 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 09 — method body uses math.* functions +// +// Purpose: validate that the C++ runtime resolves ``math.abs``, +// ``math.max``, ``math.min``, ``math.round``, and ``math.sqrt`` from +// inside a UDT method body, not just at the global scope. This shakes +// out mistakes where the analyzer registers math.* call-sites only +// when seen at top level. +// +// Trade shape: bracketed long entry whose stop/limit prices are +// computed by ``bx.clampedStop/Limit(price, atr)`` returning a +// sanitized (rounded, clamped) value via two methods. +// +// Naming note: the variable holding the Box instance is named ``bx``, +// not ``box``. ``box`` is a built-in PineScript drawing namespace +// (``box.new``, ``box.set_*``, ...) and TradingView rejects bare +// identifier ``box`` with CE10093 ("Invalid object name: box. +// Namespaces of built-ins cannot be used."), even though PineForge +// accepts it. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 09 - math in method", shorttitle="UDT_p09_MATH", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Box + float min_offset = 1.0 + float max_offset = 50.0 + +method clampedStop(Box self, float price, float atr) => + float raw = math.abs(atr) * 1.5 + float clamped = math.max(self.min_offset, math.min(self.max_offset, raw)) + math.round(price - clamped) + +method clampedLimit(Box self, float price, float atr) => + float raw = math.sqrt(math.abs(atr)) * 5.0 + float clamped = math.max(self.min_offset, math.min(self.max_offset, raw)) + math.round(price + clamped) + +var Box bx = Box.new(1.0, 50.0) + +float atrVal = ta.atr(14) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") + strategy.exit("LX", "L", + stop=bx.clampedStop(close, atrVal), + limit=bx.clampedLimit(close, atrVal), + comment="bracket long") diff --git a/tests/gate-corpus/ok/validation__udt-method-uses-na-nz-fixnan-01.pine b/tests/gate-corpus/ok/validation__udt-method-uses-na-nz-fixnan-01.pine new file mode 100644 index 0000000..a40d13a --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-uses-na-nz-fixnan-01.pine @@ -0,0 +1,50 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 10 — method body uses na/nz/fixnan +// +// Purpose: a method whose self has float fields seeded to ``na`` must +// behave the same way as inline code. ``na(x)``, ``nz(x, fb)``, and +// ``fixnan(x)`` lower to runtime helpers (``is_na``, conditional +// fallback, persistent state). The C++ runtime must not break when +// these helpers are evaluated on a UDT field through ``self.field``. +// +// Trade shape: long-only on EMA crossover. The signal is gated by +// ``s.safeRatio()`` which uses na/nz to fold across an undefined +// initial state without spurious early entries. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 10 - na nz in method", shorttitle="UDT_p10_NA", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Sample + float prev = na + float ratio = na + +method safeRatio(Sample self, float curr) => + float p = nz(self.prev, curr) + float r = p == 0.0 ? 1.0 : curr / p + self.prev := curr + self.ratio := r + na(self.ratio) ? 1.0 : self.ratio + +var Sample s = Sample.new(na, na) + +float r = s.safeRatio(close) +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +bool entryCond = ta.crossover(emaFast, emaSlow) and r > 1.0 and not na(r) +bool exitCond = ta.crossunder(emaFast, emaSlow) + +if entryCond and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if exitCond and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-var-instance-streak-01.pine b/tests/gate-corpus/ok/validation__udt-method-var-instance-streak-01.pine new file mode 100644 index 0000000..cd0de9c --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-var-instance-streak-01.pine @@ -0,0 +1,51 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 16 — var-instance method calls accumulate across bars +// +// Purpose: ``var Counter c = ...`` initializes once on the first bar. +// All subsequent method calls on ``c`` must operate on that same C++ +// struct instance. If the codegen accidentally re-initializes the +// receiver per bar, the streak resets every bar. The probe asserts +// the cumulative ``c.green_streak`` is detectable as a series. +// +// Trade shape: long-only. Enter on green-streak >= 4 (4 consecutive +// up bars). Exit on first red bar. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 16 - var instance streak", shorttitle="UDT_p16_VAR", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +type Counter + int green_streak = 0 + int red_streak = 0 + int total_greens = 0 + int total_reds = 0 + +method observe(Counter self, bool isGreen) => + if isGreen + self.green_streak := self.green_streak + 1 + self.red_streak := 0 + self.total_greens := self.total_greens + 1 + else + self.red_streak := self.red_streak + 1 + self.green_streak := 0 + self.total_reds := self.total_reds + 1 + self.green_streak + +var Counter c = Counter.new(0, 0, 0, 0) + +bool isGreen = close > open +int gs = c.observe(isGreen) + +if gs >= 4 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if c.red_streak >= 1 and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-method-windowed-method-chain-01.pine b/tests/gate-corpus/ok/validation__udt-method-windowed-method-chain-01.pine new file mode 100644 index 0000000..b077095 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-method-windowed-method-chain-01.pine @@ -0,0 +1,78 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// UDT method probe 21 — bounded-window method-calls-method +// Purpose: parity test for nested UDT-method dispatch (a method body invokes +// a sibling method on `self`) where the underlying numeric chain is bounded +// (ta.sma + ta.stdev), so the trade list is reproducible from the OHLCV CSV +// alone — drift here isolates dispatch semantics, not history coverage. +// +// Companion / replacement coverage for probe 18. Probe 18 also tests +// "method body invokes a sibling method on ``self``", but it does so +// via a CUMULATIVE Stat (sum / sumsq / n) that depends on chart +// history. That makes the trade list sensitive to how far back the +// TV chart extends vs how far back our OHLCV CSV reaches — when the +// two diverge the engine takes different ``z > 1`` decisions even +// though the dispatch itself is correct (probe 18 currently sits at +// ~86% match against TV for exactly that reason). +// +// This probe keeps the same surface contract (refresh → feed → +// zscore, all method-on-self calls) but routes Pine's bounded-window +// ta.sma / ta.stdev THROUGH the method chain instead of accumulating +// an unbounded stat. The trade list is therefore reproducible from +// the OHLCV CSV alone — any TV/engine drift here is attributable +// to nested-method dispatch (the contract under test) and not to +// pre-CSV chart history. +// +// Trade shape: long-only EMA crossover. Each bar we call +// ``stat.refreshAndScore(close, sma, sd)`` which internally calls +// ``self.feed(close, sma, sd)`` and returns ``self.zscore()``. +// Entry fires on z > 1 with crossover, exit on the opposing cross. +// +// TV setup: 15m chart, ETH-USDT-USDT, same window as +// data/ohlcv_ETH-USDT-USDT_15m.csv. Export the trade list to trades.csv +// in this folder. +//@version=6 +strategy("PF UDT probe 21 - windowed method chain", shorttitle="UDT_p21_WMC", overlay=true, + initial_capital=1000000, currency=currency.USD, + commission_type=strategy.commission.percent, commission_value=0, slippage=0, + default_qty_type=strategy.fixed, default_qty_value=1, pyramiding=1, + process_orders_on_close=false) + +// State only stores the latest snapshot — no cumulative accumulator. +type Stat + float last = na + float mean = na + float sd = na + int n = 0 + +method feed(Stat self, float v, float m, float s) => + self.last := v + self.mean := m + self.sd := s + self.n := self.n + 1 + self.n + +method zscore(Stat self) => + na(self.sd) or self.sd == 0.0 ? 0.0 : (self.last - self.mean) / self.sd + +method refreshAndScore(Stat self, float v, float m, float s) => + int _ = self.feed(v, m, s) + self.zscore() + +var Stat stat = Stat.new(na, na, na, 0) + +int statLen = 50 +float smaVal = ta.sma(close, statLen) +float sdVal = ta.stdev(close, statLen) + +float z = stat.refreshAndScore(close, smaVal, sdVal) + +emaFast = ta.ema(close, 9) +emaSlow = ta.ema(close, 21) + +if ta.crossover(emaFast, emaSlow) and z > 1.0 and strategy.position_size == 0 + strategy.entry("L", strategy.long, qty=1, comment="entry long") +if ta.crossunder(emaFast, emaSlow) and strategy.position_size > 0 + strategy.close("L", comment="exit long") diff --git a/tests/gate-corpus/ok/validation__udt-regime-stack-stress-01.pine b/tests/gate-corpus/ok/validation__udt-regime-stack-stress-01.pine new file mode 100644 index 0000000..f54b0d2 --- /dev/null +++ b/tests/gate-corpus/ok/validation__udt-regime-stack-stress-01.pine @@ -0,0 +1,186 @@ +// This Pine Script® code is licensed under Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 +// © PineForge contributors 2026 +// +// PF probe 38 — UDT regime-stack stress (mirrors probe 30's score logic) +// Purpose: parity test for `enum`, flat `type` UDTs, and `var ` per-field +// `:=` mutation patterns supported by the engine. +// +//@version=6 +// UDT-heavy validation strategy for PineForge. +// +// Mirrors logic in validation/30-multi-indicator-score (RSI + EMA + BB score, same entries/exits) +// so you can export trades.csv from TradingView with identical inputs and compare bar-for-bar. +// +// PineForge note (2026): +// - Mutate var UDTs with per-field `:=` (`rsiL.contrib := ...`). Whole-struct reassignment +// (`rsiL := RsiLayer.new(...)`) mis-compiles. +// - Pass `input.int` / scalars into `ta.*` lengths — UDT fields (e.g. `cfg.rsi_len`) are not yet +// valid `simple` length arguments for the transpiler. +// - Avoid UDT methods, nested UDT fields, array, and UDT returns from user functions. +strategy("UDT Regime Stack (PF stress)", overlay=true, initial_capital=1000000, currency=currency.USD, process_orders_on_close=false, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0, slippage=0, default_qty_type=strategy.fixed, default_qty_value=1) + +// --- Enums (inputs + bookkeeping) --- +enum Aggression + conservative = "Conservative" + balanced = "Balanced" + aggressive = "Aggressive" + +// --- User-defined types (flat structs; no nested UDT fields) --- +type LayerInputs + int rsi_len = 14 + int ma_len = 20 + int bb_len = 20 + float bb_mult = 2.0 + +type RsiLayer + float value = na + int contrib = 0 + +type TrendLayer + float ema = na + int contrib = 0 + +type BbLayer + float mid = na + float upper = na + float lower = na + int contrib = 0 + +type ScoreSnapshot + int rsi_c = 0 + int trend_c = 0 + int bb_c = 0 + +type VolContext + float atr = na + float atr_ma = na + float ratio = 1.0 + +type OhlcBar + float o = na + float h = na + float l = na + float c = na + +type SessionScratch + bool in_session = true + int bar_streak = 0 + +type GateState + bool vol_ok = true + bool map_ok = true + +// --- Inputs --- +rsiLen = input.int(14, "RSI Length", minval=1) +maLen = input.int(20, "MA Length", minval=1) +bbLen = input.int(20, "BB Length", minval=5) +bbMult = input.float(2.0, "BB Mult", step=0.1) +profile = input.enum(Aggression.balanced, "Stress profile") + +// Frozen config in a UDT (inputs are stable; var initializes once) +var LayerInputs cfg = LayerInputs.new(rsiLen, maLen, bbLen, bbMult) + +// Map-backed thresholds (same numeric gates as the multi-indicator reference; profile kept for codegen stress) +var float thr_long = 2.0 +var float thr_short = -2.0 +var gateMap = map.new() +if barstate.isfirst + gateMap.put("long", 2.0) + gateMap.put("short", -2.0) + gateMap.put("exit_long", 0.0) + gateMap.put("exit_short", 0.0) + +switch profile + Aggression.conservative => + thr_long := nz(gateMap.get("long"), 2.0) + thr_short := nz(gateMap.get("short"), -2.0) + Aggression.balanced => + thr_long := nz(gateMap.get("long"), 2.0) + thr_short := nz(gateMap.get("short"), -2.0) + Aggression.aggressive => + thr_long := nz(gateMap.get("long"), 2.0) + thr_short := nz(gateMap.get("short"), -2.0) + => + thr_long := nz(gateMap.get("long"), 2.0) + thr_short := nz(gateMap.get("short"), -2.0) + +// --- Indicators (use input scalars for ta.* lengths; PineForge does not yet support UDT fields as `simple` lengths) --- +rsiVal = ta.rsi(close, rsiLen) +emaVal = ta.ema(close, maLen) +[bbMid, bbUpper, bbLower] = ta.bb(close, bbLen, bbMult) +atrVal = ta.atr(14) +atrMa = ta.sma(atrVal, 20) +atrRatio = atrMa > 0 ? atrVal / atrMa : 1.0 + +// Keep cfg UDT aligned with inputs (for inspection / parity with TradingView inputs) +cfg.rsi_len := rsiLen +cfg.ma_len := maLen +cfg.bb_len := bbLen +cfg.bb_mult := bbMult + +// --- Pack bar state into UDTs (mutate fields each bar; do not replace the whole struct) --- +var RsiLayer rsiL = RsiLayer.new(na, 0) +var TrendLayer trL = TrendLayer.new(na, 0) +var BbLayer bbL = BbLayer.new(na, na, na, 0) +var ScoreSnapshot snap = ScoreSnapshot.new(0, 0, 0) +var VolContext vol = VolContext.new(na, na, 1.0) +var OhlcBar curBar = OhlcBar.new(na, na, na, na) +var SessionScratch sess = SessionScratch.new(true, 0) +var GateState gates = GateState.new(true, true) + +rsiL.value := rsiVal +rsiL.contrib := rsiVal > 50 ? 1 : rsiVal < 50 ? -1 : 0 +trL.ema := emaVal +trL.contrib := close > emaVal ? 1 : -1 +bbL.mid := bbMid +bbL.upper := bbUpper +bbL.lower := bbLower +bbL.contrib := close > bbMid ? 1 : -1 +vol.atr := atrVal +vol.atr_ma := atrMa +vol.ratio := atrRatio +curBar.o := open +curBar.h := high +curBar.l := low +curBar.c := close + +// Soft volatility gate (always true here; exercises UDT fields without changing reference signals) +gates.vol_ok := vol.ratio < 25.0 +gates.map_ok := gateMap.contains("long") and gateMap.contains("short") + +score = rsiL.contrib + trL.contrib + bbL.contrib +snap.rsi_c := rsiL.contrib +snap.trend_c := trL.contrib +snap.bb_c := bbL.contrib + +// --- Session / streak scratch (deterministic; does not affect orders) --- +sess.in_session := true +// Use series `close` for history; UDT fields do not support [] in PineForge yet. +sess.bar_streak := bar_index > 0 and close == close[1] ? sess.bar_streak + 1 : 0 + +// --- Signals (match validation 30: threshold crossings on net score) --- +// `score[1]` is `na` on the first bar; do not treat `prevScore` as 0 or the first bar fires longs. +var int prevScore = na +longCond = gates.vol_ok and gates.map_ok and score >= thr_long and not na(prevScore) and prevScore < thr_long +shortCond = gates.vol_ok and gates.map_ok and score <= thr_short and not na(prevScore) and prevScore > thr_short + +if longCond + strategy.entry("Long", strategy.long) +if shortCond + strategy.entry("Short", strategy.short) + +exitL = nz(gateMap.get("exit_long"), 0.0) +exitS = nz(gateMap.get("exit_short"), 0.0) +if strategy.position_size > 0 and score <= exitL + strategy.close("Long") +if strategy.position_size < 0 and score >= exitS + strategy.close("Short") + +prevScore := score + +plot(emaVal, "EMA", color=color.blue) +plot(bbUpper, "BB Upper", color=color.gray) +plot(bbLower, "BB Lower", color=color.gray) +// plotchar `char` must be a const string (not `str.tostring` / series string) — CE10123 +plotchar(ta.crossover(score, thr_long) ? high : na, "Long cross", "▲", location=location.abovebar, color=color.teal, size=size.tiny) diff --git a/tests/gate-corpus/ok/validation__vwap-bands-breakout-1sigma-01.pine b/tests/gate-corpus/ok/validation__vwap-bands-breakout-1sigma-01.pine new file mode 100644 index 0000000..d540ae6 --- /dev/null +++ b/tests/gate-corpus/ok/validation__vwap-bands-breakout-1sigma-01.pine @@ -0,0 +1,21 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: ETHUSDT.P (Binance USDT-M Perpetual) TF: 15m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("VWAP Bands Breakout 1σ", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1, + initial_capital=100000, currency=currency.USD) + +// VWAP Bands (3-tuple form): daily anchor, 1-sigma bands. +// Entry: close breaks above upper_band (upside momentum beyond +1σ). +// Exit: close drops back below vwap (mean reversion signal). +[vw, upper_band, lower_band] = ta.vwap(close, timeframe.change("1D"), 1.0) + +long_condition = ta.crossover(close, upper_band) +exit_condition = ta.crossunder(close, vw) + +if long_condition + strategy.entry("long", strategy.long) +if exit_condition + strategy.close("long") diff --git a/tests/gate-corpus/ok/validation__vwap-bands-mean-reversion-2sigma-01.pine b/tests/gate-corpus/ok/validation__vwap-bands-mean-reversion-2sigma-01.pine new file mode 100644 index 0000000..f9ef7a7 --- /dev/null +++ b/tests/gate-corpus/ok/validation__vwap-bands-mean-reversion-2sigma-01.pine @@ -0,0 +1,23 @@ +// TV_RUN_NEEDED: tv_trades.csv missing — capture in TradingView: +// Symbol: ETHUSDT.P (Binance USDT-M Perpetual) TF: 15m Range: 2024-01-01..2024-06-01 +// Save List of Trades → tv_trades.csv in this directory. + +//@version=6 +strategy("VWAP Bands Mean Reversion 2σ", overlay=true, + default_qty_type=strategy.fixed, default_qty_value=1, + initial_capital=100000, currency=currency.USD) + +// VWAP Bands (3-tuple form): daily anchor, 2-sigma bands. +// Pine v6 ta.vwap(source, anchor, stdev_mult) returns [vwap, upper, lower]. +[vw, upper_band, lower_band] = ta.vwap(close, timeframe.change("1D"), 2.0) + +// Mean-reversion strategy: +// Entry: close crosses below lower_band (price below -2σ VWAP band) +// Exit: close crosses above vwap (price reverts to mean) +long_condition = ta.crossunder(close, lower_band) +exit_condition = ta.crossover(close, vw) + +if long_condition + strategy.entry("long", strategy.long) +if exit_condition + strategy.close("long") From 04407bfd1f6dde6a13223c4a31cfd652c2008cee Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:17:12 +0800 Subject: [PATCH 8/9] =?UTF-8?q?test(gate):=20canary=20selftest=20=E2=80=94?= =?UTF-8?q?=20comparator=20must=20flag=20divergences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- gate/selftest.mjs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 gate/selftest.mjs diff --git a/gate/selftest.mjs b/gate/selftest.mjs new file mode 100644 index 0000000..c2ff269 --- /dev/null +++ b/gate/selftest.mjs @@ -0,0 +1,29 @@ +// Canary: prove the gate's comparator actually catches a divergence. Imports the +// PURE comparator (gate/compare.mjs) so it runs in <1s without loading Pyodide. +import { compareResults } from "./compare.mjs"; + +const cases = [ + // [name, native, browser, mustFlag] + ["same-ok", { json: '{"ok":true,"cpp":"X"}' }, { json: '{"ok":true,"cpp":"X"}' }, false], + ["cpp-differs", { json: '{"ok":true,"cpp":"X"}' }, { json: '{"ok":true,"cpp":"Y"}' }, true], + ["verdict-differs", { json: '{"ok":true,"cpp":"X"}' }, { json: '{"ok":false,"error":"e","diagnostics":[]}' }, true], + ["error-differs", { json: '{"ok":false,"error":"a","diagnostics":[]}' }, { json: '{"ok":false,"error":"b","diagnostics":[]}' }, true], + ["unexpected-one-side", { json: '{"ok":true,"cpp":"X"}' }, { unexpected: "TypeError: boom" }, true], + ["unexpected-both-same", { unexpected: "TypeError: boom" }, { unexpected: "TypeError: boom" }, false], + ["unexpected-both-diff", { unexpected: "TypeError: a" }, { unexpected: "ValueError: b" }, true], + ["missing-native", undefined, { json: '{"ok":true,"cpp":"X"}' }, true], +]; + +let failed = 0; +for (const [name, n, b, mustFlag] of cases) { + const flagged = compareResults(name, n, b) !== null; + if (flagged !== mustFlag) { + console.error(`selftest FAIL: ${name} expected mustFlag=${mustFlag} got ${flagged}`); + failed++; + } +} +if (failed) { + console.error(`gate selftest: ${failed} case(s) failed`); + process.exit(1); +} +console.log(`gate selftest: ${cases.length} comparator cases OK`); From e4165355c0e5452835d27f707a6de88d36b33ee0 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 16 Jun 2026 08:18:06 +0800 Subject: [PATCH 9/9] ci(gate): run the Pyodide differential gate (dispatch + nightly + gate-file PRs) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/gate.yml | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/gate.yml diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml new file mode 100644 index 0000000..49fe1a8 --- /dev/null +++ b/.github/workflows/gate.yml @@ -0,0 +1,60 @@ +name: Pyodide Gate + +# Differential parity gate: the transpiler in real wasm32 Pyodide must match +# native CPython on every corpus input (success + failure). Heavier than the +# unit matrix (loads Pyodide), so: on demand, nightly, and on PRs that touch the +# gate itself. It does NOT block releases yet — that wiring is Phase 3. +on: + workflow_dispatch: + schedule: + - cron: "27 5 * * *" # nightly 05:27 UTC + pull_request: + paths: + - "gate/**" + - "tests/gate-corpus/**" + - "package.json" + - "package-lock.json" + - ".github/workflows/gate.yml" + - "pineforge_codegen/**" + +permissions: + contents: read + +concurrency: + group: gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + gate: + runs-on: ubuntu-latest + env: + PYTHONHASHSEED: "0" + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.14" + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: "22" + + - name: Install codegen (native oracle) + node deps + run: | + python -m pip install -e . + npm ci + + - name: Selftest (comparator catches divergences) + run: npm run gate:selftest + + - name: Run differential gate (full corpus) + run: npm run gate:full + + - name: Upload release.json (derived versions, for Phase 3 handoff) + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: release-json + path: release.json + if-no-files-found: ignore