"""Iteration engine round 2 (71 to 120): extended definitions, exits, risk off states, entry ranking."""
import json, math, statistics as S, collections, datetime as dt
import eng
from eng import SERIES, STAGES, KEYS, CHAIN, MAJ_ON, BTC, BMA, SOLBTC, ON_ALL, stats, weekly, SPLIT, gate_btc
V = json.load(open('bk_vol.json'))


def rsi(L, n=14):
    out = [None] * len(L); g = l = 0.0
    for i in range(1, len(L)):
        ch = math.log(L[i] / L[i - 1])
        if i <= n: g += max(ch, 0); l += max(-ch, 0)
        if i == n: g /= n; l /= n
        if i > n: g = (g * (n - 1) + max(ch, 0)) / n; l = (l * (n - 1) + max(-ch, 0)) / n
        if i >= n: out[i] = 100 - 100 / (1 + g / l) if l > 0 else 100
    return out


RSI = {k: rsi(SERIES[k][1]) for k in KEYS}
SMA20 = {k: [None if i < 19 else sum(SERIES[k][1][i - 19:i + 1]) / 20 for i in range(len(SERIES[k][1]))] for k in KEYS}


def extended(k, j, ent_i, rule, x):
    ds, L = SERIES[k]
    if rule == 'hot': return STAGES[k][j][0] == 'HOT'
    if rule == 'ma20': return SMA20[k][j] and L[j] / SMA20[k][j] - 1 >= x
    if rule == 'r30': return j >= 30 and L[j] / L[j - 30] - 1 >= x
    if rule == 'rsi': return RSI[k][j] is not None and RSI[k][j] >= x
    if rule == 'gain': return L[j] / L[ent_i] - 1 >= x
    if rule == 'days': return j - ent_i >= x
    return False


def gates(k, d, breadth=0.25, froth=0.20):
    if not gate_btc(d, '100'): return False
    if k != 'majors' and not MAJ_ON[(30, 15)].get(d): return False
    if froth and CHAIN[k] == 'Solana':
        d0 = (dt.date.fromisoformat(d) - dt.timedelta(28)).isoformat()
        if d in SOLBTC and d0 in SOLBTC and SOLBTC[d] / SOLBTC[d0] - 1 >= froth: return False
    if breadth:
        oth = [kk for kk in KEYS if kk not in (k, 'majors') and d in ON_ALL[kk]]
        if oth and sum(1 for kk in oth if ON_ALL[kk][d]) / len(oth) < breadth: return False
    return True


def run(k, nin=30, nout=15, ext_rule='hot', ext_x=None, ext_action='half', ext2=None, cost=0.01, breadth=0.25, froth=0.20, delay=0):
    """ext2: optional second extended level -> sell the rest."""
    ds, L = SERIES[k]; out = []; i = nin
    while i < len(L) - 1:
        d = ds[i]
        if not (L[i] > max(L[i - nin:i]) and gates(k, d, breadth, froth)): i += 1; continue
        j0 = i + delay
        if j0 >= len(L) - 1: break
        ent = L[j0]; w = 1.0; acc = 0.0; ex = None; trimmed = False
        for j in range(j0 + 1, len(L)):
            if L[j] < min(L[j - nout:j]): ex = j; break
            if not trimmed and ext_rule and extended(k, j, j0, ext_rule, ext_x):
                if ext_action == 'all': ex = j; break
                acc += 0.5 * (L[j] / ent - 1); w = 0.5; trimmed = True
            elif trimmed and ext2 and extended(k, j, j0, ext2[0], ext2[1]): ex = j; break
        if ex is None: break
        f = feats(k, i)
        out.append(dict(k=k, sig=d, entry=ds[j0], exit=ds[ex], ret=acc + w * (L[ex] / ent - 1) - cost, days=ex - j0, **f))
        i = ex + 1
    return out


def feats(k, i):
    ds, L = SERIES[k]; d = ds[i]
    vol = V['vol'].get(k, {}); dd = [ds[x] for x in range(max(0, i - 28), i + 1)]
    v7 = [vol.get(x) for x in dd[-7:]]; v28 = [vol.get(x) for x in dd]
    v7 = [x for x in v7 if x]; v28 = [x for x in v28 if x]
    b7 = BTC.get(d); d7 = (dt.date.fromisoformat(d) - dt.timedelta(7)).isoformat()
    lo = min(L[max(0, i - 90):i + 1]); hi = max(L[max(0, i - 90):i + 1])
    return dict(f_r30=L[i] / L[i - 30] - 1 if i >= 30 else None, f_r7=L[i] / L[i - 7] - 1 if i >= 7 else None,
                f_vol=(S.mean(v7) / S.mean(v28) - 1) if len(v7) >= 5 and len(v28) >= 20 else None,
                f_btc7=(b7 / BTC[d7] - 1) if b7 and d7 in BTC else None, f_up90=L[i] / lo - 1, f_dd90=L[i] / hi - 1,
                f_rsi=RSI[k][i], f_meme=1 if ('meme' in k or k in ('sol_pump', 'sol_bonk')) else 0,
                f_majors=1 if k == 'majors' else 0)


def evaluate(**kw):
    ts = []
    for k in KEYS: ts += run(k, **kw)
    a = [t for t in ts if t['entry'] < SPLIT]; b = [t for t in ts if t['entry'] >= SPLIT]
    return dict(all=stats(ts), A=stats(a), B=stats(b), wk=weekly(ts), wkB=weekly(b), trades=ts)


def line(name, r):
    def f(o): return "n%3d avg%+5.1f med%+5.1f w%3d pf%5.2f" % (o['n'], o['mean'], o['med'], o['win'], o['pf'])
    return "%-44s ALL %s | 23-24 %s | 25-26 %s | wk %.2f/%.2f" % (name[:44], f(r['all']), f(r['A']), f(r['B']), r['wk']['pf'], r['wkB']['pf'])
