import json, sys, collections, statistics as st, calendar, time, gzip
sys.path.insert(0, '/tmp/run'); from feat import tier_of
F = json.load(open('/tmp/run/feats3.json'))
GT = json.load(open('/tmp/run/gt_early.json')) if __import__('os').path.exists('/tmp/run/gt_early.json') else {}
from feat import SUP
for r in F:   # fill 7 day peak from GeckoTerminal for old runners
    if r['kind'] == 'runner' and r.get('peak7') is None and GT.get(r['tok']) and r.get('start_ts'):
        s = SUP.get(r['tok']); w = [x for x in GT[r['tok']] if r['start_ts'] - 3600 <= x[0] <= r['start_ts'] + 7 * 86400]
        if s and len(w) >= 2 and min(x[0] for x in w) <= r['start_ts'] + 6 * 3600:
            cl = sorted((x[4] * s for x in w), reverse=True); r['peak7'] = cl[1]; r['peak_src'] = 'gt'
            r['t_hit'] = {str(th): next(((x[0] - r['start_ts']) / 3600 for x in sorted(w) if x[4] * s >= th), None) for th in (5e5, 1e6, 5e6)}
W0 = calendar.timegm((2026, 9, 14, 17, 0, 0))
CR = json.load(gzip.open('/tmp/run/creators.json.gz', 'rt'))
RUNSET = {r['tok'] for r in F if r['kind'] == 'runner' and (r.get('peak7') or 0) >= 1e6}
def grp(r):
    if r['kind'] == 'control2': return '0 base'
    if r['kind'] == 'control': return None
    if r.get('peak7') is None: return None
    t = r['peak7']
    if t < 5e5: return '0b late'
    return '1 .5-1M' if t < 1e6 else '2 1-2M' if t < 2e6 else '3 2-5M' if t < 5e6 else '4 5-20M' if t < 2e7 else '5 20M+'
ev = []
for r in F:
    th = (r.get('t_hit') or {}).get('1000000.0')
    if r['kind'] == 'runner' and th is not None and r.get('start_ts'): ev.append((r['start_ts'] + th * 3600, set(r.get('early5m') or []), r['tok']))
ev.sort(key=lambda e: e[0])
for r in F:
    s = r.get('start_ts') or 0; e5 = set(r.get('early5m') or []); e60 = set(r.get('early60') or [])
    vets = set(); best = 0; nprior = 0
    for t1, ws, tk in ev:
        if t1 >= s: break
        if tk == r['tok']: continue
        nprior += 1; ov = e5 & ws
        if ov: vets |= ov; best = max(best, len(ov))
    r['vets_5m'] = len(vets); r['vets_60s'] = len(vets & e60); r['max_overlap'] = best; r['n_prior_runners'] = nprior
    r['vet_share'] = len(vets) / len(e5) if e5 else 0
    cr = r.get('creator') or ''
    prev = [x for x in CR.get(cr, []) if (x[1] or 1e18) < s - 60]
    r['creator_prev_launches'] = len(prev) if cr in CR else None
    r['creator_prev_runner'] = int(any(x[0] in RUNSET for x in prev)) if cr in CR else None
    r['hour'] = time.gmtime(s + 7 * 3600).tm_hour; r['month'] = time.strftime('%m', time.gmtime(s))
    r['early_quiet'] = int((r.get('buyers_60s') or 0) <= 10)
    r['quote_n'] = {'0x0000000000000000000000000000000000000000': 'ETH', '0x5fc5360d0400a0fd4f2af552add042d716f1d168': 'USDG', '0x0bd7d308f8e1639fab988df18a8011f41eacad73': 'WETH'}.get(r.get('quote'), 'other(stock/meme)')
json.dump(F, open('/tmp/run/feats4.json', 'w'))
G = collections.defaultdict(list)
for r in F:
    g = grp(r)
    if g: G[g].append(r)
keys = sorted(G); print('n:', {k: len(G[k]) for k in keys})
def med(xs):
    xs = [x for x in xs if x is not None]; return st.median(xs) if xs else None
def auc(pos, neg):
    pos = [x for x in pos if x is not None]; neg = [x for x in neg if x is not None]
    if len(pos) < 8 or len(neg) < 8: return None
    allv = sorted([(x, 1) for x in pos] + [(x, 0) for x in neg]); n = len(allv); rk = [0] * n; i = 0
    while i < n:
        j = i
        while j < n and allv[j][0] == allv[i][0]: j += 1
        for k in range(i, j): rk[k] = (i + j + 1) / 2
        i = j
    sp = sum(rk[k] for k in range(n) if allv[k][1]); P, N = len(pos), len(neg)
    return (sp - P * (P + 1) / 2) / (P * N)
runW = [r for k in keys if k[0] in '12345' for r in G[k] if (r.get('start_ts') or 0) >= W0]; base = G['0 base']
big = [r for k in keys if k[0] in '45' for r in G[k]]; small = [r for k in keys if k[0] in '12' for r in G[k]]
print('window runners', len(runW), 'base', len(base), 'big', len(big), 'small', len(small))
num = ['first_buy_delay_s', 'buyers_10s', 'buyers_60s', 'buyers_300s', 'buyers_1800s', 'share_bought_10s', 'share_bought_60s', 'share_bought_300s',
       'blk0_buyers', 'blk0_share', 'sellers_30m', 'sell_share_30m', 'first_sell_s', 'w2w_30m', 'w2w_wallets', 'cv_first20', 'top1_end', 'top10_end', 'holders_end',
       'early_hold_share_end', 'creator_bought', 'creator_share_end', 'mint_to_user_share', 'n_pools', 'vets_5m', 'vets_60s', 'max_overlap', 'vet_share', 'n_prior_runners', 'creator_prev_launches', 'hour']
print('%-22s' % 'median' + ''.join('%9s' % k[:8] for k in keys) + '  runW>base big>small')
for f in num:
    vals = [med([r.get(f) for r in G[k]]) for k in keys]
    a1 = auc([r.get(f) for r in runW], [r.get(f) for r in base]); a2 = auc([r.get(f) for r in big], [r.get(f) for r in small])
    print('%-22s' % f + ''.join('%9s' % (('%.3g' % v) if v is not None else '-') for v in vals) + '   %5s   %5s' % (('%.2f' % a1) if a1 else '-', ('%.2f' % a2) if a2 else '-'))
def cat(name, fn, top=8):
    c = collections.Counter(fn(r) for r in F if grp(r))
    print('\n' + name + ' (share of each group)')
    for v, n in c.most_common(top):
        print('  %-36s' % str(v)[:36] + ''.join('%9s' % ('%.0f%%' % (100 * sum(1 for r in G[k] if fn(r) == v) / len(G[k]))) for k in keys))
cat('quote', lambda r: r['quote_n']); cat('hook', lambda r: (r.get('hook') or '')[:12]); cat('factory', lambda r: (r.get('factory') or '')[:12])
cat('start month', lambda r: r.get('month')); cat('name kind', lambda r: (r.get('namek') or ['none'])[0])
cat('vets>=3', lambda r: r['vets_5m'] >= 3); cat('first buy at first pool', lambda r: 1 - (r.get('fixed') or 0))
cat('quiet first minute (<=10 buyers)', lambda r: r['early_quiet']); cat('creator launched before', lambda r: None if r['creator_prev_launches'] is None else r['creator_prev_launches'] > 0)
cat('blk0 share>5%', lambda r: (r.get('blk0_share') or 0) > .05)
print('\nTOP runners (peak7 >= 20M):')
for r in sorted([r for r in F if r['kind'] == 'runner' and (r.get('peak7') or 0) >= 2e7], key=lambda r: -r['peak7']):
    print('  %-10s %6.0fM %s quote %-6s buyers60 %3s blk0 %.3f top10 %.2f vets %3s quiet %d fixed %d hook %s' % (r.get('sym'), r['peak7'] / 1e6, time.strftime('%d %b', time.gmtime(r['start_ts'])), r['quote_n'], r.get('buyers_60s'), r.get('blk0_share') or 0, r.get('top10_end') or 0, r['vets_5m'], r['early_quiet'], r.get('fixed') or 0, (r.get('hook') or '')[:10]))
