"""v3extend.py (A1.27, sup-to-a1 v23): CONDITIONAL HOLD PAST 4 H on the dust-guarded mc5 book (same trades and exits as A1.25/A1.26:
6 s fill, stop 0.5x, next-block exits, own buy kept, pull = 0). At fill + 4 h, if the trade is still open AND x >= M (2/3/5)
AND new buyer wallets in the last 60 min >= B (50/150/300) AND price > its level 1 h earlier: hold to fill + 8 h or + 12 h with a stop at
0.5 x the 4 h value (next-block exit), pull = 0; else sell at 4 h as now. Data reaches first swap + 12 h (12 h holds end there).
New buyer wallet = first time an address receives the token (Transfer logs from first swap), not the pool, not a router or bot.
Pick on H1 ($/day at $100), test once on H2: pass if H2 $/day up AND worst 3 days not worse AND $/day without top 2 up."""
import collections, statistics as st, datetime, json, time, math, numpy as np
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, val=val, pull=pull, end=end)")
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)]
ALIVE = [(p, s) for p, s in SET[100] if s["tb"] >= s["end"] and s["base"] >= 2]
COV = {p["pool"]: (head - p["fb"]) * SPB for p in allp}
print("open at 4 h with x>=2:", len(ALIVE), flush=True)
# new buyer wallets, first swap to fill + 4 h, candidates only
jobs = []
for p, s in ALIVE:
    tok = meta[p["pool"]][1]; z = p["fb"] + int((s["te"] + 4 * 3600) / SPB)
    for f in range(p["fb"], z + 1, 50000): jobs.append((p["pool"], tok, f, min(f + 49999, z)))
TB = collections.defaultdict(list); bad = set()
for a0 in range(0, len(jobs), 20):
    g = jobs[a0:a0 + 20]
    res = post([{"jsonrpc": "2.0", "id": k, "method": "eth_getLogs", "params": [{"address": tok, "fromBlock": hex(f), "toBlock": hex(t), "topics": [T_TR]}]} for k, (pl, tok, f, t) in enumerate(g)])
    got = {r["id"]: r for r in (res if isinstance(res, list) else [])}
    for k, (pl, tok, f, t) in enumerate(g):
        r = got.get(k)
        if not r or "result" not in r: bad.add(pl); continue
        TB[pl] += r["result"]
    time.sleep(0.25)
NB60 = {}
for p, s in ALIVE:
    pl = p["pool"]
    if pl in bad: continue
    seen = set(); n = 0; t1 = s["te"] + 3 * 3600; t2 = s["te"] + 4 * 3600
    for l in sorted(TB[pl], key=lambda l: (int(l["blockNumber"], 16), int(l["logIndex"], 16))):
        to = "0x" + l["topics"][2][-40:]
        if to in seen or to == pl or to in ROUT or to == "0x" + "0" * 40: continue
        seen.add(to); t = (int(l["blockNumber"], 16) - p["fb"]) * SPB
        if t1 < t <= t2: n += 1
    NB60[pl] = n
print("buyer counts", len(NB60), "failed pools", len(bad), "median new buyers last hour", st.median(NB60.values()) if NB60 else None, flush=True)
def ext(s, hold):
    pth, val, pull = s["pth"], s["val"], s["pull"]; x4 = s["base"]; end = s["end"]; stop = 0.5 * x4; z = s["te"] + hold
    for k in range(state(pth, end) + 1, len(pth)):
        t = pth[k][0]
        if t > z: break
        if pull is not None and pull <= t: return 0.0, "pull"
        if pth[k][3] >= 0.01 and val(k) <= stop: return s["exit_at"](t), "stop"
    if pull is not None and pull <= z: return 0.0, "pull"
    return val(state(pth, z)), "time"
def up1h(s):
    pth = s["pth"]; a = state(pth, s["end"]); b = state(pth, s["end"] - 3600)
    return b is not None and pth[a][1] > pth[b][1]
def run(usd, M, B, hold):
    R = []; X = []
    alive = {p["pool"] for p, s in ALIVE}
    for p, s in SET[usd]:
        x = s["base"]
        if p["pool"] in alive and s["base"] >= M and NB60.get(p["pool"], -1) >= B and up1h(s) and COV[p["pool"]] >= 12 * 3600 + 600:
            xe, why = ext(s, hold); X.append((p, x, xe, why)); x = xe
        R.append((p, x, usd))
    return R, X
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"])
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 4 h | H1 %s | H2 %s" % (usd, fmt(OUT[(usd, "base")][0]), fmt(OUT[(usd, "base")][1])), flush=True)
    for hold in (8 * 3600, 12 * 3600):
        for M in (2, 3, 5):
            for B in (50, 150, 300):
                R, X = run(usd, M, B, hold); a, b = S(R, D1), S(R, D2); OUT[(usd, hold, M, B)] = (a, b, X)
                n1 = sum(1 for e in X if day(e[0]["t"]) in D1); n2 = len(X) - n1
                print("hold %2dh M%d B%3d | H1 %s ext %d | H2 %s ext %d" % (hold // 3600, M, B, fmt(a), n1, fmt(b), n2), 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[2]))
print("\nH1 PICK ($100): hold %dh M%d B%d  H1 $%+.0f vs base $%+.0f" % (pick[1] // 3600, pick[2], pick[3], OUT[pick][0]["usd"], OUT[(100, "base")][0]["usd"]))
for usd in (100, 1000):
    a, b, X = OUT[(usd,) + pick[1:]]; bb = OUT[(usd, "base")][1]
    ok = b["usd"] > bb["usd"] and b["w3"] >= bb["w3"] and b["top2"] > bb["top2"]
    print(" $%d H2 pick %s\n $%d H2 base %s\n  -> %s" % (usd, fmt(b), usd, fmt(bb), "PASS" if ok else "FAIL"))
print("\nextended coins under the pick ($100): pool | day | 4 h x | extended x | exit | added $")
for p, x4, xe, why in OUT[pick][2]:
    print("  %s | %s | %.2fx | %.2fx | %s | %+.0f" % (p["pool"], day(p["t"]), x4, xe, why, (xe - x4) * 100))
print("\nall open-at-4h x>=2 candidates: pool | day | 4 h x | new buyers last 60 min | price up vs 1 h ago | 8 h x | 12 h x | last swap in data (h after first swap)")
for p, s in ALIVE:
    e8 = ext(s, 8 * 3600); e12 = ext(s, 12 * 3600)
    print("  %s | %s | %.2fx | %s | %s | %.2fx %s | %.2fx %s | %.1f" % (p["pool"], day(p["t"]), s["base"], NB60.get(p["pool"], "?"), up1h(s), e8[0], e8[1], e12[0], e12[1], s["pth"][-1][0] / 3600))
