# SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) """Compare what MT5's Displacement Order Blocks wrote (DumpIndicator CSVs, buffers b0..b12) with the Python reference, bar by bar. python3 tests/check_parity.py [--mt4] (needs numpy and pandas) Each CONFIG is one DumpIndicator run (tag -> reference kwargs, the same overrides as tests/configs.tsv). Pass: on every closed bar every buffer is equal (prices within 1e-9 relative, counts exactly, empty where the reference has nothing).""" 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 KEYS, dz_zones # noqa: E402 CONFIGS = { "dz_default": dict(), "dz_body_mid_wick": dict(swing=5, zone_mode=1, test_mode=1, break_mode=1, max_live=0), "dz_mixed_far": dict(swing=2, atr=10, disp_mult=1.5, body_pct=60.0, search=5, zone_mode=2, test_mode=2, max_live=3), "dz_move_base": dict(base_mode=1, zone_mode=1, test_mode=1, max_live=0), } def check(csv: Path, kw: dict, mt4=False) -> tuple[bool, str]: d = pd.read_csv(csv) if mt4: # Dump4 writes the bar time as text (server time, as MT5's epoch seconds) d["time"] = (pd.to_datetime(d["time"], format="%Y.%m.%d %H:%M") - pd.Timestamp(0)) // pd.Timedelta(seconds=1) t, o, h, l, c = (d[k].to_numpy(float) for k in ("time", "open", "high", "low", "close")) ref, zones, breaks, _, _ = dz_zones(t, o, h, l, c, **kw) closed = slice(0, len(d) - 1) bad, badcols = 0, [] for j, k in enumerate(KEYS): mt, rf = d[f"b{j}"].to_numpy(float)[closed], ref[k][closed] same = np.isclose(mt, rf, rtol=1e-9, atol=0, equal_nan=True) if not same.all(): bad += int((~same).sum()) first = int(np.argmax(~same)) badcols.append(f"{k}:{int((~same).sum())}(first bar {first}: mt5 {mt[first]} ref {rf[first]})") states = [z["state"] for z in zones] ok = bad == 0 return ok, (f"{csv.stem:30} bars {len(d):>6} zones {len(zones):>5} (tested {sum(z['retests'] > 0 for z in zones):>5}, " f"broken {states.count(2):>5}) breaks {len(breaks):>5} " f"{'all 13 buffers equal' if ok else 'DIFFER ' + ' '.join(badcols)}") if __name__ == "__main__": mt4 = "--mt4" in sys.argv # MT4 dumps from Dump4.mqh root = Path([a for a in sys.argv[1:] if a != "--mt4"][0]) allok, n = True, 0 for tag, kw in CONFIGS.items(): for f in sorted(root.glob(f"{tag}_*.csv")): ok, msg = check(f, kw, mt4) allok &= ok n += 1 print(("PASS " if ok else "FAIL ") + msg) print(f"\n{'ALL PASS' if allok and n else 'FAILURES'} ({n} files)") sys.exit(0 if allok and n else 1)