"""
bb_pattern_search.py -- [2026-09-30] The user: "look back 50 days and try to identify trades
that had a similar pattern as BB today, so we can have an idea how often we will see that
pattern". First version of the user's matching idea (whole day from 9:30, all main measures).

For every stock-day on the saved lists in the last 50 trading days (bars/ + 14-day reference):
per minute from 9:30, forward-filled
   p   price vs the open, %
   w   price vs VWAP, %
   x   log(1 + volume vs its 14-day normal for that minute)
distance to BB 9/30 over a window = RMS(p diff) + RMS(w diff) + 0.5 * RMS(x diff)
windows: MORNING 9:30-9:48 (what was known at the suggested buy, 8.79)
         TO 12:50  (through BB's climb, up to its 12:46 volume-spike top)
         FULL DAY
Outcomes after 9:48 for the morning matches: best gain afterwards, time of the peak,
+2% before -1%, close.
"""
import gzip
import json
import math
import sys
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

TRADE = Path("/var/www/screener/trade")
sys.path.insert(0, str(TRADE))
from alpaca_client import get_client
import reference as REF

ET = ZoneInfo("America/New_York")
BARS = TRADE / "reports" / "open_window" / "bars"
OUT = TRADE / "reports" / "playbook"
WIN = {"MORNING (9:30-9:48)": 18, "TO 12:50": 200, "FULL DAY": 385}


def paths(rows, ref):
    """rows: [[ts, o, h, l, c, v], ...] regular session."""
    by = {}
    for b in rows:
        t = datetime.fromtimestamp(b[0], ET) if not isinstance(b[0], datetime) else b[0].astimezone(ET)
        m = (t.hour - 9) * 60 + t.minute - 30
        if 0 <= m < 390:
            by[m] = b
    if not by:
        return None
    o = by[min(by)][1]
    P, W, X, C, H, L = [], [], [], [], [], []
    last, pv, cv = o, 0.0, 0.0
    for m in range(390):
        b = by.get(m)
        v = 0.0
        if b:
            last = b[4]
            v = b[5]
            pv += (b[2] + b[3] + b[4]) / 3 * v
            cv += v
            H.append(b[2]); L.append(b[3])
        else:
            H.append(last); L.append(last)
        C.append(last)
        vw = pv / cv if cv else last
        P.append((last / o - 1) * 100)
        W.append((last / vw - 1) * 100)
        X.append(math.log1p(v / max(ref["vol_per_min"][m], 1)))
    return {"o": o, "P": P, "W": W, "X": X, "C": C, "H": H, "L": L}


def dist(a, b, n):
    rp = math.sqrt(sum((a["P"][k] - b["P"][k]) ** 2 for k in range(n + 1)) / (n + 1))
    rw = math.sqrt(sum((a["W"][k] - b["W"][k]) ** 2 for k in range(n + 1)) / (n + 1))
    rx = math.sqrt(sum((a["X"][k] - b["X"][k]) ** 2 for k in range(n + 1)) / (n + 1))
    return rp + rw + 0.5 * rx


def after(s, m=18):
    e = s["C"][m]
    hi_k = max(range(m + 1, 386), key=lambda k: s["H"][k])
    first = "time"
    for k in range(m + 1, 386):
        if s["L"][k] <= e * 0.99:
            first = "stop"
            break
        if s["H"][k] >= e * 1.02:
            first = "target"
            break
    return {"best": (s["H"][hi_k] / e - 1) * 100, "peak_t": f"{9 + (30 + hi_k) // 60}:{(30 + hi_k) % 60:02d}",
            "close": (s["C"][385] / e - 1) * 100, "first": first}


def main():
    day0 = "2026-09-30"
    d = datetime.fromisoformat(day0).replace(tzinfo=ET)
    raw = get_client().get_minute_bars("BB", start=d.replace(hour=9, minute=30), end=d.replace(hour=16), limit=1000)
    bb = paths([[b.timestamp, float(b.open), float(b.high), float(b.low), float(b.close), float(b.volume)] for b in raw],
               REF.load(day0, "BB"))
    files = sorted(BARS.glob("bars_20*.json.gz"))[-50:]
    cands = []
    for p in files:
        dd = json.load(gzip.open(p, "rt"))
        for s in dd["top30_current"]:
            if dd["date"] == day0 and s == "BB":
                continue
            ref = REF.load(dd["date"], s)
            rows = dd["symbols"].get(s, {}).get("bars", [])
            if not ref or len(rows) < 200:
                continue
            x = paths(rows, ref)
            if x:
                cands.append((dd["date"], s, x))
        REF._CACHE.clear()
    print(f"{len(cands)} stock-days, {files[0].name[5:15]} .. {files[-1].name[5:15]} ({len(files)} days)\n")
    res = {}
    for name, n in WIN.items():
        ds = sorted(((dist(bb, x, n), day, s, x) for day, s, x in cands), key=lambda r: r[0])
        res[name] = ds
        allv = [r[0] for r in ds]
        print(f"=== {name}: distance to BB  best {allv[0]:.2f}  5th pct {allv[len(allv) // 20]:.2f}  median {allv[len(allv) // 2]:.2f}")
        for r in ds[:12]:
            a = after(r[3])
            print(f"   {r[1]} {r[2]:5} dist {r[0]:.2f} | open->close {r[3]['P'][385]:+5.1f}% | after 9:48: best {a['best']:+5.1f}% "
                  f"(peak {a['peak_t']}), +2% before -1%: {a['first']}, close {a['close']:+5.1f}%")
    # how often: morning matches within the closest 5% / under fixed distance, and their outcomes
    ds = res["MORNING (9:30-9:48)"]
    for cut_name, rows in (("closest 25", ds[:25]), ("closest 5% (" + str(len(ds) // 20) + ")", ds[:len(ds) // 20])):
        A = [after(r[3]) for r in rows]
        t = sum(a["first"] == "target" for a in A)
        sp = sum(a["first"] == "stop" for a in A)
        print(f"\nMORNING matches, {cut_name}: +2% before -1% {t}/{len(A)} ({t / len(A) * 100:.0f}%), -1% first {sp}, "
              f"avg best after {sum(a['best'] for a in A) / len(A):+.1f}%, avg close {sum(a['close'] for a in A) / len(A):+.2f}%, "
              f"per day {len(rows) / len(files):.1f}")
    bb_a = after(bb)
    print(f"\nBB itself after 9:48: best {bb_a['best']:+.1f}% (peak {bb_a['peak_t']}), {bb_a['first']}, close {bb_a['close']:+.1f}%")
    # chart: BB vs the 6 closest full-day and morning matches
    fig, axs = plt.subplots(2, 1, figsize=(11, 8))
    for ax, name in zip(axs, ("MORNING (9:30-9:48)", "FULL DAY")):
        ax.plot(range(390), bb["P"], color="black", lw=2.2, label="BB 9/30")
        for r in res[name][:6]:
            ax.plot(range(390), r[3]["P"], lw=1, alpha=.8, label=f"{r[2]} {r[1][5:]} (d {r[0]:.2f})")
        if name.startswith("MORNING"):
            ax.axvline(18, color="grey", ls="--", lw=.8)
            ax.text(19, ax.get_ylim()[1] * .9, "9:48", fontsize=8)
        ax.set_xticks([0, 30, 90, 150, 210, 270, 330, 389])
        ax.set_xticklabels(["9:30", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00"])
        ax.axhline(0, color="grey", lw=.6)
        ax.set_ylabel("price vs open %")
        ax.set_title(f"BB 9/30 vs its 6 closest matches -- {name} (last 50 trading days)", fontsize=10)
        ax.legend(fontsize=7, ncol=2)
        ax.grid(alpha=.3)
    plt.tight_layout()
    plt.savefig(OUT / "BB_2026-09-30_pattern_matches.png", dpi=115)


if __name__ == "__main__":
    main()
