"""v3exit.py (A1.18): exit grid on mc5 entries with 12 h of data. Hard stop 0.5x and pull = 0 always.
Time stop 2/4/8/12 h; trailing stop armed at 2x (sell at f x peak, f 0.5/0.6/0.7); sell half at 3x then trail the rest. Pick on H1, test once on H2."""
import sqlite3, collections, statistics as st, random, numpy as np
exec(open("/root/fwa1/v3frag.py").read().split("random.seed(7)")[0])
E = sqlite3.connect("file:/root/fwa1/v3/v3ext.db?mode=ro", uri=True)
W12 = int(12 * 3600 / SPB)
head = E.execute("SELECT MAX(b) FROM sw").fetchone()[0]
def path12(p):
    pool, tok, is0, fee, cb, sup, dec = meta[p["pool"]]; fb = p["fb"]
    ev = []
    for DB in (D, E):
        for b, li, a0, a1, sq in DB.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 DB.execute("SELECT b, li, kind, data FROM lp WHERE pool=?", (pool,)): ev.append((b, li, kind, data))
    ev = sorted(set(ev), 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]
            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
extdone = {r[0] for r in E.execute("SELECT DISTINCT pool FROM done")}
full = [p for p in pools if p["pool"] in extdone and meta[p["pool"]][4] + W12 <= head]
for p in full: CACHE[p["pool"]] = path12(p)
SC.clear()
S = []
for p in full:
    s = series_t(p, 100, 300)
    if s and 2.5e5 <= s[4] < 1e6: S.append((p, s))
print("trades with full 12 h data:", len(S), "H1", sum(p["t"] < CUT for p, s in S), "H2", sum(p["t"] >= CUT for p, s in S), flush=True)
def run(s, tstop, trail=None, arm=2.0, half=None):
    T, M, pull, m0, mc = s
    ok = T <= tstop; T, M = T[ok], M[ok]
    def fill(k):
        j = max(k, int(np.searchsorted(T, T[k] + 3, "right")) - 1); return float(M[j]), T[j]
    got = 0.0; left = 1.0; peak = 0.0; armed = False
    for k in range(len(M)):
        t, m = T[k], M[k]
        if pull is not None and pull <= t: return got + 0.0
        if m <= 0.5 * left / left and left == 1.0 and m <= 0.5:          # hard stop on full position
            v, tt = fill(k); return got + left * v
        if half and left == 1.0 and m >= half:
            v, tt = fill(k); got += 0.5 * v; left = 0.5; armed = True; peak = max(peak, m); continue
        peak = max(peak, m)
        if trail and m >= arm: armed = True
        if trail and armed and m <= trail * peak:
            v, tt = fill(k); return got + left * v
        if left < 1.0 and m <= 0.5 and not trail:
            v, tt = fill(k); return got + left * v
    if pull is not None and pull <= tstop: return got
    return got + left * (float(M[-1]) if len(M) else m0)
H = 3600
GRID = [("time %dh" % (ts // H), dict(tstop=ts)) for ts in (2 * H, 4 * H, 8 * H, 12 * H)]
for f in (0.5, 0.6, 0.7):
    for ts in (4 * H, 8 * H, 12 * H): GRID.append(("trail %.1f from 2x, %dh" % (f, ts // H), dict(tstop=ts, trail=f)))
    for ts in (4 * H, 12 * H): GRID.append(("half at 3x, trail %.1f, %dh" % (f, ts // H), dict(tstop=ts, trail=f, half=3.0)))
def book2(kw, half_):
    return [(p, run(s, **kw)) for p, s in S if (p["t"] < CUT) == (half_ == 1)]
def summ(R, nd):
    v = [x for p, x in R]; byd = collections.defaultdict(float)
    for p, x in R: byd[day(p["t"])] += (x - 1) * 100
    top5 = sum(sorted([(x - 1) * 100 for x in v], reverse=True)[:5]); tot = sum((x - 1) * 100 for x in v)
    return "n%d mean %.2fx med %.2fx w/o top3 %.2fx $%.0f/day green %d/%d top5 share %.0f%%" % (len(v), st.mean(v), st.median(v), st.mean(sorted(v)[:-3]),
        tot / nd, sum(x > 0 for x in byd.values()), nd, 100 * top5 / tot if tot > 0 else -1)
d1 = len({day(p["t"]) for p, s in S if p["t"] < CUT}); d2 = len({day(p["t"]) for p, s in S if p["t"] >= CUT})
print("days H1 %d H2 %d" % (d1, d2))
rows = []
for name, kw in GRID:
    R1 = book2(kw, 1); rows.append((st.mean([x for p, x in R1]), name, kw))
    print("%-30s H1 %s" % (name, summ(R1, d1)), flush=True)
rows.sort(key=lambda r: -r[0]); best = rows[0]
print("\nH1 PICK:", best[1])
for name, kw in [(best[1], best[2]), ("time 2h (base)", dict(tstop=2 * H))]:
    print("  %-28s H2 %s" % (name, summ(book2(kw, 2), d2)))
print("\nall exits on H2 (context only, not used to pick):")
for name, kw in GRID: print("  %-30s H2 %s" % (name, summ(book2(kw, 2), d2)))
random.seed(7)
for name, kw in [(best[1], best[2]), ("time 2h (base)", dict(tstop=2 * H))]:
    for h, nd in ((2, d2), (0, d1 + d2)):
        R = [(p, run(s, **kw)) for p, s in S if h == 0 or p["t"] >= CUT]
        byd = collections.defaultdict(lambda: [0.0, 0])
        for p, x in R: byd[day(p["t"])][0] += x - 1; byd[day(p["t"])][1] += 1
        dd = sorted(byd); res = []
        for _ in range(1000):
            smp = [random.choice(dd) for _ in dd]; c = sum(byd[d][1] for d in smp)
            res.append(1 + sum(byd[d][0] for d in smp) / c)
        res.sort()
        allday = sorted(byd); pn = [byd[d][0] * 100 for d in allday]
        w3 = min(sum(pn[i:i + 3]) for i in range(len(pn) - 2))
        print("  %-28s %s bootstrap under 1.0x %.1f%% 5th %.2fx median %.2fx, worst 3 trading days $%.0f, worst day $%.0f" % (name, "H2" if h else "all", sum(x < 1 for x in res) / 10, res[50], res[500], w3, min(pn)))
