"""Burst-candle frequency by UTC hour — the empirical "hedge fund hours". Usage: python backtest/research/burst_profile.py PARQUET_GLOB Per UTC bar-close hour of the given bars: % of bars whose range is >= K x the PRIOR bar's Wilder ATR(14), for K in {3,4,5}; mean bar range; and mean signed drift over the 6 bars following a >=4x burst (does price continue or snap back?). Analysis tooling only. """ import glob import sys import pandas as pd def burst_table(parquet_glob: str, bar_minutes: int = 5): files = sorted(glob.glob(parquet_glob)) if not files: raise SystemExit(f"no parquet matches {parquet_glob}") df = pd.concat([pd.read_parquet(f) for f in files]).sort_index() df = df[~df.index.duplicated(keep="last")] h, l, c = df["High"], df["Low"], df["Close"] tr = pd.concat([h - l, (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) atr = tr.ewm(alpha=1 / 14, min_periods=14, adjust=False).mean() ratio = (h - l) / atr.shift(1) close_hour = (df.index + pd.Timedelta(minutes=bar_minutes)).hour fwd6 = c.shift(-6) - c # signed move over the 6 bars after this one out = pd.DataFrame({ "bars": ratio.groupby(close_hour).size(), **{f"pct_ge_{k}x": (ratio >= k).groupby(close_hour).mean() * 100 for k in (3, 4, 5)}, "mean_range": (h - l).groupby(close_hour).mean(), "post4x_drift6": fwd6[ratio >= 4].groupby(close_hour[ratio >= 4]).mean(), }) return out.round(3) if __name__ == "__main__": print(burst_table(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 5).to_string())