import json, sys, collections, statistics as st, time
sys.path.insert(0, '/tmp/run'); from feat import KL, SUP
F = json.load(open('/tmp/run/feats4.json')); GT = json.load(open('/tmp/run/gt_early.json')) if __import__('os').path.exists('/tmp/run/gt_early.json') else {}
R = [r for r in F if r['kind'] == 'runner']
# chain heat: number of runners whose first $1M close happened in the 72 h before
def series(r):
    k = KL.get(r['tok']) or []; s = SUP.get(r['tok'])
    g = GT.get(r['tok']) or []
    m = {x[0]: x for x in k}
    for x in g:
        if x[0] not in m: m[x[0]] = x
    return sorted(m.values()), s
rows = []; firsts = []
for r in R:
    k, s = series(r)
    if not k or not s: continue
    mc = [(x[0], x[4] * s, x[5] or 0) for x in k]
    i1 = next((i for i, x in enumerate(mc) if x[1] >= 1e6), None)
    if i1 is None: continue
    T1 = mc[i1][0]
    if r.get('start_ts') and mc[0][0] > r['start_ts'] + 6 * 3600 and T1 - mc[0][0] < 3600: continue   # series starts above 1M: crossing not observed
    firsts.append(T1)
    prev = mc[max(0, i1 - 24):i1 + 1]; after = [x for x in mc if T1 < x[0] <= T1 + 7 * 86400]
    if len(after) < 24: continue   # need a week of follow up (or most of it)
    peak_after = sorted((x[1] for x in after), reverse=True)[1] if len(after) > 1 else after[0][1]
    hi_prev = max(x[1] for x in prev); lo_prev = min(x[1] for x in prev)
    rows.append(dict(tok=r['tok'], sym=r.get('sym'), T1=T1, age_h=((T1 - r['start_ts']) / 3600) if r.get('start_ts') else None,
                     ret24=mc[i1][1] / prev[0][1] if prev[0][1] else None, dd24=lo_prev / hi_prev, vol24_mc=sum(x[2] for x in prev) / mc[i1][1], volh_mc=mc[i1][2] / mc[i1][1],
                     hrs_above_500k=sum(1 for x in mc[:i1] if x[1] >= 5e5), quote=r.get('quote_n'), namek=(r.get('namek') or ['none'])[0], vets=r.get('vets_5m'), month=time.strftime('%m', time.gmtime(T1)),
                     buyers_1800s=r.get('buyers_1800s'), top10=r.get('top10_end'), peak_after=peak_after, x2=peak_after >= 2e6, x5=peak_after >= 5e6, x20=peak_after >= 2e7,
                     gt_any=bool(GT.get(r['tok']))))
firsts.sort()
import bisect
for x in rows:
    x['heat72'] = bisect.bisect_left(firsts, x['T1']) - bisect.bisect_left(firsts, x['T1'] - 3 * 86400)
json.dump(rows, open('/tmp/run/ladder.json', 'w'))
n = len(rows); print('coins observed crossing $1M with a week after:', n, ' reach 2M %.0f%%  5M %.0f%%  20M %.0f%%' % (100 * sum(r['x2'] for r in rows) / n, 100 * sum(r['x5'] for r in rows) / n, 100 * sum(r['x20'] for r in rows) / n))
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) < 5 or len(neg) < 5: return None
    allv = sorted([(x, 1) for x in pos] + [(x, 0) for x in neg]); m = len(allv); rk = [0] * m; i = 0
    while i < m:
        j = i
        while j < m 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(m) if allv[k][1]); P, N = len(pos), len(neg)
    return (sp - P * (P + 1) / 2) / (P * N)
rows.sort(key=lambda r: r['T1']); h1, h2 = rows[:n // 2], rows[n // 2:]
for tgt in ('x5', 'x20'):
    print('\ntarget', tgt, '(AUC all / first half / second half; >0.5 = higher value goes with reaching it)')
    for f in ('age_h', 'ret24', 'dd24', 'vol24_mc', 'volh_mc', 'hrs_above_500k', 'heat72', 'vets', 'buyers_1800s', 'top10'):
        a = [auc([r[f] for r in S if r[tgt]], [r[f] for r in S if not r[tgt]]) for S in (rows, h1, h2)]
        print('  %-16s %s' % (f, '  '.join(('%.2f' % x) if x else '  - ' for x in a)))
def cat(name, fn):
    c = collections.defaultdict(lambda: [0, 0, 0])
    for r in rows: v = fn(r); c[v][0] += 1; c[v][1] += r['x5']; c[v][2] += r['x20']
    print('\n' + name + '  (n, reach 5M, reach 20M)')
    for v, (a, b, d) in sorted(c.items(), key=lambda kv: -kv[1][0])[:8]: print('  %-22s %4d  %5.0f%%  %5.0f%%' % (str(v)[:22], a, 100 * b / a, 100 * d / a))
cat('quote', lambda r: r['quote']); cat('name kind', lambda r: r['namek']); cat('month of $1M', lambda r: r['month'])
cat('age at $1M', lambda r: '<1h' if (r['age_h'] or 0) < 1 else '<6h' if r['age_h'] < 6 else '<24h' if r['age_h'] < 24 else '<72h' if r['age_h'] < 72 else '3d+')
cat('heat72 (runners hitting 1M in prior 3d)', lambda r: '<10' if r['heat72'] < 10 else '<25' if r['heat72'] < 25 else '<50' if r['heat72'] < 50 else '50+')
cat('vets at launch', lambda r: 'none' if r['vets'] is None else '0' if r['vets'] == 0 else '1-9' if r['vets'] < 10 else '10-49' if r['vets'] < 50 else '50+')
cat('turnover 24h (vol/mcap)', lambda r: '<0.5' if r['vol24_mc'] < .5 else '<1' if r['vol24_mc'] < 1 else '<2' if r['vol24_mc'] < 2 else '2+')
cat('drawdown in prior 24h (low/high)', lambda r: '<0.3' if r['dd24'] < .3 else '<0.5' if r['dd24'] < .5 else '<0.7' if r['dd24'] < .7 else '0.7+')
