"""v3sim.py (FW Analyst 1, A1.13): tradeable test of 'market cap at +5 min $250k to $1M' on v3 WETH coins.
Entry at +5 min (pool state = last swap before 300 s). Depth: in-range liquidity L estimated from real swaps (L = dWETH / d sqrt(price)),
median of the last 8 clean swaps, constant-product on virtual reserves. Fees: pool fee + GMGN 1% each way + gas $0.05 a tx.
Exits: take profit 1.5x / 2x / 3x on net sell value, stop 0.6x / 0.5x, time stop 1 h / 2 h. Latency: fill at the pool state 3 s after trigger.
Pulled = 0: a Burn taking 50%+ of WETH added so far before our exit sets the trade to 0. Halves split at 25 Sep 00:00 Bangkok."""
import sqlite3, pickle, collections, statistics as st, math, sys, json
SPB = 0.101283; CUT = 1790269200
D = sqlite3.connect("file:/root/fwa1/v3/v3a1.db?mode=ro", uri=True)
P, W = pickle.load(open("/root/fwa1/v3/an.pkl", "rb"))
meta = {r[0]: r for r in D.execute("SELECT pool, token, is0, fee, cb, supply, dec FROM meta")}
lpdone = {r[0] for r in D.execute("SELECT pool FROM lpdone")}
def s256(h):
    v = int(h, 16); return v - (1 << 256) if v >= 1 << 255 else v
def words(d): d = d[2:]; return [int(d[i:i + 64], 16) for i in range(0, len(d), 64)]
def path(p):
    """list of (s, sqrtp_human, L_est or None, weth_of_swap, pool_weth_reserve) and pull time (s) or None"""
    pool, tok, is0, fee, cb, sup, dec = meta[p["pool"]]; fb = p["fb"]
    ev = []
    for b, li, a0, a1, sq in D.execute("SELECT b, li, a0, a1, sq FROM sw WHERE pool=?", (pool,)): ev.append((b, li, "s", (a0, a1, sq)))
    for b, li, kind, data in D.execute("SELECT b, li, kind, data FROM lp WHERE pool=?", (pool,)): ev.append((b, li, kind, data))
    ev.sort(key=lambda e: (e[0], e[1]))
    out = []; prev = None; res = 0.0; added = 0.0; pull = None
    for b, li, kind, d in ev:
        if kind != "s":
            w = words(d); amt = (w[3] if is0 else w[2]) / 1e18 if kind == "m" else (w[2] if is0 else w[1]) / 1e18
            liq = w[1] if kind == "m" else w[0]          # liquidity units (single-sided launches add 0 WETH)
            if kind == "m": added += liq; res += amt
            else:
                res -= amt
                if added > 0 and liq >= 0.5 * added and pull is None: pull = (b - fb) * SPB
            continue
        a0, a1, sq = d
        Pr = (int(sq, 16) / 2 ** 96) ** 2
        sw_we = (s256(a1) if is0 else s256(a0)) / 1e18; res += sw_we
        if Pr <= 0: continue
        px = (Pr if is0 else 1 / Pr) * 10 ** dec / 1e18
        we = abs(sw_we); r = math.sqrt(px); L = None
        if prev is not None and we >= 0.001 and abs(r - prev) / prev > 1e-5: L = we / abs(r - prev)
        out.append(((b - fb) * SPB, r, L, we, max(res, 0.0))); prev = r
    return out, pull, fee / 1e6, sup
def Lat(pth, i):
    Ls = [x[2] for x in pth[max(0, i - 40):i + 1] if x[2]][-8:]
    return st.median(Ls) if Ls else None
def state(pth, t):
    i = max((k for k, x in enumerate(pth) if x[0] <= t), default=None); return i
def buy(r, L, x):      # x ETH in (after fees) -> tokens out; virtual reserves W = L r, T = L / r
    Wv, Tv = L * r, L / r; return Tv - Wv * Tv / (Wv + x)
def sell(r, L, q):
    Wv, Tv = L * r, L / r; return Wv - Wv * Tv / (Tv + q)
def trade(p, usd, tp, sl, tstop, lfac=1.0):
    pth, pull, pf, sup = CACHE[p["pool"]]
    i = state(pth, 300)
    if i is None: return None
    L = Lat(pth, i)
    if not L: return None
    eu = p["eu"]; gas = 0.05 / eu; x = usd / eu
    q = buy(pth[i][1], L * lfac, x * (1 - pf) * 0.99 - gas)
    cost = x
    js = [k for k, y in enumerate(pth) if 300 < y[0] <= tstop]
    for k in js:
        t = pth[k][0]
        if pull is not None and pull <= t: return dict(v=0.0, why="pull", t=pull)
        Lk = Lat(pth, k)
        if not Lk: continue
        v = sell(pth[k][1], Lk * lfac, q) * (1 - pf) * 0.99 - gas
        m = v / cost
        if (tp and m >= tp) or (sl and m <= sl):
            j = state(pth, t + 3); Lj = Lat(pth, j) or Lk
            if pull is not None and pull <= t + 3: return dict(v=0.0, why="pull", t=pull)
            v2 = sell(pth[j][1], Lj * lfac, q) * (1 - pf) * 0.99 - gas
            return dict(v=v2 / cost, why="tp" if m >= (tp or 9e9) else "sl", t=t)
    if pull is not None and pull <= tstop: return dict(v=0.0, why="pull", t=pull)
    j = state(pth, tstop); Lj = Lat(pth, j)
    v = sell(pth[j][1], Lj * lfac, q) * (1 - pf) * 0.99 - gas if Lj else 0
    return dict(v=max(v, 0) / cost, why="time", t=tstop)
CACHE = {}
pools = [p for p in P if p["pool"] in lpdone and p["feats"][300]["mc"]]
for p in pools: CACHE[p["pool"]] = path(p)
print("pools with lp data", len(pools), flush=True)
pickle.dump((pools, CACHE), open("/root/fwa1/v3/simcache.pkl", "wb"))
