| # SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
|
| """Compare what the indicator drew (DumpIndicator CSVs on MT5, Dump4 CSVs on MT4) with tests/reference.py, bar by bar.
|
|
|
| python3 tests/check_parity.py <dump dir> (needs numpy and pandas; looks in <dump dir>/dump)
|
| python3 tests/check_parity.py <dump dir> --mt4 (the MT4 edition, dumps from the MT4 tester)
|
|
|
| Buffers: b0 new-uptrend arrow, b1 new-downtrend arrow, b2 HiLo line, b3 its colour (0 up, 1 down), b4 swing line
|
| (price on each confirmed pivot bar), b5 side, b6 activator trend. The indicator scans InpHistory closed bars.
|
| Pass: line within 1e-9 relative, trend, side, arrows and swing pivots identical on every scanned bar."""
|
| import sys
|
| from pathlib import Path
|
|
|
| import numpy as np
|
| import pandas as pd
|
|
|
| sys.path.insert(0, str(Path(__file__).parent))
|
| from reference import run # noqa: E402
|
|
|
| CONFIGS = {"gn_default": (3, True, 3000), "gn_hilo5_noswing": (5, False, 3000)}
|
| # DumpIndicator InpParams for MT5 in input order (each input group counts as a string parameter); as tests/configs.tsv
|
| MT5_PARAMS = {"gn_default": "s:Gann HiLo and swing|i:3|b:true|b:true|s:Display|i:3000|c:16748574|c:4678655|s:Alerts|b:false|b:false|s:alert.wav|b:false|b:false",
|
| "gn_hilo5_noswing": "s:Gann HiLo and swing|i:5|b:false|b:true|s:Display|i:3000|c:16748574|c:4678655|s:Alerts|b:false|b:false|s:alert.wav|b:false|b:false"}
|
|
|
|
|
| def num(col):
|
| return pd.to_numeric(col, errors="coerce").to_numpy(float)
|
|
|
|
|
| def check(csv, hilo, swing, hist, mt4=False):
|
| d = pd.read_csv(csv)
|
| n = len(d)
|
| # MT4 tester: the indicator was attached with far fewer than InpHistory bars, so it starts at bar 0 and keeps
|
| # feeding (arrows are compared by bar only, as before: MT4 puts them a multiple of ATR from the candle)
|
| start = 0 if mt4 else max(0, n - 1 - hist)
|
| h, l, c = (d[k].to_numpy(float)[start:n - 1] for k in ("high", "low", "close"))
|
| rows, piv = run(h, l, c, hilo, swing)
|
| m = len(rows)
|
| ref_line = np.array([np.nan if r["line"] is None else r["line"] for r in rows])
|
| ok_line = np.isclose(num(d["b2"])[start:start + m], ref_line, rtol=1e-9, atol=0, equal_nan=True)
|
| ok_side = num(d["b5"])[start:start + m] == np.array([r["side"] for r in rows])
|
| ok_trend = num(d["b6"])[start:start + m] == np.array([r["trend"] for r in rows])
|
| up = sorted(i for i in np.flatnonzero(~np.isnan(num(d["b0"])[start:start + m])))
|
| dn = sorted(i for i in np.flatnonzero(~np.isnan(num(d["b1"])[start:start + m])))
|
| rup = [k for k, r in enumerate(rows) if r["ev"] > 0]
|
| rdn = [k for k, r in enumerate(rows) if r["ev"] < 0]
|
| sw = num(d["b4"])[start:start + m]
|
| mt_piv = {int(i): sw[i] for i in np.flatnonzero(~np.isnan(sw))}
|
| ref_piv = {pb: px for _, pb, px, _ in piv}
|
| ok_piv = mt_piv.keys() == ref_piv.keys() and all(abs(mt_piv[k] - ref_piv[k]) <= 1e-9 * abs(ref_piv[k]) for k in ref_piv)
|
| ok = ok_line.all() and ok_side.all() and ok_trend.all() and up == rup and dn == rdn and ok_piv
|
| msg = (f"{csv.stem:34} bars {m:>5} line {'OK' if ok_line.all() else (~ok_line).sum()} side {'OK' if ok_side.all() else (~ok_side).sum()}"
|
| f" trend {'OK' if ok_trend.all() else (~ok_trend).sum()} up {len(rup):>3} {'OK' if up == rup else 'DIFFER'}"
|
| f" down {len(rdn):>3} {'OK' if dn == rdn else 'DIFFER'} pivots {len(ref_piv):>4} {'OK' if ok_piv else 'DIFFER'}")
|
| return ok, msg
|
|
|
|
|
| if __name__ == "__main__":
|
| mt4 = "--mt4" in sys.argv # dumps of the MT4 Strategy Tester run
|
| root = Path([a for a in sys.argv[1:] if a != "--mt4"][0])
|
| allok, k = True, 0
|
| for tag, (hilo, swing, hist) in CONFIGS.items():
|
| for f in sorted(root.glob(f"dump/{tag}_*.csv")):
|
| ok, msg = check(f, hilo, swing, hist, mt4)
|
| allok &= ok
|
| k += 1
|
| print(("PASS " if ok else "FAIL ") + msg)
|
| print(f"\n{'ALL PASS' if allok and k else 'FAILURES'} ({k} files)")
|
| sys.exit(0 if allok and k else 1)
|