#!/usr/bin/env python3
"""ATH breakout events + price/volume/basket ideas, decision at the close of the hour AFTER the breakout bar (the first-hour retest).
Event: first hourly CLOSE above the running closing ATH, ATH 6h+ old, 20%+ pullback since, old ATH cap >= $100k, 48h+ history.
Entry: close of bar i+1 (D). Outcomes over 7 days from entry. Writes /opt/abstudy/events.json."""
import json, math, collections, statistics as st, re
C = [json.loads(l) for l in open("/opt/abstudy/candles.jsonl")]
BANDS = [(1e5, 3e5, "1 micro"), (3e5, 1e6, "2 small"), (1e6, 3e6, "3 medium"), (3e6, 1e7, "4 above medium"), (1e7, 3e7, "5 bigger"), (3e7, 1e13, "6 biggest")]
NARR = {"ai": r"\b(ai|agi|gpt|agent|bot|neural|intelligen|robot|claude|grok|llm)", "cat": r"(cat|kitty|meow|neko)", "dog": r"(dog|inu|doge|shib|puppy|wif)",
        "frog": r"(pepe|frog|kek)", "stock": r"(stock|stonk|nvda|tsla|aapl|amzn|spy|meta|coin|hood|gme|amc|mstr)", "pol": r"(trump|maga|elon|biden|usa|america)",
        "money": r"(money|cash|gold|rich|bank|dollar|yield)"}
def narr(c):
    s = f'{c.get("sym") or ""} {c.get("name") or ""}'.lower()
    return [k for k, p in NARR.items() if re.search(p, s)] or ["other"]
# hourly maps per coin
for c in C:
    c["k"] = sorted(c["k"]); c["T"] = {r[0]: i for i, r in enumerate(c["k"])}; c["narr"] = narr(c)
# chain + narrative baskets: equal-weight mean of hourly log returns (clipped), and breadth (share at a 24h high, new-ATH count)
def basket(group):
    ret = collections.defaultdict(list); hi24 = collections.defaultdict(list); athc = collections.defaultdict(int)
    for c in group:
        k = c["k"]; m = 0
        for i in range(1, len(k)):
            r = math.log(k[i][4] / k[i - 1][4]) if k[i - 1][4] > 0 and k[i][4] > 0 else 0
            ret[k[i][0]].append(max(-0.5, min(0.5, r)))
            if i >= 24: hi24[k[i][0]].append(1 if k[i][4] >= max(x[4] for x in k[i - 24:i]) else 0)
            if k[i][4] > m:
                if i > 24: athc[k[i][0]] += 1
                m = k[i][4]
    ts = sorted(ret); idx, v = {}, 0
    for t in ts:
        v += st.mean(ret[t]) if len(ret[t]) >= 3 else 0; idx[t] = v
    return idx, {t: st.mean(x) for t, x in hi24.items() if len(x) >= 3}, athc
BK = {ch: basket([c for c in C if c["chain"] == ch]) for ch in ("sol", "robinhood")}
NB = {(ch, n): basket([c for c in C if c["chain"] == ch and n in c["narr"]]) for ch in ("sol", "robinhood") for n in list(NARR) + ["other"]}
def bret(idx, t, h):
    a, b = idx.get(t), idx.get(t - h * 3600)
    return (a - b) if a is not None and b is not None else None
def ema(vals, n):
    a = 2 / (n + 1); e = vals[0]
    for x in vals[1:]: e = a * x + (1 - a) * e
    return e
def rsi(cl, n=14):
    if len(cl) < n + 1: return None
    g = [max(0, cl[i] - cl[i - 1]) for i in range(len(cl) - n, len(cl))]; l = [max(0, cl[i - 1] - cl[i]) for i in range(len(cl) - n, len(cl))]
    return 100 - 100 / (1 + (sum(g) / n) / (sum(l) / n + 1e-12))
EV = []
for c in C:
    k = c["k"]; sup = c["sup"]; n = len(k)
    ath, athi, mn, nbreak, last_lab = k[0][4], 0, k[0][4], 0, None
    lowi = 0
    for i in range(1, n - 1):
        cl = k[i][4]
        if cl > ath:
            pull = 1 - mn / ath; mc = ath * sup
            if i - athi >= 6 and pull >= 0.20 and i >= 48 and 1e5 <= mc < 5e10:
                D = i + 1; E = k[D][4]; t = k[i][0]
                fut = k[D + 1:D + 1 + 168]
                if len(fut) >= 24:
                    mx = max(x[4] for x in fut) / E
                    fail = 0; ret_tr = None
                    for j, b in enumerate(fut[:72]):
                        if b[4] >= 1.5 * E: break
                        if b[4] <= 0.8 * ath: fail = 1; break
                    for b in fut:
                        if b[4] >= 2 * E: ret_tr = 1.0; break
                        if b[4] <= 0.75 * E: ret_tr = b[4] / E - 1; break
                    if ret_tr is None: ret_tr = fut[-1][4] / E - 1
                    retest_px = ath * 1.03; filled = k[D][3] <= retest_px
                    E2 = retest_px if filled else E
                    mx2 = max(x[4] for x in fut) / E2
                    pre = k[:i + 1]; cls = [x[4] for x in pre]; vol = [x[5] for x in pre]
                    lowv = min(x[4] for x in k[athi:i])
                    lowj = athi + min(range(i - athi), key=lambda j: k[athi + j][4])
                    run_from_low = cl / lowv
                    r12 = max(x[2] for x in k[i - 12:i]) / min(x[3] for x in k[i - 12:i]); r72 = max(x[2] for x in k[i - 72:i]) / min(x[3] for x in k[i - 72:i]) if i >= 72 else None
                    lows = [x[3] for x in k[lowj:i]]; hl = sum(1 for a_, b_ in zip(lows, lows[1:]) if b_ > a_)
                    v24 = sum(vol[-25:-1]) / 24 + 1e-9
                    bk = BK[c["chain"]]; nbk = [NB[(c["chain"], nn)] for nn in c["narr"]]
                    cret3 = math.log(cl / k[i - 72][4]) if i >= 72 else None
                    b3 = bret(bk[0], t, 72); b7 = bret(bk[0], t, 168); b1 = bret(bk[0], t, 24)
                    nb3 = [bret(x[0], t, 72) for x in nbk]; nb3 = [x for x in nb3 if x is not None]
                    nath24 = sum(bk[2].get(t - h * 3600, 0) for h in range(1, 25))
                    nnath24 = sum(sum(x[2].get(t - h * 3600, 0) for h in range(1, 25)) for x in nbk)
                    bi = bk[0]; b30 = [bi[t - h * 3600] for h in range(0, 720, 24) if (t - h * 3600) in bi]
                    gmt = (t // 3600) % 24
                    EV.append(dict(a=c["a"], chain=c["chain"], sym=c["sym"], name=c["name"], src=c["src"], lp=c.get("lp"), narr=c["narr"], t=t, D=k[D][0],
                        old_ath=ath, mc=mc, band=next(b[2] for b in BANDS if b[0] <= mc < b[1]), entry=E, entry_retest=E2, retest_filled=filled,
                        max7=mx, max7_retest=mx2, big2=1 if mx >= 2 else 0, big4=1 if mx >= 4 else 0, fail=fail, trade=ret_tr - 0.03, mc_entry=E * sup,
                        f=dict(
                          i01_cap=math.log10(mc), i02_hours_since_ath=i - athi, i03_pullback=pull, i04_hours_from_low=i - lowj, i05_nth_break=nbreak + 1,
                          i06_break_candle=cl / k[i][1] - 1, i07_ret24=cl / k[i - 24][4] - 1, i08_above_ema24=cl / ema(cls[-48:], 24) - 1,
                          i09_vol_surge=k[i][5] / v24, i10_vol_trend=(sum(vol[-10:]) / 10 + 1e-9) / (sum(vol[-34:-10]) / 24 + 1e-9),
                          i11_retest_depth=k[D][3] / ath - 1, i12_retest_hold=1 if k[D][4] > ath else 0, i13_upper_wick=(k[i][2] - cl) / (k[i][2] - k[i][3] + 1e-12),
                          i14_higher_lows=hl / max(1, len(lows) - 1), i15_compress=(r12 / r72) if r72 else None, i16_rsi=rsi(cls), i17_age_h=(t - (c.get("created") or k[0][0])) / 3600,
                          i18_hour_gmt=gmt, i19_weekend=1 if (t // 86400 + 4) % 7 >= 5 else 0, i20_basket_1d=b1, i21_basket_3d=b3, i22_basket_7d=b7,
                          i23_basket_at_30d_high=1 if b30 and bi.get(t, -9) >= max(b30) - 1e-9 else 0, i24_rs_vs_basket_3d=(cret3 - b3) if (cret3 is not None and b3 is not None) else None,
                          i25_chain_ath_count_24h=nath24, i26_chain_breadth=bk[1].get(t), i27_run_from_low=run_from_low, i28_launch_multiple=cl / k[0][4],
                          i29_narr_basket_3d=max(nb3) if nb3 else None, i30_rs_vs_narr_3d=(cret3 - max(nb3)) if (cret3 is not None and nb3) else None,
                          i31_narr_ath_count_24h=nnath24, i32_first_in_narr=1 if nnath24 == 0 else 0, i33_d_close_vs_break=E / cl - 1,
                          i34_d_volume_vs_break=k[D][5] / (k[i][5] + 1e-9), i35_prev_break_failed=1 if last_lab == 1 else 0 if last_lab == 0 else None,
                          i36_drop_speed=(lowj - athi), i37_cap_round=1 if any(abs(math.log10(mc) - x) < 0.05 for x in (6, 7, 8)) else 0,
                          i38_4h_confirm=1 if (i % 4 == 3) else None, i39_stock_quote=1 if (c.get("quote") or "").upper() not in ("", "WETH", "ETH") else 0,
                          i40_vol_vs_cap=sum(vol[-24:]) / (mc + 1)
                        )))
                    nbreak += 1; last_lab = fail
            ath, athi, mn = cl, i, cl
        else:
            mn = min(mn, cl)
json.dump(EV, open("/opt/abstudy/events.json", "w"))
cnt = collections.Counter((e["band"]) for e in EV)
print("events", len(EV), "coins", len({e["a"] for e in EV}))
for b in sorted(cnt):
    E_ = [e for e in EV if e["band"] == b]
    print(f"  {b:16s} n {len(E_):5d} coins {len({e['a'] for e in E_}):4d} 2x {st.mean(e['big2'] for e in E_):.0%} 4x {st.mean(e['big4'] for e in E_):.0%} fail {st.mean(e['fail'] for e in E_):.0%} trade {st.mean(e['trade'] for e in E_)*100:+.1f}% chains {dict(collections.Counter(e['chain'] for e in E_))}")
