Files

71 lines
2.5 KiB
Python
Raw Permalink Normal View History

"""Generic golden replay for the alt-chart bar builders and the footprint.
Each builder turns one candle into 0..n completed bars, so the fixture stores
one CSV line per input candle holding every bar flattened (an empty line means
no bar closed on that candle). Close-driven builders (Renko, Kagi, P&F, Range,
Three-Line-Break) receive a flat candle, mirroring the binding's `update(close)`.
Values are checked bit-for-bit against the Rust-generated `g_<Canonical>.csv`.
"""
import json
import math
import os
import pytest
import wickra as ta
HERE = os.path.dirname(__file__)
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
def _cell(s):
return math.nan if s == "nan" else float(s)
def _bar_rows(name):
# Keep blank lines: one row per input candle, blank == no bar closed.
with open(os.path.join(GOLDEN, name + ".csv")) as f:
lines = f.read().splitlines()[1:]
return [[] if not ln.strip() else [_cell(x) for x in ln.split(",")] for ln in lines]
def _input():
with open(os.path.join(GOLDEN, "input.csv")) as f:
return [[float(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
with open(os.path.join(GOLDEN, "bars_manifest.json")) as _mf:
MANIFEST = json.load(_mf)
ROWS = _input()
def _flatten(bars):
out = []
for bar in bars:
out.extend(float(x) for x in bar)
return out
@pytest.mark.parametrize("spec", MANIFEST, ids=[m["canonical"] for m in MANIFEST])
def test_bars_match_golden(spec):
ind = getattr(ta, spec["native"])(*spec["params"])
expected = _bar_rows("g_" + spec["canonical"])
feed = spec["feed"]
assert len(expected) == len(ROWS), f"{spec['canonical']}: {len(expected)} rows vs {len(ROWS)} inputs"
for i, (o, h, l, c, v) in enumerate(ROWS):
if feed == "close":
produced = ind.update(c)
elif feed == "candle4":
produced = ind.update(o, h, l, c)
elif feed == "candle5":
produced = ind.update(o, h, l, c, v)
else: # trade footprint
produced = ind.update(c, v, c >= o)
got = _flatten(produced)
want = expected[i]
assert len(got) == len(want), f"{spec['canonical']} row {i}: arity {len(got)} vs {len(want)}"
for gv, w in zip(got, want):
if math.isnan(w):
assert math.isnan(gv), f"{spec['canonical']} row {i}: want NaN got {gv}"
else:
assert abs(gv - w) <= 1e-6 * max(1.0, abs(w)), f"{spec['canonical']} row {i}: got {gv} want {w}"