"""v3an.py (FW Analyst 1): first 2 h of every v3 WETH pool since 13 Sep. Builds per-pool launch facts at 1/5/15 min, the 2 h $1M label,
and per-wallet real results (ETH in, ETH out, tokens left marked at the 2 h last price), with walk-forward repeat-wallet memory."""
import sqlite3, json, bisect, collections, pickle
SPB = 0.101283
D = sqlite3.connect("file:/root/fwa1/v3/v3a1.db?mode=ro", uri=True)
HUB = {l.split()[0].lower() for l in open("/root/fwa1/v3/hubs.txt") if l.strip()}
HUB |= {"0x0000000000000000000000000000000000000000", "0x000000000000000000000000000000000000dead"}
EU = json.load(open("/root/fwa1/v3/ethusd.json")); EUt = [p[0] / 1000 for p in EU]
def ethusd(ts):
    i = max(0, min(len(EU) - 1, bisect.bisect_left(EUt, ts))); return EU[i][1]
B0, T0 = 76678020, 1790787651.0
def btime(b): return T0 + (b - B0) * SPB
def s256(h):
    v = int(h, 16); return v - (1 << 256) if v >= 1 << 255 else v
meta = D.execute("SELECT pool, token, is0, fee, cb, supply, dec FROM meta").fetchall()
first = dict(D.execute("SELECT pool, MIN(b) FROM sw GROUP BY pool").fetchall())
meta = sorted([m for m in meta if m[0] in first and m[5]], key=lambda m: first[m[0]])
early_mem = collections.Counter()   # wallet -> number of EARLIER pools it bought in within 15 min of first swap
pending = []                        # (unlock_block, wallets) memory becomes known only after that pool's 15 min window closes
P = []; WAL = []
for n, (pool, tok, is0, fee, cb, sup, dec) in enumerate(meta):
    fb = first[pool]
    while pending and pending[0][0] <= fb:
        for w in pending.pop(0)[1]: early_mem[w] += 1
    sw = D.execute("SELECT b, li, tx, a0, a1 FROM sw WHERE pool=? ORDER BY b, li", (pool,)).fetchall()
    tr = collections.defaultdict(list)
    for b, li, tx, frm, too, v in D.execute("SELECT b, li, tx, frm, too, v FROM tr WHERE token=? ORDER BY b, li", (tok,)):
        tr[tx].append((frm.lower(), too.lower(), int(v, 16) / 10 ** dec))
    eu = ethusd(btime(fb))
    pxs = []; txw = collections.defaultdict(float); txt = collections.defaultdict(float); txb = {}
    for b, li, tx, a0, a1 in sw:
        d0, d1 = s256(a0), s256(a1)
        tk, we = (d0, d1) if is0 else (d1, d0)
        tk /= 10 ** dec; we /= 1e18
        if tk == 0 or we == 0: continue
        s = (b - fb) * SPB
        px = abs(we / tk)
        pxs.append((s, px, abs(we)))
        txw[tx] += we; txt[tx] += tk; txb[tx] = b
    if not pxs: continue
    mc = lambda px: px * sup * eu
    # $1M label: first swap of 0.01+ ETH at mcap >= $1M, within 2 h
    t1m = next((s for s, px, w in pxs if w >= 0.01 and mc(px) >= 1e6), None)
    peak = max(mc(px) for s, px, w in pxs if w >= 0.01) if any(w >= 0.01 for _, _, w in pxs) else 0
    big = [px for s_, px, w_ in pxs if w_ >= 0.01]
    lastpx = big[-1] if big else pxs[-1][1]
    poolweth = sum(we for we in txw.values()) if False else None
    # wallets per tx
    W = {}
    for tx, we in txw.items():
        s = (txb[tx] - fb) * SPB; T = tr.get(tx, [])
        if we > 0:      # buy: pool got WETH, follow tokens out of the pool through hubs
            cur = [too for frm, too, v in T if frm == pool]
            w = None
            for hop in range(4):
                if not cur: break
                c = cur[-1]
                if c not in HUB and c != pool: w = c; break
                cur = [too for frm, too, v in T if frm == c]
            if not w: continue
            r = W.setdefault(w, [s, 0.0, 0.0, 0.0, 0]); r[1] += we; r[3] += -txt[tx]; r[4] += 1; r[0] = min(r[0], s)
        else:           # sell: tokens into pool (maybe via hub), seller = sender that is not a hub
            snd = [frm for frm, too, v in T if frm not in HUB and frm != pool and (too == pool or too in HUB)]
            if not snd: continue
            w = snd[0]
            r = W.setdefault(w, [9e9, 0.0, 0.0, 0.0, 0]); r[2] += -we; r[3] += -txt[tx]
    feats = {}
    for T in (60, 300, 900):
        bw = {w: r for w, r in W.items() if r[0] < T}
        buyeth = sum(r[1] for r in bw.values())
        rep1 = sum(r[1] for w, r in bw.items() if early_mem[w] >= 1); rep3 = sum(r[1] for w, r in bw.items() if early_mem[w] >= 3)
        prior = [(s, px) for s, px, wv in pxs if s < T]
        feats[T] = dict(nbuy=len(bw), nnew=sum(1 for w in bw if early_mem[w] == 0), buyeth=buyeth,
                        rep1=rep1 / buyeth if buyeth else 0, rep3=rep3 / buyeth if buyeth else 0,
                        mc=mc(prior[-1][1]) if prior else None, mcmax=max(mc(px) for s, px in prior) if prior else None,
                        mc0=mc(pxs[0][1]), nswap=len(prior))
    P.append(dict(pool=pool, token=tok, fee=fee, fb=fb, t=btime(fb), eu=eu, t1m=t1m, peak=peak, last_mc=mc(lastpx), feats=feats, nw=len(W)))
    for w, r in W.items():
        if r[0] > 900 or r[1] <= 0: continue
        WAL.append((pool, w, r[0], r[1], r[2], min(max(r[3], 0) * lastpx, 20 * r[1]), early_mem[w], t1m))
    pending.append((fb + int(900 / SPB), [w for w, r in W.items() if r[0] < 900]))
    pending.sort()
    if n % 500 == 0: print(n, len(P), len(WAL), flush=True)
pickle.dump((P, WAL), open("/root/fwa1/v3/an.pkl", "wb"))
print("done", len(P), len(WAL))
