Capture true daily OHLC ranges end-to-end through the binance/crypto data pipeline so the warehouse `features` table stores real high/low instead of the degenerate high=low=close. This unlocks real Corwin-Schultz measured spreads for the binance book. - crypto_fetch._klines: extract HIGH (kline idx2) and LOW (idx3) per row; return (day, close, high, low, qvol). fetch_crypto_pit writes high/low into the npz (keys: day, close, high, low, qvol, funding). - normalize_crypto: new optional (high, low) args; uses real H/L when given, falls back to high=low=close otherwise (backward-compat for old npz/callers). - Readers (ingest_crypto_pit + ingest-warehouse CLI crypto branch): read high/low from npz when present, else fall back to close — old npz without high/low still ingest, no crash. - close (and thus combined-book returns) unchanged; only high/low are added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
2.1 KiB
Python
42 lines
2.1 KiB
Python
"""Silver normalizers: raw bronze arrays -> canonical BarRecords (epoch-second ts)."""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
|
|
def test_normalize_futures_builds_adjusted_bars_with_epoch_second_ts() -> None:
|
|
from fxhnt.adapters.warehouse.silver_futures import normalize_futures
|
|
days = np.array([0, 1, 2], dtype=np.int64) # epoch-days
|
|
inst = np.array([10, 10, 20], dtype=np.int64) # roll on day 2
|
|
close = np.array([100.0, 102.0, 50.0])
|
|
high = np.array([101.0, 103.0, 51.0])
|
|
low = np.array([99.0, 101.0, 49.0])
|
|
bars = normalize_futures("ES", days, inst, close, high, low)
|
|
assert [b.ts for b in bars] == [0, 86_400, 172_800] # day * 86400
|
|
assert bars[2].close == bars[1].close # roll -> flat synthetic
|
|
assert bars[2].high / bars[2].close == 51.0 / 50.0 # range preserved
|
|
assert all(b.symbol == "ES" for b in bars)
|
|
|
|
|
|
def test_normalize_crypto_falls_back_to_close_only_bars() -> None:
|
|
"""Backward-compat: old npz / callers pass close only -> high == low == close (flat bar)."""
|
|
from fxhnt.adapters.warehouse.silver_crypto import normalize_crypto
|
|
day = np.array([19_000, 19_001], dtype=np.int64) # epoch-days
|
|
close = np.array([42000.0, 43000.0])
|
|
bars = normalize_crypto("BTCUSDT", day, close)
|
|
assert [b.ts for b in bars] == [19_000 * 86_400, 19_001 * 86_400]
|
|
assert bars[0].high == bars[0].low == bars[0].close == 42000.0
|
|
assert all(b.symbol == "BTCUSDT" for b in bars)
|
|
|
|
|
|
def test_normalize_crypto_uses_real_high_low_when_provided() -> None:
|
|
"""When high/low are supplied, BarRecord carries the true OHLC range."""
|
|
from fxhnt.adapters.warehouse.silver_crypto import normalize_crypto
|
|
day = np.array([19_000, 19_001], dtype=np.int64)
|
|
close = np.array([42000.0, 43000.0])
|
|
high = np.array([42500.0, 43800.0])
|
|
low = np.array([41200.0, 42100.0])
|
|
bars = normalize_crypto("BTCUSDT", day, close, high, low)
|
|
assert bars[0].close == 42000.0 and bars[0].high == 42500.0 and bars[0].low == 41200.0
|
|
assert bars[1].close == 43000.0 and bars[1].high == 43800.0 and bars[1].low == 42100.0
|