#!/usr/bin/env python3
"""Backtest mirato: solo strategie ATTIVE (VPTR3, RETTANGOLO, MA_TRAILING).
Verifica che la regola (pre-trailing 1% + trailing 1.5% lista 5 step) sia applicata."""
import sys, os, time, json
sys.path.insert(0, '/opt/charter-live/live_deploy_v2')
os.chdir('/opt/charter-live/live_deploy_v2')
from bybit_demo_client import BybitDemoClient

c = BybitDemoClient()
r = c._request('GET', '/v5/position/closed-pnl', {'category': 'linear', 'settleCoin': 'USDT', 'limit': '200'}, signed=True)
trades = r.get('result', {}).get('list', [])
r2 = c._request('GET', '/v5/order/history', {'category': 'linear', 'limit': '200'}, signed=True)
orders = r2.get('result', {}).get('list', [])

# Strategie ATTIVE (regola Mattia 14/08: PEPE non esiste più, NEAR/BONK/ZEC sono SQW libere,
# WIF/AEO sono RSI-SWING libere, AR è RETTANGOLO_SIMPLE libera, RENDER e VIRTUAL sono
# Pine source VPTR3/RETTANGOLO del periodo in cui erano enabled, quindi vanno inclusi
# nel backtest anche se ora sono disabled/blocked - il trade SOURCE Pine conta)
ACTIVE = {
    'vptr3': ['BTCUSDT', 'AXSUSDT', 'SIRENUSDT', 'VIRTUALUSDT'],  # VIRTUAL: era VPTR3 2H enabled fino 10/08
    'rettangolo': ['UNIUSDT', 'RENDERUSDT'],  # RENDER: era RETTANGOLO 5m enabled, ora blocked ma trade del periodo enabled sono RETTANGOLO
    'ma_trailing': ['BEATUSDT'],
}

# Esplicito: simboli ESCLUSI dal trailing (strategie SOURCE Pine DIVERSE dalle 3 attive)
EXCLUDED_SYMBOLS = {
    'WIFUSDT', 'AEOUSDT',          # RSI-SWING
    'ARUSDT',                       # RETTANGOLO_SIMPLE
    'NEARUSDT', '1000BONKUSDT', 'ZECUSDT',  # SQW (ZEC SQW per Mattia anche se CSV dice VPTR3)
    'DASHUSDT', 'SOLUSDT', 'ETHUSDT',  # disabled vari senza trade rilevanti
}

def find_open_ts(tr, orders):
    sym, side, entry = tr['symbol'], tr['side'], float(tr['avgEntryPrice'])
    for o in orders:
        if o['symbol'] != sym: continue
        if o['side'] != side: continue
        try: op = float(o.get('avgPrice', 0))
        except: continue
        if abs(op - entry) / max(entry, 1e-9) < 0.0005:
            return int(o.get('createdTime', 0))
    return None

def get_klines_1m(symbol, start_ms, end_ms):
    klines = []
    cursor = start_ms
    while cursor < end_ms:
        params = {
            'category': 'linear', 'symbol': symbol, 'interval': '1',
            'start': cursor, 'end': min(end_ms, cursor + 1000 * 60 * 1000),
            'limit': '1000',
        }
        try:
            r = c._request('GET', '/v5/market/kline', params, signed=False)
            lst = r.get('result', {}).get('list', [])
            if not lst: break
            for k in lst:
                klines.append({'ts': int(k[0]), 'o': float(k[1]), 'h': float(k[2]),
                               'l': float(k[3]), 'c': float(k[4])})
            klines.sort(key=lambda x: x['ts'])
            if len(lst) < 1000: break
            cursor = klines[-1]['ts'] + 60 * 1000
            time.sleep(0.05)
        except: break
    return klines

# Regola: pre-trailing 1% + trailing 1.5% lista 5 step
PRE = 0.01
TRG = 0.015
STEP = [(0.005, 0.000), (0.010, 0.005), (0.020, 0.010), (0.030, 0.020), (0.050, 0.030)]
STEP_MIN = 0.002

def simulate(tr, klines):
    sym = tr['symbol']; side = tr['side']; qty = float(tr['qty'])
    entry = float(tr['avgEntryPrice']); exit_price = float(tr['avgExitPrice'])
    pnl_real = float(tr['closedPnl'])
    if entry <= 0 or not klines: return None
    pre = False; trail = False; max_p = 0.0; sl_p = None
    for k in klines:
        if side == 'Buy':
            cur = (k['c']-entry)/entry; hi = (k['h']-entry)/entry; lo = (k['l']-entry)/entry
        else:
            cur = (entry-k['c'])/entry; hi = (entry-k['l'])/entry; lo = (entry-k['h'])/entry
        if hi > max_p: max_p = hi
        if not pre and max_p >= PRE:
            pre = True
            if sl_p is None or sl_p < 0.0: sl_p = 0.0
        if pre and not trail and max_p >= TRG:
            trail = True
        if trail:
            for st, sl in STEP:
                if max_p >= st:
                    ns = sl
                    if sl_p is None or (ns - sl_p) >= STEP_MIN: sl_p = ns
        if sl_p is not None and lo <= sl_p:
            return (sl_p * qty * entry, pnl_real, sl_p, max_p, 'pre_trail' if sl_p == 0.0 and not trail else 'trail')
    if side == 'Buy': return (((exit_price-entry)/entry)*qty*entry, pnl_real, None, max_p, 'end')
    else: return (((entry-exit_price)/entry)*qty*entry, pnl_real, None, max_p, 'end')

# Filtra solo trade delle strategie attive
active_symbols = set()
for syms in ACTIVE.values():
    active_symbols.update(syms)
print(f"Simboli attivi (con trailing): {sorted(active_symbols)}")
print(f"Simboli esclusi (liberi): {sorted(EXCLUDED_SYMBOLS)}")
print()

results = []
for i, tr in enumerate(trades):
    sym = tr['symbol']
    if sym in EXCLUDED_SYMBOLS:
        continue
    if sym not in active_symbols:
        continue
    open_ts = find_open_ts(tr, orders)
    if open_ts is None:
        open_ts = int(tr.get('updatedTime', 0)) - 3600 * 1000
    close_ts = int(tr.get('updatedTime', 0))
    klines = get_klines_1m(tr['symbol'], open_ts, close_ts)
    res = simulate(tr, klines)
    if res:
        pnl_i, pnl_r, sl_p, max_p, reason = res
        results.append({
            'symbol': tr['symbol'], 'pnl_real': pnl_r, 'pnl_ideale': pnl_i,
            'sl_pnl_pct': sl_p, 'max_pnl_pct': max_p, 'reason': reason,
        })

print(f"\nTrade ATTIVI analizzati: {len(results)}")
total_real = sum(r['pnl_real'] for r in results)
total_ideale = sum(r['pnl_ideale'] for r in results)
print(f"PnL reale: {total_real:+.2f}, ideale: {total_ideale:+.2f}, diff: {total_ideale - total_real:+.2f}")

# Per strategia
by_strat = {}
SYMBOL_STRAT = {s: 'vptr3' for s in ACTIVE['vptr3']}
SYMBOL_STRAT.update({s: 'rettangolo' for s in ACTIVE['rettangolo']})
SYMBOL_STRAT.update({s: 'ma_trailing' for s in ACTIVE['ma_trailing']})
for r in results:
    s = SYMBOL_STRAT.get(r['symbol'], '?')
    if s not in by_strat: by_strat[s] = {'n': 0, 'real': 0, 'ideal': 0, 'pre': 0, 'trail': 0}
    by_strat[s]['n'] += 1
    by_strat[s]['real'] += r['pnl_real']
    by_strat[s]['ideal'] += r['pnl_ideale']
    if r['reason'] in ('pre_trail', 'trail'): by_strat[s]['pre'] += 1
    if r['reason'] == 'trail': by_strat[s]['trail'] += 1

print(f"\n=== Per strategia ===")
for s in sorted(by_strat.keys()):
    i = by_strat[s]
    print(f"  {s:12} n={i['n']:>3} pre={i['pre']:>3} trail={i['trail']:>3} pnl_real={i['real']:+8.2f} pnl_ideale={i['ideal']:+8.2f} diff={i['ideal']-i['real']:+7.2f}")

# Per simbolo
by_sym = {}
for r in results:
    sym = r['symbol']
    if sym not in by_sym: by_sym[sym] = {'n': 0, 'real': 0, 'ideal': 0, 'pre': 0, 'trail': 0}
    by_sym[sym]['n'] += 1
    by_sym[sym]['real'] += r['pnl_real']
    by_sym[sym]['ideal'] += r['pnl_ideale']
    if r['reason'] in ('pre_trail', 'trail'): by_sym[sym]['pre'] += 1
    if r['reason'] == 'trail': by_sym[sym]['trail'] += 1

print(f"\n=== Per simbolo ===")
for sym in sorted(by_sym.keys()):
    i = by_sym[sym]
    print(f"  {sym:14} n={i['n']:>3} pre={i['pre']:>3} trail={i['trail']:>3} pnl_real={i['real']:+8.2f} pnl_ideale={i['ideal']:+8.2f} diff={i['ideal']-i['real']:+7.2f}")
