#!/usr/bin/env python3
"""
Backtest V2 completo - regola trailing unificata CON PRE-TRAILING.
Gira sul VPS perché li' c'e' bybit_demo_client.py con firma corretta.
Regola:
  - PRE-TRAILING a +1% PnL → SL a breakeven
  - TRIGGER a +1.5% PnL → lista 5 step (high water mark)
Applicato a TUTTI i trade chiusi 2° account.
"""
import sys, os, time, json, re
sys.path.insert(0, '/opt/charter-live/live_deploy_v2')
os.chdir('/opt/charter-live/live_deploy_v2')
from bybit_demo_client import BybitDemoClient

# === REGOLA TRAILING UNIFICATA ===
PRE_TRAILING_PCT = 0.01  # +1%
TRIGGER_PCT = 0.015       # +1.5%
STEP_LIST = [
    (0.005, 0.000),  # +0.5% → lock breakeven
    (0.010, 0.005),  # +1.0% → lock +0.5%
    (0.020, 0.010),  # +2.0% → lock +1.0%
    (0.030, 0.020),  # +3.0% → lock +2.0%
    (0.050, 0.030),  # +5.0% → lock +3.0%
]
STEP_MIN_PCT = 0.002

c = BybitDemoClient()

# Get closed-pnl (no cursor, solo prima pagina 200)
r = c._request('GET', '/v5/position/closed-pnl', {'category': 'linear', 'settleCoin': 'USDT', 'limit': '200'}, signed=True)
trades = r.get('result', {}).get('list', [])
print(f"Trades chiusi: {len(trades)}", file=sys.stderr)

# Get orders (no cursor)
r2 = c._request('GET', '/v5/order/history', {'category': 'linear', 'limit': '200'}, signed=True)
orders = r2.get('result', {}).get('list', [])


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 Exception as e:
            print(f"  ERR klines: {e}", file=sys.stderr)
            break
    return klines


def simulate(tr, klines):
    symbol = 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_trailing_active = False
    trailing_active = False
    max_pnl_pct = 0.0
    sl_pnl_pct = None
    exit_pnl_pct = None
    exit_reason = None

    for k in klines:
        if side == 'Buy':
            cur_pnl_pct = (k['c'] - entry) / entry
            cur_high_pct = (k['h'] - entry) / entry
            cur_low_pct = (k['l'] - entry) / entry
        else:
            cur_pnl_pct = (entry - k['c']) / entry
            cur_high_pct = (entry - k['l']) / entry
            cur_low_pct = (entry - k['h']) / entry

        if cur_high_pct > max_pnl_pct:
            max_pnl_pct = cur_high_pct

        # PRE-TRAILING +1% → SL breakeven
        if not pre_trailing_active and max_pnl_pct >= PRE_TRAILING_PCT:
            pre_trailing_active = True
            if sl_pnl_pct is None or sl_pnl_pct < 0.0:
                sl_pnl_pct = 0.0

        # TRIGGER +1.5% → attiva trailing
        if pre_trailing_active and not trailing_active and max_pnl_pct >= TRIGGER_PCT:
            trailing_active = True

        # muovi SL con step list
        if trailing_active:
            for step_threshold, step_lockin in STEP_LIST:
                if max_pnl_pct >= step_threshold:
                    new_sl_pnl = step_lockin
                    if sl_pnl_pct is None or (new_sl_pnl - sl_pnl_pct) >= STEP_MIN_PCT:
                        sl_pnl_pct = new_sl_pnl

        # check exit
        if sl_pnl_pct is not None and cur_low_pct <= sl_pnl_pct:
            exit_pnl_pct = sl_pnl_pct
            if sl_pnl_pct == 0.0 and not trailing_active:
                exit_reason = 'pre_trailing_breakeven'
            else:
                exit_reason = 'trailing_exit'
            break

    if exit_pnl_pct is None:
        if side == 'Buy':
            final_pnl = (exit_price - entry) / entry
        else:
            final_pnl = (entry - exit_price) / entry
        exit_pnl_pct = final_pnl
        exit_reason = 'end_of_data'

    pnl_ideale = exit_pnl_pct * qty * entry
    return {
        'symbol': symbol, 'pnl_real': pnl_real, 'pnl_ideale': pnl_ideale,
        'pre_trailing_triggered': pre_trailing_active,
        'trailing_triggered': trailing_active,
        'exit_reason': exit_reason,
        'max_pnl_pct': max_pnl_pct,
        'lock_in_pct': sl_pnl_pct or 0,
    }


# === MAIN ===
results = []
not_found = 0
for i, tr in enumerate(trades):
    open_ts = find_open_ts(tr, orders)
    if open_ts is None:
        open_ts = int(tr.get('updatedTime', 0)) - 3600 * 1000
        not_found += 1
    close_ts = int(tr.get('updatedTime', 0))
    duration_min = max(1, (close_ts - open_ts) / 60000)
    klines = get_klines_1m(tr['symbol'], open_ts, close_ts)
    res = simulate(tr, klines)
    if res:
        res['duration_min'] = duration_min
        results.append(res)
    if (i + 1) % 5 == 0:
        print(f"  [{i+1}/{len(trades)}] processed", file=sys.stderr)

print(f'\nTotale: {len(results)} (open_ts stimati: {not_found})')

total_real = sum(r['pnl_real'] for r in results)
total_ideale = sum(r['pnl_ideale'] for r in results)
diff = total_ideale - total_real
n_pre = sum(1 for r in results if r['pre_trailing_triggered'])
n_trail = sum(1 for r in results if r['trailing_triggered'])
n_pre_only = sum(1 for r in results if r['pre_trailing_triggered'] and not r['trailing_triggered'])

print(f'\n=== RISULTATI BACKTEST V2 (regola completa) ===')
print(f"Trade analizzati:                   {len(results)}")
print(f"Trade con pre-trailing (>=1%):      {n_pre}")
print(f"  di cui SOLO pre-trailing:         {n_pre_only}")
print(f"Trade con trailing vero (>=1.5%):  {n_trail}")
print()
print(f"PnL reale totale:       {total_real:+.2f} USDT")
print(f"PnL ideale (regola V2): {total_ideale:+.2f} USDT")
print(f"Differenza:             {diff:+.2f} USDT")
print(f"Verdetto: {'PEGGO' if diff < 0 else 'MEGLIO'} con la regola completa")

SYMBOL_STRAT = {
    'ARUSDT': 'rettangolo_simple', 'WIFUSDT': 'rsi_swing', 'AEROUSDT': 'rsi_swing',
    'VIRTUALUSDT': 'vptr3', 'BTCUSDT': 'vptr3', 'ZECUSDT': 'vptr3', 'AXSUSDT': 'vptr3',
    'NEARUSDT': 'ma_trailing', 'BEATUSDT': 'ma_trailing',
    'UNIUSDT': 'rettangolo', 'DASHUSDT': 'vptr3', 'SOLUSDT': 'vptr3',
    '1000BONKUSDT': 'sqw', '1000PEPEUSDT': 'rettangolo', 'SUIUSDT': 'rettangolo',
    'RENDERUSDT': 'rettangolo',
}
by_strat = {}
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['pre_trailing_triggered']: by_strat[s]['pre'] += 1
    if r['trailing_triggered']: by_strat[s]['trail'] += 1

print(f'\n=== Per strategia ===')
for s in sorted(by_strat.keys(), key=lambda k: -by_strat[k]['real']):
    i = by_strat[s]
    d = i['ideal'] - i['real']
    print(f"  {s:25} 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={d:+7.2f}")

early = sorted([r for r in results if r['pnl_ideale'] > r['pnl_real']],
               key=lambda r: r['pnl_ideale'] - r['pnl_real'], reverse=True)
print(f"\n=== TOP 15 trade con EXIT ANTICIPATO (ideal > real) ===")
for r in early[:15]:
    d = r['pnl_ideale'] - r['pnl_real']
    print(f"  {r['symbol']:14} dur={r['duration_min']:>5.0f}min real={r['pnl_real']:+7.2f} ideal={r['pnl_ideale']:+7.2f} reason={r['exit_reason']:30} max_pnl={r['max_pnl_pct']*100:+.2f}% diff={d:+7.2f}")

pre_only = [r for r in results if r['pre_trailing_triggered'] and not r['trailing_triggered']]
print(f"\n=== Trade con SOLO PRE-TRAILING (lock breakeven, no trailing vero): {len(pre_only)} ===")
for r in pre_only[:10]:
    d = r['pnl_ideale'] - r['pnl_real']
    print(f"  {r['symbol']:14} real={r['pnl_real']:+7.2f} ideal={r['pnl_ideale']:+7.2f} max_pnl={r['max_pnl_pct']*100:+.2f}% diff={d:+7.2f}")
