"""
phase_buyvol.py -- [2026-09-28] Buy volume together with price speed, phase by phase.

Each day is split into phases with a zigzag: a new phase starts when price turns
by >= TURN_PCT from the phase's extreme (up-leg / down-leg); a leg that moved less
than FLAT_PCT overall is labeled a flat base. Per phase: minutes, % change, speed
(%/min and vs the stock's 14-day normal move per minute at that time), smoothness
(R^2), volume vs the 14-day normal, and TRUE buy/sell volume from the tick data
(each trade labeled against the latest quote: at/above ask = buy, at/below bid =
sell, else vs midpoint; no fresh quote -> tick rule), buy share, net buying
(buy - sell) and buy volume per minute.

    python3 phase_buyvol.py 2026-09-28 AGEN TGB IMMX NWL SG CLRO
"""
import json
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

sys.path.insert(0, "/var/www/screener/trade")
sys.path.insert(0, "/var/www/screener/trade/reports/agen")
import day_report
import reference as REF

ET = ZoneInfo("America/New_York")
CACHE = "/var/www/screener/trade/data/simulations/cache"
TURN_PCT = 1.0
FLAT_PCT = 1.0


def minute_flow(day, sym):
    """{minute_index: [buy_vol, sell_vol, other_vol]} from the tick cache, regular session."""
    flow = defaultdict(lambda: [0.0, 0.0, 0.0])
    q = None
    last_p, last_side = None, 0
    for line in open(f"{CACHE}/{day}/{sym}.jsonl"):
        ts, kind, a, b = json.loads(line)
        t = datetime.fromisoformat(ts)
        if kind == "quote":
            q = (t, a, b)
            continue
        p, size = a, b
        side = 0
        if q and (t - q[0]).total_seconds() <= 2 and q[1] > 0 and q[2] >= q[1]:
            bid, ask = q[1], q[2]
            side = 1 if p >= ask else -1 if p <= bid else (1 if p > (bid + ask) / 2 else -1 if p < (bid + ask) / 2 else 0)
        if side == 0 and last_p is not None:
            side = 1 if p > last_p else -1 if p < last_p else last_side
        last_p, last_side = p, side
        te = t.astimezone(ET)
        m = (te.hour - 9) * 60 + te.minute - 30
        if 0 <= m < 390:
            flow[m][0 if side > 0 else 1 if side < 0 else 2] += size
    return flow


def phases(c):
    """Zigzag legs [(start_i, end_i)] on closes."""
    legs, start, ext_i, direction = [], 0, 0, 0
    for i in range(1, len(c)):
        if direction >= 0 and c[i] >= c[ext_i]:
            ext_i, direction = i, direction if direction else (1 if c[i] > c[start] else 0)
        if direction <= 0 and c[i] <= c[ext_i]:
            ext_i, direction = i, direction if direction else (-1 if c[i] < c[start] else 0)
        if direction == 1 and c[i] <= c[ext_i] * (1 - TURN_PCT / 100):
            legs.append((start, ext_i)); start, ext_i, direction = ext_i, i, -1
        elif direction == -1 and c[i] >= c[ext_i] * (1 + TURN_PCT / 100):
            legs.append((start, ext_i)); start, ext_i, direction = ext_i, i, 1
    legs.append((start, len(c) - 1))
    return [(a, b) for a, b in legs if b > a]


def r2(cs):
    n = len(cs)
    if n < 3:
        return 0
    xm, ym = (n - 1) / 2, sum(cs) / n
    sxx = sum((k - xm) ** 2 for k in range(n))
    slope = sum((k - xm) * (y - ym) for k, y in enumerate(cs)) / sxx
    sst = sum((y - ym) ** 2 for y in cs)
    ssr = sum((y - (ym + slope * (k - xm))) ** 2 for k, y in enumerate(cs))
    return (1 - ssr / sst if sst else 0) * (1 if slope > 0 else -1)


def run(day, sym):
    ref = REF.load(day, sym)
    B = [r for r in day_report.load_minutes(sym, day) if r["session"] == "reg"][:390]
    t = [r["time_et"] for r in B]
    idx = [(int(x[:2]) - 9) * 60 + int(x[3:]) - 30 for x in t]
    c = [float(r["close"]) for r in B]
    v = [float(r["volume"]) for r in B]
    vw = [float(r["vwap_regular"]) for r in B]
    flow = minute_flow(day, sym)
    tot_b = sum(f[0] for f in flow.values())
    tot_s = sum(f[1] for f in flow.values())
    day_buy_per_min = tot_b / 390
    out = []
    for a, b in phases(c):
        seg = range(a + 1, b + 1)
        mins = b - a
        chg = (c[b] / c[a] - 1) * 100
        kind = "UP" if chg >= FLAT_PCT else "DOWN" if chg <= -FLAT_PCT else "FLAT"
        norm_move = sum(ref["abs_move_pct"][min(idx[i], 389)] - ref["abs_move_pct"][min(idx[i] - 1, 389)]
                        for i in seg if idx[i] > 0)
        norm_vol = sum(ref["vol_per_min"][min(idx[i], 389)] for i in seg)
        bv = sum(flow[idx[i]][0] for i in seg)
        sv = sum(flow[idx[i]][1] for i in seg)
        out.append({"from": t[a], "to": t[b], "kind": kind, "min": mins, "chg": chg,
                    "speed": chg / mins, "r2": r2(c[a:b + 1]),
                    "vol_x": sum(v[i] for i in seg) / norm_vol if norm_vol else None,
                    "buy_share": bv / (bv + sv) * 100 if bv + sv else None,
                    "net_buy": bv - sv, "buy_per_min": bv / mins,
                    "buy_rate_x": (bv / mins) / day_buy_per_min if day_buy_per_min else None,
                    "vwap_end": (c[b] / vw[b] - 1) * 100, "p_end": c[b]})
    for k in range(len(out)):
        out[k]["next"] = out[k + 1]["chg"] if k + 1 < len(out) else None
    return out, (c[-1] / float(B[0]["open"]) - 1) * 100, tot_b / (tot_b + tot_s) * 100 if tot_b + tot_s else None


if __name__ == "__main__":
    day = sys.argv[1]
    allp = {}
    for sym in sys.argv[2:]:
        ph, oc, day_share = run(day, sym)
        allp[sym] = {"oc": oc, "phases": ph}
        print(f"\n=== {sym}  open->close {oc:+.1f}%   day buy share {day_share:.0f}%")
        print(f"   {'phase':13} {'kind':4} {'min':>4} {'chg':>6} {'%/min':>6} {'R2':>5} {'vol x':>6} {'buy%':>5} {'net buy':>9} {'buy rate x':>10} {'vsVWAP':>7} | next")
        for p in ph:
            print(f"   {p['from']}-{p['to']} {p['kind']:4} {p['min']:4} {p['chg']:+5.1f}% {p['speed']:+6.3f} {p['r2']:+5.2f} "
                  f"{(p['vol_x'] or 0):5.1f}x {(p['buy_share'] or 0):4.0f}% {p['net_buy']:+9,.0f} {(p['buy_rate_x'] or 0):9.1f}x "
                  f"{p['vwap_end']:+6.1f}% | {('%+.1f%%' % p['next']) if p['next'] is not None else '-'}")
    json.dump(allp, open(f"/var/www/screener/trade/reports/agen/phase_buyvol_{day}.json", "w"), indent=1)
