forked from animatedread/Warrior_EA
91 lines
4.8 KiB
Python
91 lines
4.8 KiB
Python
|
|
r"""Runs the SQX bar decoder (research/sqxbars.py, which wraps research/sqx.py) on a COPY
|
||
|
|
of one SQX history file, so the operator has real decoded sample data to point a manual
|
||
|
|
backtest/tester run at without touching the SQX install itself.
|
||
|
|
|
||
|
|
WHY A COPY, AND WHY THIS SCRIPT DOESN'T EDIT sqxbars.py:
|
||
|
|
Tests\sample_data\SQX_History\SP500_the5ers\SP500_the5ers_H1.dat is a copy of
|
||
|
|
SQX_144_2953_win_20260601\user\data\History\SP500_the5ers\SP500_the5ers_H1.dat - the
|
||
|
|
original was never opened for writing, only read by `cp`. sqxbars.py hardcodes its own
|
||
|
|
HIST/CACHE module constants (both point at locations OUTSIDE this repo, one of them inside
|
||
|
|
the live SQX install), so rather than edit those constants in place or call sqxbars.load()
|
||
|
|
(which reads through HIST/CACHE), this script calls sqxbars.decode() directly with an
|
||
|
|
explicit path built from the COPY - bypassing HIST/CACHE entirely, so nothing in sqxbars.py
|
||
|
|
needs to change and the production research scripts (sqx_audit.py, sqx_portfolio.py,
|
||
|
|
breadth_seasonal.py, ...) are untouched and keep reading the real install.
|
||
|
|
|
||
|
|
WHY THIS DOES NOT PRODUCE SCALED PRICES:
|
||
|
|
sqxbars.load() requires either an explicit `decimals=` or a `ref=(times, closes)` validated
|
||
|
|
reference series to CALIBRATE the decimal scale - see sqxbars.calibrate()'s own docstring
|
||
|
|
and sqx.py's calibrate_decimals(): the scale is not recoverable from the file alone, and
|
||
|
|
guessing it is exactly the "silent wrong scale" trap this project's own notes warn about
|
||
|
|
twice already (project_sqx_tick_format.md, project_sqx_bar_decoder.md). This script has no
|
||
|
|
validated reference series available (fills.Book() pulls from this machine's own live
|
||
|
|
fill-engine data, out of scope for a self-contained sample), so it calls sqxbars.decode()
|
||
|
|
directly - the raw, un-scaled int64 decode - which proves the parser reads the copied file
|
||
|
|
correctly (record count, monotonic time, structural sanity) without fabricating a price
|
||
|
|
scale. The saved .npz keeps the raw integer columns; see the printed note for how the
|
||
|
|
operator can calibrate them for real use.
|
||
|
|
|
||
|
|
Usage: .venv\Scripts\python.exe Tests\convert_sample_data.py
|
||
|
|
"""
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import datetime as dt
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
|
|
SAMPLE_HIST = os.path.join(REPO_ROOT, "Tests", "sample_data", "SQX_History") + os.sep
|
||
|
|
SAMPLE_OUT = os.path.join(REPO_ROOT, "Tests", "sample_data", "converted") + os.sep
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.join(REPO_ROOT, "research"))
|
||
|
|
import sqxbars # noqa: E402 (path must be extended first)
|
||
|
|
|
||
|
|
SYMBOL = "SP500_the5ers"
|
||
|
|
TIMEFRAME = "H1"
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
os.makedirs(SAMPLE_OUT, exist_ok=True)
|
||
|
|
src = os.path.join(SAMPLE_HIST, SYMBOL, f"{SYMBOL}_{TIMEFRAME}.dat")
|
||
|
|
if not os.path.exists(src):
|
||
|
|
raise FileNotFoundError(
|
||
|
|
f"{src} - expected the copied sample .dat here; see this file's docstring.")
|
||
|
|
|
||
|
|
print(f"=== SQX SAMPLE CONVERSION: {SYMBOL} {TIMEFRAME} ===")
|
||
|
|
print(f"source (a COPY, not the SQX install): {src}")
|
||
|
|
rows, nf = sqxbars.decode(src)
|
||
|
|
print(f"decoded {len(rows):,} bar records, {nf} fields per record")
|
||
|
|
if len(rows) == 0:
|
||
|
|
raise ValueError("decoder produced zero records - the copy may be truncated or corrupt")
|
||
|
|
|
||
|
|
t_ms = rows[:, 0]
|
||
|
|
monotonic = bool((np.diff(t_ms) >= 0).all())
|
||
|
|
t0 = dt.datetime.fromtimestamp(t_ms[0] / 1000, dt.UTC)
|
||
|
|
t1 = dt.datetime.fromtimestamp(t_ms[-1] / 1000, dt.UTC)
|
||
|
|
print(f"time span: {t0:%Y-%m-%d %H:%M} .. {t1:%Y-%m-%d %H:%M} UTC")
|
||
|
|
print(f"time monotonic: {monotonic}")
|
||
|
|
|
||
|
|
#--- Structural sanity independent of any price scale, per sqxbars.load()'s own check:
|
||
|
|
#--- a high below its low means the FIELD ORDER is wrong, which no rescaling could reveal.
|
||
|
|
raw_open, raw_high, raw_low, raw_close = rows[:, 1], rows[:, 2], rows[:, 3], rows[:, 4]
|
||
|
|
bad_hilo = int((raw_high < raw_low).sum())
|
||
|
|
print(f"raw high<low violations: {bad_hilo} of {len(rows):,} "
|
||
|
|
f"({'OK' if bad_hilo == 0 else 'SUSPECT FIELD ORDER'})")
|
||
|
|
print(f"raw close range: [{raw_close.min():,} .. {raw_close.max():,}] "
|
||
|
|
f"(UNSCALED integer units - see this file's docstring for why)")
|
||
|
|
|
||
|
|
out_path = os.path.join(SAMPLE_OUT, f"{SYMBOL}_{TIMEFRAME}_raw.npz")
|
||
|
|
np.savez_compressed(out_path, rows=rows,
|
||
|
|
columns=np.array(sqxbars.COLS))
|
||
|
|
print(f"\nsaved raw decoded sample -> {out_path}")
|
||
|
|
print("NOTE: prices in this file are RAW UNSCALED INTEGERS, not calibrated to a decimal")
|
||
|
|
print("point. To get real prices, calibrate against a trusted reference series the way")
|
||
|
|
print("sqxbars.calibrate()/load() do - e.g. sqxbars.load(sym, tf, ref=(times_ms, closes))")
|
||
|
|
print("with a validated MT5 rate export for the same instrument/timeframe.")
|
||
|
|
print(f"\n{SYMBOL} {TIMEFRAME}: {len(rows):,} bars decoded, monotonic={monotonic}, "
|
||
|
|
f"high<low violations={bad_hilo} -> " +
|
||
|
|
("PASS" if monotonic and bad_hilo == 0 else "FAIL"))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|