#!/usr/bin/env python3
"""
Backtest personalizzato per RSI-SWING, RETTANGOLO_SIMPLE, SQW.
Prova una griglia di parametri (pre_trailing, trigger, step_list)
e mostra quale combinazione produce il miglior PnL per ogni strategia.
"""
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()

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

# Get orders
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:
            break
    return klines


# Strategie target
STRATEGIES = {
    'rsi_swing': ['WIFUSDT', 'AEROUSDT'],
    'rettangolo_simple': ['ARUSDT'],
    'sqw': ['1000BONKUSDT', 'NEARUSDT'],  # SQW includes NEAR/ZEC/BONK
}


def simulate(tr, klines, pre_trailing_pct, trigger_pct, step_list, step_min_pct=0.002):
    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

    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
        if pre_trailing_pct > 0 and 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
        if not trailing_active and max_pnl_pct >= trigger_pct:
            trailing_active = True

        # 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

        # EXIT
        if sl_pnl_pct is not None and cur_low_pct <= sl_pnl_pct:
            return (sl_pnl_pct * qty * entry, pnl_real)

    # se nessun exit, usa l'exit reale
    if side == 'Buy':
        return ((exit_price - entry) / entry * qty * entry, pnl_real)
    else:
        return ((entry - exit_price) / entry * qty * entry, pnl_real)


# === GRIGLIA PARAMETRI ===
# step_list options: lista di step (profit_threshold, lock_in)
STEP_LISTS = {
    'aggressive_5step': [(0.005, 0.000), (0.010, 0.005), (0.020, 0.010), (0.030, 0.020), (0.050, 0.030)],  # attuale
    'linear_1pct': [(0.01, 0.000), (0.02, 0.01), (0.03, 0.02), (0.05, 0.04), (0.10, 0.08)],  # step 1% lock
    'tighter_5step': [(0.005, 0.002), (0.010, 0.007), (0.020, 0.015), (0.030, 0.025), (0.050, 0.045)],  # lock più alto
    'no_step_just_breakeven': None,  # solo pre-trailing
    'wide_3step': [(0.02, 0.005), (0.04, 0.020), (0.08, 0.060)],  # pochi step larghi
}

# pre_trailing options
PRE_TRAILING_OPTIONS = [0.0, 0.01, 0.02, 0.03, 0.04, 0.05]

# trigger options
TRIGGER_OPTIONS = [0.015, 0.02, 0.025, 0.03, 0.04, 0.05, 0.06, 0.08, 0.10]


# Pre-carica klines per ogni trade
print("=== Caricamento klines per ogni trade ===", file=sys.stderr)
trade_klines = {}
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
    close_ts = int(tr.get('updatedTime', 0))
    klines = get_klines_1m(tr['symbol'], open_ts, close_ts)
    trade_klines[tr.get('orderId') or f"{tr['symbol']}_{tr.get('updatedTime')}"] = klines
    if (i+1) % 10 == 0:
        print(f"  [{i+1}/{len(trades)}] candele caricate", file=sys.stderr)

print(f"  Fatto. {len(trade_klines)} trade caricati.", file=sys.stderr)


# Per ogni strategia, fai grid search
def get_strategy_trades(strategy_name):
    """Ritorna i trade per una strategia."""
    target_symbols = STRATEGIES.get(strategy_name, [])
    return [t for t in trades if t['symbol'] in target_symbols]


print("\n=== GRID SEARCH PER STRATEGIA ===\n")

for strat_name in STRATEGIES:
    strat_trades = get_strategy_trades(strat_name)
    if not strat_trades:
        continue
    real_pnl = sum(float(t['closedPnl']) for t in strat_trades)
    print(f"--- {strat_name} ({len(strat_trades)} trade) | PnL reale: {real_pnl:+.2f} ---")
    
    # Fai grid search (limitata per non esplodere)
    best = {'pnl': -1e9, 'params': None}
    results = []
    for pre in PRE_TRAILING_OPTIONS:
        for tr_pct in TRIGGER_OPTIONS:
            for sl_name, sl in STEP_LISTS.items():
                if pre > tr_pct:
                    continue  # pre deve essere <= trigger
                pnl_ideale = 0
                for tr in strat_trades:
                    key = tr.get('orderId') or f"{tr['symbol']}_{tr.get('updatedTime')}"
                    klines = trade_klines.get(key, [])
                    res = simulate(tr, klines, pre, tr_pct, sl)
                    if res:
                        pnl_ideale += res[0]
                diff = pnl_ideale - real_pnl
                results.append((pre, tr_pct, sl_name, pnl_ideale, diff))
                if diff > best['pnl']:
                    best = {'pnl': diff, 'params': (pre, tr_pct, sl_name, pnl_ideale)}
    
    print(f"  BEST: pre={best['params'][0]:.3f} trigger={best['params'][1]:.3f} step={best['params'][2]:20} pnl_ideale={best['params'][3]:+.2f} diff={best['pnl']:+.2f}")
    print()
    
    # Top 10 risultati
    print(f"  Top 10 combinazioni per {strat_name}:")
    results.sort(key=lambda r: r[4], reverse=True)
    for pre, tr_pct, sl_name, pnl, diff in results[:10]:
        print(f"    pre={pre:.3f} trigger={tr_pct:.3f} step={sl_name:20} pnl_ideale={pnl:+.2f} diff={diff:+.2f}")
    print()
    
    # Worst 5
    print(f"  Worst 5 combinazioni per {strat_name}:")
    for pre, tr_pct, sl_name, pnl, diff in results[-5:]:
        print(f"    pre={pre:.3f} trigger={tr_pct:.3f} step={sl_name:20} pnl_ideale={pnl:+.2f} diff={diff:+.2f}")
    print()
    print("="*80)
    print()
