#!/usr/bin/env python3
"""
理想收益率引擎: 无限子弹, 所有信号都执行, 每笔交易收益率连乘
ideal_ret = prod(1+ret_net_i) - 1
"""
import pandas as pd
import numpy as np
import json, os, sys, time
sys.path.insert(0, '/tmp')
import grid_or as g

def ideal_backtest(pool, members, dailies, sig, profit, dd, start=g.START_DATE, mode='merge', stop=None, hold=g.MAX_HOLD):
    """无限子弹版: 不限制仓位, 所有信号都买入, 每笔独立卖出, 返回trades"""
    all_dates = sorted(set().union(*[set(d['trade_date']) for d in dailies.values()]))
    all_dates = [x for x in all_dates if x >= start]
    date_set = set(all_dates)
    sig_by_date = {}
    for code, bs in sig.items():
        for (bd, t0d) in bs:
            if bd in date_set:
                sig_by_date.setdefault(bd, []).append((code, t0d))
    positions = []  # 每笔: {code, buy_date, buy_price, high_since, hold_n}
    trades = []
    for date in all_dates:
        active = active_members(members, date)
        for pos in positions[:]:
            d = dailies[pos['code']]
            rows = d[d['trade_date'] == date]
            if len(rows) == 0:
                continue
            r = rows.iloc[0]
            cur = float(r['close'])
            pos['hold_n'] += 1
            sell = None
            if mode == 'A':
                if cur / pos['buy_price'] - 1 >= profit:
                    sell = 'profit'
                elif float(r['low']) <= pos['buy_price'] * (1 - (stop if stop is not None else dd)):
                    sell = 'stop'
                elif pos['hold_n'] >= hold:
                    sell = 'timeout'
            elif mode == 'B':
                if float(r['low']) <= pos['high_since'] * (1 - profit):
                    sell = 'trail'
                elif float(r['low']) <= pos['buy_price'] * (1 - (stop if stop is not None else dd)):
                    sell = 'stop'
                elif pos['hold_n'] >= hold:
                    sell = 'timeout'
            elif mode == 'C':
                if float(r['low']) <= pos['high_since'] * (1 - dd):
                    sell = 'dd'
                elif stop is not None and float(r['low']) <= pos['buy_price'] * (1 - stop):
                    sell = 'stop'
                elif cur / pos['buy_price'] - 1 >= profit:
                    sell = 'profit'
                elif pos['hold_n'] >= hold:
                    sell = 'timeout'
            else:
                if float(r['low']) <= pos['high_since'] * (1 - dd):
                    sell = 'dd'
                elif cur / pos['buy_price'] - 1 >= profit:
                    sell = 'profit'
                elif pos['hold_n'] >= hold:
                    sell = 'timeout'
            pos['high_since'] = max(pos['high_since'], cur)
            if sell:
                net_ret = (cur / pos['buy_price'] - 1) - (g.COST_BUY + g.COST_SELL + 2 * g.SLIP)
                trades.append({'code': pos['code'], 'buy_date': pos['buy_date'], 'sell_date': date,
                               'buy_price': pos['buy_price'], 'sell_price': cur,
                               'ret_net': net_ret, 'hold_days': pos['hold_n'], 'reason': sell})
                positions.remove(pos)
        # 买入: 所有信号都执行(无限子弹), 但同一股票同持不重复开仓
        cands = sorted(sig_by_date.get(date, []), key=lambda x: x[1])
        held_codes = {p['code'] for p in positions}
        for code, t0d in cands:
            if code not in active:
                continue
            if code in held_codes:
                continue
            d = dailies[code]
            rows = d[d['trade_date'] == date]
            if len(rows) == 0:
                continue
            buy_px = float(rows.iloc[0]['open'])
            if buy_px <= 0:
                continue
            positions.append({'code': code, 'buy_date': date, 'buy_price': buy_px * (1 + g.SLIP),
                              'high_since': buy_px, 'hold_n': 0})
    return trades

def active_members(members, date_str):
    ym = date_str[:6]
    best = None
    for m in sorted(members.keys()):
        if m[:6] <= ym:
            best = m
    return members.get(best, set()) if best else set()

def ideal_ret(trades):
    """理想收益率 = prod(1+ret_net) - 1"""
    if not trades:
        return 0.0
    r = 1.0
    for t in trades:
        r *= (1 + t['ret_net'])
    return r - 1.0

def full_metrics(trades):
    rets = [t['ret_net'] for t in trades]
    n = len(rets)
    if n == 0:
        return {'trades': 0, 'total_ret': 0, 'win_rate': 0, 'avg_ret': 0, 'pl_ratio': 0}
    wins = [x for x in rets if x > 0]
    losses = [x for x in rets if x <= 0]
    pl = np.mean(wins) / abs(np.mean(losses)) if losses and np.mean(losses) != 0 else float('inf')
    return {'trades': n, 'total_ret': ideal_ret(trades), 'win_rate': len(wins) / n,
            'avg_ret': float(np.mean(rets)), 'pl_ratio': float(pl)}

if __name__ == '__main__':
    # 验证: 四池定稿参数 理想收益率 vs 实盘
    import verify_or as vf
    COMBOS = {
        'hs300': {'bp': {'vol_base': 'maN', 'ma_n': 2, 'vol_shrink': 0.6, 'shrink_win': 5, 'break_up': 15, 'break_strength': 0.0, 'zt_vol_filter': 7.0}, 'profit': 0.20, 'dd': 0.15, 'stop': 0.35, 'hold': 10, 'mode': 'C'},
        'zz500': {'bp': {'vol_base': 'maN', 'ma_n': 12, 'vol_shrink': 0.8, 'shrink_win': 8, 'break_up': 10, 'break_strength': 0.04, 'zt_vol_filter': 5.0}, 'profit': 0.30, 'dd': 0.20, 'stop': 0.30, 'hold': 45, 'mode': 'C'},
        'zz1000': {'bp': {'vol_base': 'or', 'ma_n': 12, 'vol_shrink': 0.55, 'shrink_win': 8, 'break_up': 8, 'break_strength': 0.01, 'zt_vol_filter': 4.0}, 'profit': 0.60, 'dd': 0.07, 'stop': 0.10, 'hold': 30, 'mode': 'C'},
        'zz2000': {'bp': {'vol_base': 'maN', 'ma_n': 10, 'vol_shrink': 0.5, 'shrink_win': 3, 'break_up': 8, 'break_strength': 0.0, 'zt_vol_filter': 1.5}, 'profit': 0.99, 'dd': 0.99, 'stop': 0.20, 'hold': 25, 'mode': 'A'},
    }
    cache = {}
    for pool, c in COMBOS.items():
        if pool not in cache:
            cache[pool] = g.load_pool(pool)
        members, dailies = cache[pool]
        bp = c['bp']
        sig = g.build_sig(dailies, bp)
        # 实盘
        trades_r, equity = vf.full_backtest(pool, bp, c['profit'], c['dd'], mode=c['mode'], stop=c['stop'], hold=c['hold'], sig=sig, cache=cache)
        # 理想
        trades_i = ideal_backtest(pool, members, dailies, sig, c['profit'], c['dd'], mode=c['mode'], stop=c['stop'], hold=c['hold'])
        mr = vf.metrics(equity)
        mi = full_metrics(trades_i)
        print(f"{pool}: 实盘 {len(trades_r)}笔 收{mr['total_ret']*100:+.1f}% | 理想 {mi['trades']}笔 收{mi['total_ret']*100:+.1f}% 胜{mi['win_rate']*100:.0f}% 均{mi['avg_ret']*100:+.2f}% 盈亏比{mi['pl_ratio']:.2f}")
