"""
量化交易回测系统 - 优化版
功能：基于包住形态的交易信号识别和回测分析
作者：量化交易团队
版本：2.0
"""

import datetime
from typing import List, Dict, Tuple, Optional, Any
from dataclasses import dataclass
from loguru import logger
from peewee import fn
from models.weex import Weex15, Weex1


# ===============================================================
# 📊 配置管理类
# ===============================================================

@dataclass
class BacktestConfig:
    """回测配置类"""
    # 交易参数
    take_profit: float = 8.0  # 止盈点数
    stop_loss: float = -1.0  # 止损点数
    contract_size: float = 10000  # 合约规模
    open_fee: float = 5.0  # 开仓手续费
    close_fee_rate: float = 0.0005  # 平仓手续费率

    # 回测日期范围
    start_date: str = "2025-7-1"
    end_date: str = "2025-7-31"

    # 信号参数
    enable_bear_bull_engulf: bool = True  # 涨包跌信号
    enable_bull_bear_engulf: bool = True  # 跌包涨信号

    def __post_init__(self):
        """验证配置参数"""
        if self.take_profit <= 0:
            raise ValueError("止盈点数必须大于0")
        if self.stop_loss >= 0:
            raise ValueError("止损点数必须小于0")


@dataclass
class TradeRecord:
    """交易记录类"""
    entry_time: datetime.datetime
    exit_time: datetime.datetime
    signal_type: str
    direction: str
    entry_price: float
    exit_price: float
    profit_loss: float
    profit_amount: float
    total_fee: float
    net_profit: float


@dataclass
class SignalStats:
    """信号统计类"""
    signal_name: str
    count: int = 0
    wins: int = 0
    total_profit: float = 0.0

    @property
    def win_rate(self) -> float:
        """胜率计算"""
        return (self.wins / self.count * 100) if self.count > 0 else 0.0

    @property
    def avg_profit(self) -> float:
        """平均盈利"""
        return self.total_profit / self.count if self.count > 0 else 0.0


# ===============================================================
# 📊 数据获取模块
# ===============================================================

def get_data_by_date(model, date_str):
    """按天获取指定表的数据"""
    try:
        target_date = datetime.datetime.strptime(date_str, '%Y-%m-%d')
    except ValueError:
        logger.error("日期格式不正确，请使用 YYYY-MM-DD 格式。")
        return []

    start_ts = int(target_date.timestamp() * 1000)
    end_ts = int((target_date + datetime.timedelta(days=1)).timestamp() * 1000) - 1

    query = (model
             .select()
             .where(model.id.between(start_ts, end_ts))
             .order_by(model.id.asc()))

    return [
        {'id': i.id, 'open': i.open, 'high': i.high, 'low': i.low, 'close': i.close}
        for i in query
    ]


def get_future_data_1min(start_ts, end_ts):
    """获取指定时间范围内的 1 分钟数据"""
    query = (Weex1
             .select()
             .where(Weex1.id.between(start_ts, end_ts))
             .order_by(Weex1.id.asc()))
    return [{'id': i.id, 'open': i.open, 'high': i.high, 'low': i.low, 'close': i.close} for i in query]


# ===============================================================
# 📈 信号判定模块
# ===============================================================

def is_bullish(c): return float(c['open']) < float(c['close'])


def is_bearish(c): return float(c['open']) > float(c['close'])


def check_signal(prev, curr):
    """判断是否出现包住形态"""
    p_open, p_close = float(prev['open']), float(prev['close'])
    c_open, c_close = float(curr['open']), float(curr['close'])

    # 前跌后涨包住 -> 做多
    if is_bullish(curr) and is_bearish(prev) and c_open <= p_close and c_close >= p_open:
        return "long", "bear_bull_engulf"

    # 前涨后跌包住 -> 做空
    if is_bearish(curr) and is_bullish(prev) and c_open >= p_close and c_close <= p_open:
        return "short", "bull_bear_engulf"

    return None, None


# ===============================================================
# 💹 回测模拟模块（使用 1 分钟数据）
# ===============================================================

# ---------- 替换后的 simulate_trade ----------
def simulate_trade(direction, entry_price, entry_time, next_15min_time, tp=8, sl=-1):
    """
    返回 (exit_price, profit_diff, exit_time, exit_reason)
    exit_reason: 'tp' (触及止盈并以tp价平仓),
                 'sl' (触及止损并以sl价平仓),
                 'open_tp' (开盘跳空并以开盘价止盈),
                 'open_sl' (开盘跳空并以开盘价止损),
                 'timeout' (到分析窗口末尾，用最后一根收盘价平仓)
    注意：sl 参数为负数（你的设定），但在计算止损价时已处理方向
    """
    future_candles = get_future_data_1min(entry_time, next_15min_time)
    if not future_candles:
        return None, 0.0, None, None

    # 计算目标价位（数值）
    tp_price = entry_price + tp if direction == "long" else entry_price - tp
    sl_price = entry_price + sl if direction == "long" else entry_price - sl  # sl 为负数，long 情况为 entry + (负数) -> 小于 entry

    for candle in future_candles:
        open_p = float(candle['open'])
        high = float(candle['high'])
        low = float(candle['low'])
        ts = candle['id']

        if direction == "long":
            # 开盘跳空（以开盘价直接触及止盈/止损）
            if open_p >= tp_price:
                return open_p, open_p - entry_price, ts, 'open_tp'
            if open_p <= sl_price:
                return open_p, open_p - entry_price, ts, 'open_sl'
            # 盘中触及
            if high >= tp_price:
                return tp_price, tp, ts, 'tp'
            if low <= sl_price:
                return sl_price, sl, ts, 'sl'
        else:  # short
            if open_p <= tp_price:
                return open_p, entry_price - open_p, ts, 'open_tp'
            if open_p >= sl_price:
                return open_p, entry_price - open_p, ts, 'open_sl'
            if low <= tp_price:
                return tp_price, tp, ts, 'tp'
            if high >= sl_price:
                return sl_price, sl, ts, 'sl'

    # 未触发止盈止损，用最后一根收盘价平仓（视为 timeout）
    final = future_candles[-1]
    final_price = float(final['close'])
    diff = (final_price - entry_price) if direction == "long" else (entry_price - final_price)
    return final_price, diff, final['id'], 'timeout'


# ---------- 替换后的 backtest_single_position ----------
def backtest_single_position(dates, tp, sl):
    """
    单笔持仓回测（增强版），加入连续3次止损触发反向开仓（转向单）逻辑
    - 只有当 exit_reason 属于 'sl' 或 'open_sl' 时才算“真实止损”
    - 当连续真实止损计数达到 3 时，**下一笔**信号反向开仓（且该转向单不计入连续止损统计）
    """
    all_data = []
    for date_str in dates:
        all_data.extend(get_data_by_date(Weex15, date_str))
    all_data.sort(key=lambda x: x['id'])

    stats = {
        "bear_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包跌"},
        "bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨"},
    }

    trades = []
    current_position = None  # 当前持仓信息 dict or None

    consec_sl_count = 0          # 连续真实止损计数（只有 exit_reason 为 'sl' 或 'open_sl' 时 +1）
    reverse_next_signal = False  # 是否需要将下一笔信号取反（由连续3次止损触发）
    ignore_next_result = False   # 标记下一笔成交的结果是否要被忽略（用于转向单：转向单不计入连续止损计数，也不影响计数）

    for idx in range(1, len(all_data) - 1):
        prev, curr = all_data[idx - 1], all_data[idx]
        entry_candle = all_data[idx + 1]

        # 原始信号判定
        direction, signal = check_signal(prev, curr)
        if not direction:
            continue

        # 如果需要反向下一笔信号（由之前三次止损触发），则翻转 direction 并标记为转向单（只对这一次有效）
        is_reversal_trade = False
        if reverse_next_signal:
            direction = 'long' if direction == 'short' else 'short'
            is_reversal_trade = True
            reverse_next_signal = False
            ignore_next_result = True  # 这笔成交的平仓结果不影响 consec_sl_count

        # 下一个 15 分钟K线的时间范围（用 idx+50 作为 15min 窗口近似）
        next_15min_time = all_data[idx + 50]['id'] if idx + 50 < len(all_data) else all_data[-1]['id']
        entry_price = float(entry_candle['open'])

        # 如果已有持仓
        if current_position:
            # 同向信号 -> 跳过（维持现有持仓）
            if current_position['direction'] == direction:
                continue
            # 反向信号 -> 先按当前位置止盈止损平仓，再根据规则决定是否开新仓
            else:
                exit_price, diff, exit_time, exit_reason = simulate_trade(
                    current_position['direction'],
                    current_position['entry_price'],
                    current_position['entry_time'],
                    entry_candle['id'],
                    tp=tp,
                    sl=sl
                )
                if exit_price is not None:
                    trades.append({
                        "entry_time": datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
                        "exit_time": datetime.datetime.fromtimestamp(exit_time / 1000),
                        "signal": current_position['signal'],
                        "direction": "做多" if current_position['direction'] == "long" else "做空",
                        "entry": current_position['entry_price'],
                        "exit": exit_price,
                        "diff": diff,
                        "exit_reason": exit_reason,
                        "is_reversal_trade": current_position.get('is_reversal_trade', False)
                    })
                    # 更新统计（只有非转向单会计入统计？按你原逻辑计入）
                    stats_key = 'bear_bull_engulf' if current_position['signal'] == '涨包跌' else 'bull_bear_engulf'
                    stats[stats_key]['count'] += 1
                    stats[stats_key]['total_profit'] += diff
                    if diff > 0:
                        stats[stats_key]['wins'] += 1

                    # 根据 exit_reason 更新 consec_sl_count（但如果当时该仓为被标记为 ignore_result，则不变）
                    if not current_position.get('ignore_result', False):
                        if exit_reason in ('sl', 'open_sl'):
                            consec_sl_count += 1
                        else:
                            consec_sl_count = 0

                        # 如果累计达到 3 次真实止损 -> 标记下一笔反向
                        if consec_sl_count >= 3:
                            reverse_next_signal = True
                            consec_sl_count = 0  # 触发后清零（下一笔为转向单）
                    else:
                        # 如果这笔被标记为 ignore（通常是前面是转向单），则不影响计数
                        pass

                current_position = None  # 清空持仓

        # 开新仓（注意：如果这笔是转向单，我们之前已经取反了 direction，并设置了 is_reversal_trade 和 ignore_next_result）
        current_position = {
            "direction": direction,
            "signal": stats[signal]['name'],
            "entry_price": entry_price,
            "entry_time": entry_candle['id'],
            "is_reversal_trade": is_reversal_trade,
            "ignore_result": ignore_next_result  # 如果为 True，则本仓平仓结果不计入 consec_sl_count
        }

        # 如果我们标记了 ignore_next_result（说明这是转向单），只在刚开仓后清除标记，确保仅忽略这笔的**平仓**结果
        if ignore_next_result:
            # 清掉，只有这笔仓的平仓结果需要被忽略（记录在 current_position 中），
            # 后续在处理平仓时会读取 current_position['ignore_result'] 并决定是否影响计数
            ignore_next_result = False

    # 最后一笔持仓如果未平仓，用最后收盘价平掉
    if current_position:
        exit_price, diff, exit_time, exit_reason = simulate_trade(
            current_position['direction'],
            current_position['entry_price'],
            current_position['entry_time'],
            all_data[-1]['id'],
            tp=tp,
            sl=sl
        )
        if exit_price is not None:
            trades.append({
                "entry_time": datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
                "exit_time": datetime.datetime.fromtimestamp(exit_time / 1000),
                "signal": current_position['signal'],
                "direction": "做多" if current_position['direction'] == "long" else "做空",
                "entry": current_position['entry_price'],
                "exit": exit_price,
                "diff": diff,
                "exit_reason": exit_reason,
                "is_reversal_trade": current_position.get('is_reversal_trade', False)
            })
            stats_key = 'bear_bull_engulf' if current_position['signal'] == '涨包跌' else 'bull_bear_engulf'
            stats[stats_key]['count'] += 1
            stats[stats_key]['total_profit'] += diff
            if diff > 0:
                stats[stats_key]['wins'] += 1

            # 最后一笔是否计入连续止损计数
            if not current_position.get('ignore_result', False):
                if exit_reason in ('sl', 'open_sl'):
                    consec_sl_count += 1
                else:
                    consec_sl_count = 0
                if consec_sl_count >= 3:
                    # 可选择记录或告警，这里仅重置计数
                    consec_sl_count = 0

    return trades, stats



# ===============================================================
# 🚀 启动主流程
# ===============================================================

if __name__ == '__main__':
    dates = [f"2025-9-{i}" for i in range(1, 31)]

    trades, stats = backtest_single_position(dates, tp=50, sl=-10)

    logger.info("===== 每笔交易详情 =====")
    for t in trades:
        logger.info(
            f"{t['entry_time']} {t['direction']}({t['signal']}) "
            f"入场={t['entry']:.2f} 出场={t['exit']:.2f} 出场时间={t['exit_time']} "
            f"差价={t['diff']:.2f}"
        )

    total_profit = sum(t['diff'] / t['entry'] * 10000 for t in trades)
    total_fee = sum(5 + 10000 / t['entry'] * t['exit'] * 0.0005 for t in trades)

    print(f"\n一共交易笔数：{len(trades)}")
    print(f"一共盈利：{total_profit:.2f}")
    print(f"一共手续费：{total_fee:.2f}")
    print(f"净利润：{total_profit - total_fee:.2f}")
    print("\n===== 信号统计 =====")

    # ===============================================================================================================================

    # for i in range(1, 16):
    #     for i1 in range(1, 51):
    #         trades, stats = backtest_single_position(dates, tp=i1, sl=-i)
    #
    #         total_profit = sum(t['diff'] / t['entry'] * 10000 for t in trades)
    #         total_fee = sum(5 + 10000 / t['entry'] * t['exit'] * 0.0005 for t in trades)
    #
    #         if total_profit > total_fee * 0.1:
    #             print("\n===== 信号统计 =====")
    #             print(f"止盈：{i1}, 止损：{i}")
    #             print(f"\n一共交易笔数：{len(trades)}")
    #             print(f"一共盈利：{total_profit:.2f}")
    #             print(f"一共手续费：{total_fee:.2f}")
    #             print(f"净利润：{total_profit - total_fee * 0.1}")

# 需要优化，目前有两种情况，第一种，同向，不如说上一单开单是涨，上一单还没有结束，当前信号来了，就不开单，等上一单到了上一单的止损位或者止盈位在平仓
# 第二种，方向，上一单是涨，上一单还没有结束，当前信号来了，是跌，然后就按照现在这个信号要开仓的位置，平掉上一单，然后开一单方向的，
# 一笔中可能有好几次信号，都按照上面的规则去判断，要保证同一时间，只会有一笔持仓，
# 打印每笔的交易详细，如果一笔中同向，输入为一条交易记录
