"""v3drip.py (A1.28, sup-to-a1 v24): DRIP SELLER exit on the dust-guarded mc5 book (same 178 trades and exits as A1.25 to A1.27:
6 s fill, stop 0.5x, 4 h, next-block exits, own buy kept in pool WETH, pull = 0, pool fee + 1% each way + gas).
Seller = tx sender (eth_getTransactionByHash.from) of a swap that takes WETH out of the pool. After our fill, flag DRIP when one
wallet has >= N sells of >= W WETH inside the last 300 blocks (about 30 s); exit at the end of the next block.
Grid W 0.25 / 0.5 / 1.0, N 3 / 5. Pick on H1 ($/day at $100), test once on H2. Pass = H2 $/day up AND worst 3 days not worse AND
up without top 2 AND beats 95% of 200 random exits (same count of cut trades, uniform random time between fill and base exit).
Also: every 5x+ winner the rule would cut (price at the cut), and who the drip wallets are (launcher = tx sender of the first LP
mint, LP owner from A1.26, first funder from the explorer)."""
import collections, statistics as st, datetime, json, time, random, os, urllib.request
src = open("/root/fwa1/v3dead.py").read().split("# new buyer wallets")[0]
a = "return dict(pth=pth, i=i, te=te, base=base, tb=tb, exit_at=exit_at, mc=mc)"
assert src.count(a) == 1
src = src.replace(a, "return dict(pth=pth, i=i, te=te, base=base, tb=tb, exit_at=exit_at, mc=mc, sup=sup)")
exec(src)
alld = sorted({day(p["t"]) for p in allp}); D1 = [d for d in alld if d < day(CUT)]; D2 = [d for d in alld if d >= day(CUT)]
LPOWN = json.load(open("/root/fwa1/v3/lpown.json"))
TXF = "/root/fwa1/v3/txfrom.json"
TXFROM = json.load(open(TXF)) if os.path.exists(TXF) else {}
WIN = 300
# sells of >= 0.25 WETH between fill and base exit, per trade
SELLS = {}; need = set()
for p, s in SET[100]:
    pool, tok, is0, fee, cb, sup, dec = meta[p["pool"]]; fb = p["fb"]
    b0 = fb + int(s["te"] / SPB); b1 = fb + int(s["tb"] / SPB) + 1
    rows = set()
    for DB in (D, E):
        for b, li, tx, a0, a1 in DB.execute("SELECT b, li, tx, a0, a1 FROM sw WHERE pool=? AND b>? AND b<=?", (pool, b0, b1)): rows.add((b, li, tx, a0, a1))
    ev = []
    for b, li, tx, a0, a1 in sorted(rows):
        we = (s256(a1) if is0 else s256(a0)) / 1e18
        if we <= -0.25: ev.append((b, li, tx, -we)); need.add(tx)
    SELLS[p["pool"]] = ev
need = sorted(t for t in need if t not in TXFROM)
print("trades", len(SET[100]), "sell swaps >= 0.25 WETH", sum(len(v) for v in SELLS.values()), "tx lookups", len(need), flush=True)
for a0 in range(0, len(need), 20):
    g = need[a0:a0 + 20]
    res = post([{"jsonrpc": "2.0", "id": k, "method": "eth_getTransactionByHash", "params": [h]} for k, h in enumerate(g)])
    for r in res if isinstance(res, list) else []:
        if r.get("result"): TXFROM[g[r["id"]]] = r["result"]["from"].lower()
    if a0 % 400 == 0:
        json.dump(TXFROM, open(TXF + ".tmp", "w")); os.replace(TXF + ".tmp", TXF)
    time.sleep(0.2)
json.dump(TXFROM, open(TXF + ".tmp", "w")); os.replace(TXF + ".tmp", TXF)
miss = sum(1 for v in SELLS.values() for e in v if e[2] not in TXFROM)
print("tx sender missing", miss, flush=True)
def flag(pool, fb, W, N):
    """first time (s after first swap) a single sender has >= N sells of >= W inside WIN blocks; also the wallet"""
    hist = collections.defaultdict(list)
    for b, li, tx, we in SELLS[pool]:
        if we < W: continue
        w = TXFROM.get(tx)
        if not w: continue
        h = hist[w]; h.append(b)
        while h and h[0] < b - WIN: h.pop(0)
        if len(h) >= N: return (b - fb) * SPB, w
    return None, None
def run(usd, W, N):
    R = []; cut = []
    for p, s in SET[usd]:
        x = s["base"]; t, w = flag(p["pool"], p["fb"], W, N)
        if t is not None and s["te"] < t < s["tb"]:
            xc = s["exit_at"](t); j = state(s["pth"], t + NB)
            mcc = s["pth"][j][1] ** 2 * s["sup"] * p["eu"]
            cut.append((p, s["base"], xc, w, t - s["te"], mcc)); x = xc
        R.append((p, x, usd))
    return R, cut
def worst3(R, days):
    pn = collections.defaultdict(float)
    for p, x, u in R: pn[day(p["t"])] += (x - 1) * u
    dl = sorted(datetime.date(2026, int(d[:2]), int(d[3:])) for d in days)
    cal = [dl[0] + datetime.timedelta(j) for j in range((dl[-1] - dl[0]).days + 1)]
    v = [pn.get(c.strftime("%m-%d"), 0.0) for c in cal]
    return min(sum(v[i:i + 3]) for i in range(max(1, len(v) - 2)))
def S(R, days):
    R = [r for r in R if day(r[0]["t"]) in days]
    if not R: return dict(n=0, mean=0, usd=0, w3=0, top2=0)
    pnl = sorted(((x - 1) * u for p, x, u in R), reverse=True)
    return dict(n=len(R), mean=st.mean(x for p, x, u in R), usd=sum(pnl) / len(days), w3=worst3(R, days), top2=sum(pnl[2:]) / len(days))
def fmt(d): return "n%d mean %.2fx $%+.0f/day worst3 $%+.0f no-top2 $%+.0f/day" % (d["n"], d["mean"], d["usd"], d["w3"], d["top2"])
def cs(cut, days):
    c = [e for e in cut if day(e[0]["t"]) in days]
    return "cut %d (base losers %d, base winners %d, 5x+ %d)" % (len(c), sum(e[1] < 1 for e in c), sum(e[1] >= 1 for e in c), sum(e[1] >= 5 for e in c))
OUT = {}
for usd in (100, 1000):
    base = [(p, s["base"], usd) for p, s in SET[usd]]
    OUT[(usd, "base")] = (S(base, D1), S(base, D2))
    print("\n== $%d ==\nBASE | H1 %s | H2 %s" % (usd, fmt(OUT[(usd, "base")][0]), fmt(OUT[(usd, "base")][1])), flush=True)
    for W in (0.25, 0.5, 1.0):
        for N in (3, 5):
            R, cut = run(usd, W, N); a, b = S(R, D1), S(R, D2); OUT[(usd, W, N)] = (a, b, cut)
            print("W%.2f N%d | H1 %s %s | H2 %s %s" % (W, N, fmt(a), cs(cut, D1), fmt(b), cs(cut, D2)), flush=True)
keys = [k for k in OUT if k[0] == 100 and k[1] != "base"]
pick = max(keys, key=lambda k: (OUT[k][0]["usd"], k[1], k[2]))
print("\nH1 PICK ($100): W%.2f N%d  H1 $%+.0f vs base $%+.0f" % (pick[1], pick[2], OUT[pick][0]["usd"], OUT[(100, "base")][0]["usd"]))
random.seed(24)
for usd in (100, 1000):
    a, b, cut = OUT[(usd,) + pick[1:]]; bb = OUT[(usd, "base")][1]
    c2 = sum(1 for e in cut if day(e[0]["t"]) in D2)
    H2T = [(p, s) for p, s in SET[usd] if day(p["t"]) in D2 and s["tb"] > s["te"] + 1]
    draws = []
    for r in range(200):
        pickd = {id(p) for p, s in random.sample(H2T, min(c2, len(H2T)))}
        R = []
        for p, s in SET[usd]:
            x = s["base"]
            if id(p) in pickd: x = s["exit_at"](random.uniform(s["te"], s["tb"]))
            R.append((p, x, usd))
        draws.append(S(R, D2)["usd"])
    p95 = sorted(draws)[int(0.95 * len(draws)) - 1]
    ok = b["usd"] > bb["usd"] and b["w3"] >= bb["w3"] and b["top2"] > bb["top2"] and b["usd"] > p95
    print(" $%d H2 pick %s\n $%d H2 base %s\n  random same-size exits: median $%+.0f/day, 95th $%+.0f/day\n  -> %s" % (
        usd, fmt(b), usd, fmt(bb), st.median(draws), p95, "PASS" if ok else "FAIL"))
# cut list under the pick, and 5x+ winners cut under ANY cell
print("\ncut trades under the pick ($100): pool | day | base x | drip x | minutes after fill | cap at cut | drip wallet")
for p, xb, xc, w, dt, mcc in OUT[pick][2]:
    print("  %s | %s | %.2fx | %.2fx | %.1f | $%.0fk | %s" % (p["pool"], day(p["t"]), xb, xc, dt / 60, mcc / 1e3, w))
print("\n5x+ winners cut by any cell ($100): cell | pool | day | base x | cut x | minutes after fill | cap at cut")
for k in keys:
    for p, xb, xc, w, dt, mcc in OUT[k][2]:
        if xb >= 5: print("  W%.2f N%d | %s | %s | %.2fx | %.2fx | %.1f | $%.0fk" % (k[1], k[2], p["pool"], day(p["t"]), xb, xc, dt / 60, mcc / 1e3))
# who are the drip wallets (pick cell)
def first_mint_sender(pool):
    for DB in (D, E):
        r = DB.execute("SELECT b, li FROM lp WHERE pool=? AND kind='m' ORDER BY b, li LIMIT 1", (pool,)).fetchone()
        if r: break
    else: return None
    lg = post({"jsonrpc": "2.0", "id": 1, "method": "eth_getLogs", "params": [{"address": pool, "fromBlock": hex(r[0]), "toBlock": hex(r[0])}]}).get("result") or []
    for l in lg:
        if int(l["logIndex"], 16) == r[1]:
            t = post({"jsonrpc": "2.0", "id": 1, "method": "eth_getTransactionByHash", "params": [l["transactionHash"]]}).get("result")
            return t["from"].lower() if t else None
    return None
def funder(w):
    for u in ("https://robinhoodchain.blockscout.com/api?module=account&action=txlist&sort=asc&page=1&offset=3&address=" + w,
              "https://robinhoodchain.blockscout.com/api?module=account&action=txlistinternal&sort=asc&page=1&offset=3&address=" + w):
        try:
            d = json.load(urllib.request.urlopen(urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"}), timeout=20))
            for t in d.get("result") or []:
                if t.get("to", "").lower() == w and int(t.get("value", "0")) > 0: return t["from"].lower()
        except Exception as e: return "explorer error %s" % type(e).__name__
    return "none found"
print("\ndrip wallet identity (pick cell): pool | drip wallet | launcher | LP owner | first funder | relation")
for p, xb, xc, w, dt, mcc in OUT[pick][2]:
    L = first_mint_sender(p["pool"]); O = (LPOWN.get(p["pool"]) or [None, None, None])[2]; F = funder(w)
    rel = "IS launcher" if w == L else "IS LP owner" if w == O else "funded by launcher" if F == L else "funded by LP owner" if F == O else "no link found"
    print("  %s | %s | %s | %s | %s | %s" % (p["pool"], w, L, O, F, rel), flush=True)
