SPB Git forge

spb/wp15_uqo

Public MIT
3commits 1branches 1releases
7.6 MBsize
maindefault branch
53 min agolast push
TeX 53.6% Python 46.2%
3.8 KB · 101 lines python
Raw Blame History
1"""Stage D.1 — refine split candidates into high-confidence events + exclusion windows.23Stage C flagged 442k ticker-days with overnight ratios near rational split ratios4(over-inclusive). Here we keep events where:5  * the overnight ratio prev_close/first_open is within TOL of a canonical ratio6    r in {2,3,4,5,6,7,8,10,20,3/2} (forward) or 1/r (reverse); tighter TOL for 3/2;7  * both adjacent days have RTH dollar volume >= MIN_DV (kills dead-OTC noise);8  * prev_close >= MIN_PRICE.910Outputs:11  outputs/diagnostics/stage_d/split_events.parquet   (ticker, date, ratio, kind)12  outputs/diagnostics/stage_d/split_windows.parquet  (ticker, date) — event day ± WINDOW days13"""1415from __future__ import annotations1617from pathlib import Path1819import polars as pl2021ROOT = Path(__file__).resolve().parents[1]22DAILY = ROOT / "data" / "metadata" / "daily"23OUT = ROOT / "outputs" / "diagnostics" / "stage_d"2425CANONICAL = [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 20.0]26TOL, TOL_32 = 0.015, 0.00827MIN_DV = 50_000.028MIN_PRICE = 1.029WINDOW = 1  # exclusion: event day +/- 1 trading day303132def main() -> None:33    OUT.mkdir(parents=True, exist_ok=True)34    d = (35        pl.scan_parquet(str(DAILY / "daily_*.parquet"))36        .filter(pl.col("rth_last_close").is_not_null() & (pl.col("rth_volume") > 0))37        .select("ticker", "date", "rth_first_open", "rth_last_close", "rth_dollar_vol")38        .sort("ticker", "date")39        .with_columns(40            pl.col("rth_last_close").shift(1).over("ticker").alias("prev_close"),41            pl.col("rth_dollar_vol").shift(1).over("ticker").alias("prev_dv"),42            pl.col("date").shift(1).over("ticker").alias("prev_date"),43        )44        .filter(45            pl.col("prev_close").is_not_null()46            & ((pl.col("date") - pl.col("prev_date")).dt.total_days() <= 7)47            & (pl.col("prev_dv") >= MIN_DV)48            & (pl.col("rth_dollar_vol") >= MIN_DV)49            & (pl.col("prev_close") >= MIN_PRICE)50        )51        .with_columns((pl.col("prev_close") / pl.col("rth_first_open"))52                      .alias("overnight_ratio"))53    )5455    cond = None56    exprs = []57    for r in CANONICAL + [1.5]:58        tol = TOL_32 if r == 1.5 else TOL59        for ratio, kind in [(r, "split"), (1.0 / r, "reverse_split")]:60            c = (pl.col("overnight_ratio") / ratio - 1).abs() < tol61            cond = c if cond is None else (cond | c)62            exprs.append(63                pl.when((pl.col("overnight_ratio") / ratio - 1).abs() < tol)64                .then(pl.lit(f"{kind}:{r:g}")).otherwise(None))6566    events = (67        d.filter(cond)68        .with_columns(pl.coalesce(exprs).alias("event"))69        .select("ticker", "date", "overnight_ratio", "event",70                "prev_close", "rth_first_open")71        .collect()72    )73    events.write_parquet(OUT / "split_events.parquet")7475    # exclusion windows: event date +/- WINDOW *observed* trading days per ticker76    dates = (77        pl.scan_parquet(str(DAILY / "daily_*.parquet"))78        .select("ticker", "date").unique().sort("ticker", "date")79        .with_columns(pl.int_range(pl.len()).over("ticker").alias("idx"))80        .collect()81    )82    ev = events.join(dates, on=["ticker", "date"], how="left")83    win = []84    for off in range(-WINDOW, WINDOW + 1):85        win.append(ev.select("ticker", (pl.col("idx") + off).alias("idx")))86    windows = (87        pl.concat(win).unique()88        .join(dates, on=["ticker", "idx"], how="inner")89        .select("ticker", "date").unique().sort("ticker", "date")90    )91    windows.write_parquet(OUT / "split_windows.parquet")9293    print(f"events={events.height} "94          f"(splits={(events['event'].str.starts_with('split')).sum()}, "95          f"reverse={(events['event'].str.starts_with('reverse')).sum()}) "96          f"window_days={windows.height}")979899if __name__ == "__main__":100    main()101