"""
label_days.py -- [2026-10-01] The user: the library's reference days must have their best
entry and exit spots marked, like the BB chart. Marks every stock-day in the library
(reports/open_window/bars/, ~2 years of 9:28/9:30 lists) automatically, on 1-min closes:

  best_any      the biggest buy -> later sell gain of the day (hindsight, no limits)
  best_held     the biggest gain a bot could actually have held: never more than max_dd%
                (1%) under the buy price between the buy and the sell, held >= 5 minutes
  leg2          a second held trade after the first one's sell (two-move days like AGEN)
  no_trade      best_held gain < 1%: the right decision that day was not to trade
Each mark keeps the measured signals at the buy and the sell (analyze_trades.entry_signals /
exit_signals: vs open, vs VWAP, minutes since the low, volume vs normal, tags ...).
Buys are allowed 9:31-15:15, sells until 15:55.

Output: data/playbook/day_labels.jsonl (one line per stock-day) + a few sample charts.
    python3 label_days.py              (all days)     python3 label_days.py --charts 12
"""
import gzip
import json
import random
import sys
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

HERE = Path(__file__).resolve().parent
TRADE = Path("/var/www/screener/trade")
sys.path.insert(0, str(TRADE))
sys.path.insert(0, str(HERE))
import reference as REF
import analyze_trades as A

ET = ZoneInfo("America/New_York")
BARS = TRADE / "reports" / "open_window" / "bars"
OUT = TRADE / "data" / "playbook" / "day_labels.jsonl"
MAX_DD = 1.0
MIN_HOLD = 5


def build(rows, ref):
    B, pv, v = [], 0.0, 0.0
    for b in rows:
        t = datetime.fromtimestamp(b[0], ET)
        m = (t.hour - 9) * 60 + t.minute - 30
        if not 0 <= m < 390:
            continue
        x = {"t": t, "m": m, "o": b[1], "h": b[2], "l": b[3], "c": b[4], "v": b[5]}
        pv += (x["h"] + x["l"] + x["c"]) / 3 * x["v"]
        v += x["v"]
        x["vwap"] = pv / v if v else x["c"]
        x["volx"] = x["v"] / max(ref["vol_per_min"][m], 1)
        B.append(x)
    return B


def best_held(B, start=0, lo_m=1, hi_m=345, exit_by=385):
    """Best buy i / sell j (closes), j >= i + MIN_HOLD, low between never < buy*(1-MAX_DD%)."""
    best = None
    n = len(B)
    for i in range(start, n):
        if not (lo_m <= B[i]["m"] <= hi_m):
            continue
        e = B[i]["c"]
        floor = e * (1 - MAX_DD / 100)
        hi_c, hi_j = None, None
        for j in range(i + 1, n):
            if B[j]["m"] > exit_by or B[j]["l"] < floor:
                break
            if j >= i + MIN_HOLD and (hi_c is None or B[j]["c"] > hi_c):
                hi_c, hi_j = B[j]["c"], j
        if hi_j is not None:
            g = hi_c / e - 1
            if best is None or g > best[2]:
                best = (i, hi_j, g)
    return best


def mark(B, tr):
    if not tr:
        return None
    i, j, g = tr
    return {"entry": A.entry_signals(B, i, B[0]["o"]), "exit": A.exit_signals(B, j, i),
            "gain_pct": round(g * 100, 2), "entry_m": B[i]["m"], "exit_m": B[j]["m"]}


def label(day, s, B):
    ba = A.best_trade(B, 1, 345)
    bh = best_held(B)
    leg2 = best_held(B, start=bh[1] + 1) if bh else None
    lab = {"date": day, "symbol": s, "open": B[0]["o"], "close": B[-1]["c"],
           "open_to_close_pct": round((B[-1]["c"] / B[0]["o"] - 1) * 100, 2),
           "best_any": mark(B, ba), "best_held": mark(B, bh),
           "leg2": mark(B, leg2) if leg2 and leg2[2] * 100 >= 1 else None}
    lab["no_trade"] = not bh or bh[2] * 100 < 1.0
    return lab


def chart(lab, B, path):
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    import matplotlib.dates as md
    ts = [x["t"].replace(tzinfo=None) for x in B]
    fig, (ax, axv) = plt.subplots(2, 1, figsize=(11, 5.4), gridspec_kw={"height_ratios": [3, 1]}, sharex=True)
    ax.plot(ts, [x["c"] for x in B], color="#1f3a5f", lw=1)
    ax.plot(ts, [x["vwap"] for x in B], color="#c0392b", lw=1.1)
    ax.axhline(B[0]["o"], color="grey", ls=":", lw=.8)
    for key, col in (("best_held", "#16a085"), ("leg2", "#2980b9")):
        mk = lab.get(key)
        if not mk:
            continue
        bi = next(x for x in B if x["m"] == mk["entry_m"])
        bj = next(x for x in B if x["m"] == mk["exit_m"])
        ax.scatter([bi["t"].replace(tzinfo=None)], [bi["c"]], marker="*", s=170, color=col, edgecolor="black", zorder=5)
        ax.scatter([bj["t"].replace(tzinfo=None)], [bj["c"]], marker="*", s=170, color="#8e44ad", edgecolor="black", zorder=5)
        ax.annotate(f"buy {bi['c']:.2f}\n" + ", ".join(mk["entry"]["tags"]), (bi["t"].replace(tzinfo=None), bi["c"]),
                    xytext=(6, -26), textcoords="offset points", fontsize=7, color=col)
        ax.annotate(f"sell {bj['c']:.2f} (+{mk['gain_pct']:.1f}%)\n" + ", ".join(mk["exit"]["tags"]),
                    (bj["t"].replace(tzinfo=None), bj["c"]), xytext=(6, 8), textcoords="offset points", fontsize=7,
                    color="#8e44ad")
    ax.set_title(f"{lab['symbol']} {lab['date']}  open->close {lab['open_to_close_pct']:+.1f}%  "
                 + ("NO GOOD TRADE (best held < 1%)" if lab["no_trade"] else "marked best trade(s) a bot could hold (stop 1%)"),
                 fontsize=9)
    ax.grid(alpha=.3)
    axv.bar(ts, [x["v"] for x in B], width=1 / 1440, color=["#8e44ad" if x["volx"] >= 10 else "#1f3a5f" for x in B])
    axv.xaxis.set_major_formatter(md.DateFormatter("%H:%M"))
    axv.grid(alpha=.3)
    plt.tight_layout()
    plt.savefig(path, dpi=105)
    plt.close()


def main(n_charts=0):
    labs, sample = [], []
    files = sorted(BARS.glob("bars_20*.json.gz"))
    for p in files:
        d = json.load(gzip.open(p, "rt"))
        day = d["date"]
        for s in d["top30_current"]:
            ref = REF.load(day, s)
            rows = d["symbols"].get(s, {}).get("bars", [])
            if not ref or len(rows) < 200:
                continue
            B = build(rows, ref)
            if len(B) < 200:
                continue
            lab = label(day, s, B)
            labs.append(lab)
            if n_charts:
                sample.append((lab, B) if len(sample) < 400 else sample[random.randrange(len(sample))])
        REF._CACHE.clear()
    OUT.parent.mkdir(parents=True, exist_ok=True)
    with open(OUT, "w") as f:
        for lab in labs:
            f.write(json.dumps(lab, default=str) + "\n")
    nt = sum(l["no_trade"] for l in labs)
    g = sorted(l["best_held"]["gain_pct"] for l in labs if l["best_held"])
    print(f"{len(labs)} stock-days labeled ({files[0].name[5:15]}..{files[-1].name[5:15]}) -> {OUT}")
    print(f"no good trade (best held < 1%): {nt} ({nt / len(labs) * 100:.0f}%); best held gain median {g[len(g) // 2]:.2f}%, "
          f"75th pct {g[len(g) * 3 // 4]:.2f}%, 90th {g[len(g) * 9 // 10]:.2f}%; two-move days {sum(1 for l in labs if l['leg2'])}")
    if n_charts:
        random.seed(1)
        for lab, B in random.sample(sample, min(n_charts, len(sample))):
            chart(lab, B, HERE / "labels" / f"{lab['date']}_{lab['symbol']}.png")


if __name__ == "__main__":
    (HERE / "labels").mkdir(exist_ok=True)
    main(int(sys.argv[sys.argv.index("--charts") + 1]) if "--charts" in sys.argv else 0)
