1import datetime as dt2import math34import numpy as np5import polars as pl67from wp39.features import aggregate_buckets, bucket_returns, session_features8910def _reg_day(n=391, seed=0):11 rng = np.random.default_rng(seed)12 r = rng.normal(0, 1e-3, n)13 px = 100 * np.exp(np.cumsum(r))14 return pl.DataFrame({"session_date": [dt.date(2024, 3, 5)] * n, "m": list(range(n)), "open": np.r_[100.0, px[:-1]],15 "high": px * 1.0005, "low": px * 0.9995, "close": px, "volume": rng.integers(100, 1000, n).astype(float),16 "close_minute": [390] * n})171819def test_bucket_aggregation_counts_and_close_print():20 reg = _reg_day()21 b5 = aggregate_buckets(reg, 5); b30 = aggregate_buckets(reg, 30)22 assert b5.height == 78 and b30.height == 1323 assert b5["close"][-1] == reg["close"][-1] and b30["close"][-1] == reg["close"][-1]24 assert abs(b5["volume"].sum() - reg["volume"].sum()) < 1e-9252627def test_rv_identities_and_shares():28 reg = _reg_day()29 for f in (1, 5, 30):30 out = session_features(aggregate_buckets(reg, f), f, None)31 r = out.row(0, named=True); p = f"f{f}m_"32 assert 0 <= r[p + "rsneg_share"] <= 1 and 0 <= r[p + "rj"] <= 1 and 0 < r[p + "present_share"] <= 1.0 + 1e-933 assert 0 <= r[p + "vol_first30"] <= 1 and 0 <= r[p + "down_vol_share"] <= 1 and 0 <= r[p + "vol_gini"] <= 134 o1 = session_features(aggregate_buckets(reg, 1), 1, None).row(0, named=True)35 rr = bucket_returns(aggregate_buckets(reg, 1))["r"].to_numpy()36 assert abs(o1["f1m_rv"] - np.sum(rr ** 2)) < 1e-1237 assert abs(o1["f1m_drift"] - math.log(reg["close"][-1] / reg["open"][0])) < 1e-1238