"""DC1.1 analysis (DC Analyst 1, 7 Oct 2026): universe labels, triggers, baseline P&L, timing. Reads /root/dca1/ev_v4.pkl, ev_v3.pkl."""
import pickle, gzip, csv, math, statistics, datetime, collections, bisect, json, random
OUT = "/root/dca1"
BKK = datetime.timezone(datetime.timedelta(hours=7))
def bk(t): return datetime.datetime.fromtimestamp(t, BKK).strftime("%Y-%m-%d %H:%M") if t else ""
T_START = int(datetime.datetime(2026, 9, 15, tzinfo=BKK).timestamp()); T_END = int(datetime.datetime(2026, 10, 6, tzinfo=BKK).timestamp())
T_B = int(datetime.datetime(2026, 9, 29, tzinfo=BKK).timestamp())
DAYS_A = (T_B - T_START) / 86400; DAYS_B = (T_END - T_B) / 86400
H24, D7 = 86400, 7 * 86400
CELLS = [(L, N) for L in (150_000, 250_000, 500_000) for N in (50, 100, 200)]
DELAYS = (0, 900, 3600, 4 * 3600, 8 * 3600)
P = {}
for f in ("ev_v4.pkl", "ev_v3.pkl"):
    try: P.update(pickle.load(open(OUT + "/" + f, "rb")))
    except Exception as e: print("missing", f, e)
# one row per coin: the pool with the highest sellable peak
best = {}
for p, d in P.items():
    pk = max(x[6] for x in d["ev"]); key = d["token"] or p
    if key not in best or pk > best[key][0]: best[key] = (pk, p)
coins = [P[p] for _, p in best.values()]
DATAEND = max(x[0] for d in coins for x in d["ev"][-1:])
print("coins", len(coins), "pools", len(P), "data end", bk(DATAEND))
def Rfix(R, cap):
    if not R or R <= 0: R = 0.1 * cap
    return min(R, 0.5 * cap) if cap > 0 else R
def sim(d, i, ticket):
    """Buy `ticket` USD at event i. Returns (multiple, reason, exit_t)."""
    ev = d["ev"]; pull = d["pull"] or 9e18
    t_e, cap_e = ev[i][0], ev[i][1]
    if cap_e <= 0: return None
    R = Rfix(ev[i][7], cap_e); units = ticket * 0.99 * R / (R + ticket) / cap_e
    def out(cap, R):
        if cap <= 0: return 0.0
        v = units * cap; R = Rfix(R, cap); return v * R / (R + v) * 0.99 / ticket
    last = ev[i]
    for j in range(i + 1, len(ev)):
        x = ev[j]
        if x[0] >= pull: return (0.0, "pull", pull)
        if x[0] > t_e + D7: return (out(last[1], last[7]), "7d", last[0])
        if x[2] <= 0.3 * cap_e: return (out(x[2], x[7]), "stop", x[0])
        if x[6] >= 5 * cap_e: return (out(x[6], x[7]), "5x", x[0])
        last = x
    if pull < 9e18 and pull <= DATAEND: return (0.0, "pull", pull)
    return (out(last[1], last[7]), "open" if last[0] < t_e + D7 else "7d", last[0])
def honest5x(d, i0, ticket=1000):
    """Best net multiple for a $ticket buy at any event >= i0 sold later at a sellable event, before pull."""
    ev = d["ev"]; pull = d["pull"] or 9e18; best_cost = None; bestm = 0.0
    for j in range(i0, len(ev)):
        x = ev[j]
        if x[0] >= pull: break
        if best_cost is not None and x[6] > 0:
            units = ticket * 0.99 / best_cost; v = units * x[6]; R = Rfix(x[7], x[6])
            bestm = max(bestm, v * R / (R + v) * 0.99 / ticket)
        if x[1] > 0:
            R = Rfix(x[7], x[1]); c = x[1] * (R + ticket) / R
            if best_cost is None or c < best_cost: best_cost = c
    return bestm
rows = []; trades = []
for d in coins:
    ev = d["ev"]; pull = d["pull"] or 0; t0 = d["t0"]; TS = [x[0] for x in ev]
    HI = []; _h = 0.0
    for x in ev: _h = max(_h, x[6]); HI.append(_h)
    i100 = next((k for k, x in enumerate(ev) if x[6] >= 100_000), None)
    if i100 is None: continue
    def pk(h):
        m = 0.0
        for x in ev:
            if x[0] > t0 + h or (pull and x[0] >= pull): break
            m = max(m, x[6])
        return m
    h5 = honest5x(d, i100)
    r = dict(token=d["token"], pool=d["pool"], venue=d["venue"], sym=d["sym"], creator=d["creator"], hooks=d["hooks"],
             first_trade=bk(t0), t0=int(t0), t100=bk(ev[i100][0]), pull=bk(pull) if pull else "", pk24=round(pk(H24)), pk7=round(pk(D7)),
             best_1k_mult=round(h5, 2), honest5x=int(h5 >= 5), partial7d=int(t0 + D7 > DATAEND))
    # triggers
    trig = {}
    nb = 0; hmax = 0.0
    for k, x in enumerate(ev):
        nb += x[5]
        if pull and x[0] >= pull: break
        for L, N in CELLS:
            key = "T1_%dk_%d" % (L // 1000, N)
            if key not in trig and x[6] >= L and nb >= N: trig[key] = k
    # T3: hour with $50k+ volume, coin older than 24 h, prior 24 h under $50k
    vol = collections.defaultdict(float)
    for k, x in enumerate(ev):
        if pull and x[0] >= pull: break
        hr = int(x[0] // 3600) * 3600; vol[hr] += x[3] + x[4]
        if "T3" not in trig and hr >= t0 + H24 and vol[hr] >= 50_000:
            prior = sum(vol.get(hr - 3600 * m, 0) for m in range(1, 25))
            if prior < 50_000: trig["T3"] = k
    for key, k in trig.items():
        r[key] = bk(ev[k][0]); r[key + "_min"] = round((ev[k][0] - t0) / 60, 1)
        tt = ev[k][0]
        if tt < T_START or tt >= T_END: continue
        half = "A" if tt < T_B else "B"
        for dl in DELAYS:
            j = k if dl == 0 else bisect.bisect_left(TS, tt + dl)
            if j >= len(ev): continue
            if pull and ev[j][0] >= pull: continue
            if dl:
                hi = HI[j]
                if ev[j][1] < 0.7 * hi: continue
            s = sim(d, j, 100); s1k = sim(d, j, 1000)
            if not s: continue
            trades.append(dict(trigger=key, delay=dl, half=half, token=d["token"], pool=d["pool"], venue=d["venue"], entry=bk(ev[j][0]),
                               entry_cap=round(ev[j][1]), mult=round(s[0], 4), reason=s[1], exit=bk(s[2]), mult1k=round(s1k[0], 4),
                               honest5x_coin=r["honest5x"], caught=int(dl == 0 and s1k[0] >= 5 or (s1k[1] == "5x" and s1k[0] >= 5))))
    rows.append(r)
keys = sorted({k for r in rows for k in r}, key=lambda k: (k not in ("token", "pool", "venue"), k))
with gzip.open(OUT + "/dc-a1-1-universe.csv.gz", "wt") as f:
    w = csv.DictWriter(f, fieldnames=keys); w.writeheader(); w.writerows(rows)
with gzip.open(OUT + "/dc-a1-1-trades.csv.gz", "wt") as f:
    w = csv.DictWriter(f, fieldnames=list(trades[0])); w.writeheader(); w.writerows(trades)
# summary
U = [r for r in rows if T_START <= r["t0"] < T_END]
H = [r for r in U if r["honest5x"]]
print("universe coins", len(U), "honest5x", len(H), "per day", round(len(U) / 21, 1))
S = {}
TR = sorted({t["trigger"] for t in trades}, key=lambda k: (k[:2], k))
for key in TR:
    for dl in DELAYS:
        tt = [t for t in trades if t["trigger"] == key and t["delay"] == dl]
        A = [t for t in tt if t["half"] == "A"]; B = [t for t in tt if t["half"] == "B"]
        fired = [r for r in U if key in r]
        mins = [r[key + "_min"] for r in fired]
        catch = len({t["token"] for t in tt if t["mult1k"] >= 5})
        S["%s|%d" % (key, dl)] = dict(trigger=key, delay_min=dl // 60, coins_day=round(len(fired) / 21, 1), trades=len(tt),
            catch_honest5x=catch, catch_share=round(catch / max(1, len(H)), 3), med_min_to_trigger=round(statistics.median(mins), 1) if mins else None,
            usd_day_A=round(sum((t["mult"] - 1) * 100 for t in A) / DAYS_A, 1), usd_day_B=round(sum((t["mult"] - 1) * 100 for t in B) / DAYS_B, 1),
            nA=len(A), nB=len(B), mean_x_A=round(statistics.mean([t["mult"] for t in A]), 3) if A else None,
            mean_x_B=round(statistics.mean([t["mult"] for t in B]), 3) if B else None,
            usd_day_A_1k=round(sum((t["mult1k"] - 1) * 1000 for t in A) / DAYS_A, 1), usd_day_B_1k=round(sum((t["mult1k"] - 1) * 1000 for t in B) / DAYS_B, 1),
            pulls=sum(t["reason"] == "pull" for t in tt), fives=sum(t["reason"] == "5x" for t in tt), stops=sum(t["reason"] == "stop" for t in tt),
            opens=sum(t["reason"] == "open" for t in tt))
json.dump(S, open(OUT + "/summary.json", "w"), indent=1)
for k, v in S.items(): print(json.dumps(v))
