"""
量化交易回测系统 - 策略方向反转版
功能：基于包住形态的交易信号识别和回测分析（信号方向已按用户要求反转）
作者：量化交易团队（修改：ChatGPT）
版本：2.1
"""

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-07-01"
    end_date: str = "2025-07-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):
    """按天获取指定表的数据，date_str 需为 YYYY-MM-DD"""
    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):
    """
    判断是否出现包住形态，并返回（direction, signal_key）
    注意：根据用户要求，这里把原来的方向映射“反转”：
      - 如果是 前跌后涨包住（过去我们认为做多），现在**改为做空**
      - 如果是 前涨后跌包住（过去我们认为做空），现在**改为做多**
    signal_key 保持 "bear_bull_engulf" / "bull_bear_engulf" 以便统计区分信号种类
    """
    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 "short", "bear_bull_engulf"

    # 前涨后跌包住 -> **原来做空，现在改为做多**
    if is_bearish(curr) and is_bullish(prev) and c_open >= p_close and c_close <= p_open:
        return "long", "bull_bear_engulf"

    return None, None


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

def simulate_trade(direction, entry_price, entry_time, next_15min_time, tp=8, sl=-1):
    """
    用 1 分钟数据进行精细化止盈止损模拟
    entry_time: 当前信号的 entry candle id（毫秒时间戳）
    next_15min_time: 下一个15min时间戳，用于界定止盈止损分析范围
    direction：'long' 或 'short'
    entry_price：开仓价格
    """
    # 查 15 分钟之间的 1 分钟数据
    future_candles = get_future_data_1min(entry_time, next_15min_time)
    if not future_candles:
        return None, 0, None

    # 止盈止损价格（tp 为正，sl 为负）
    tp_price = entry_price + tp if direction == "long" else entry_price - tp
    sl_price = entry_price + sl if direction == "long" else entry_price - sl

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

        if direction == "long":
            # 开盘跳空优先
            if open_p >= tp_price:
                return open_p, open_p - entry_price, candle['id']
            if open_p <= sl_price:
                return open_p, open_p - entry_price, candle['id']
            # 盘中触及
            if high >= tp_price:
                return tp_price, tp, candle['id']
            if low <= sl_price:
                return sl_price, sl, candle['id']

        else:  # short
            if open_p <= tp_price:
                return open_p, entry_price - open_p, candle['id']
            if open_p >= sl_price:
                return open_p, entry_price - open_p, candle['id']
            if low <= tp_price:
                return tp_price, tp, candle['id']
            if high >= sl_price:
                return sl_price, sl, candle['id']

    # 未触及 TP/SL，用最后一根收盘价平仓
    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']


# ===============================================================
# 📊 主回测流程（含单笔持仓逻辑）
# ===============================================================

def backtest_single_position(dates: List[str], tp: float, sl: float):
    """单笔持仓回测，保证任意时刻只有一笔持仓"""
    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": "涨包跌（bear_bull_engulf）"},
        "bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨（bull_bear_engulf）"},
    }

    trades = []
    current_position = None  # 当前持仓信息（字典或 None）

    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

        # 下一个 15 分钟K线的时间范围（若长度不足则取到最后）
        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
            # 反向信号 -> 先按当前持仓的规则用 simulate_trade 平仓，然后再开新仓
            else:
                exit_price, diff, exit_time = 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
                    })
                    # 更新统计（signal 字段是中文名）
                    stats_key = 'bear_bull_engulf' if current_position['signal'].startswith('涨包跌') else 'bull_bear_engulf'
                    stats[stats_key]['count'] += 1
                    stats[stats_key]['total_profit'] += diff
                    if diff > 0:
                        stats[stats_key]['wins'] += 1

                # 清空当前持仓，接下来在下方开新仓
                current_position = None

        # 开新仓（无论之前是否有仓位，若有仓位已被平掉现在可以开仓）
        current_position = {
            "direction": direction,
            "signal": stats[signal]['name'],
            "entry_price": entry_price,
            "entry_time": entry_candle['id']
        }

    # 最后一笔持仓如果未平仓，用最后收盘价平掉（以全数据范围最后时间为结算界限）
    if current_position:
        exit_price, diff, exit_time = 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
            })
            stats_key = 'bear_bull_engulf' if current_position['signal'].startswith('涨包跌') else 'bull_bear_engulf'
            stats[stats_key]['count'] += 1
            stats[stats_key]['total_profit'] += diff
            if diff > 0:
                stats[stats_key]['wins'] += 1

    return trades, stats


# ===============================================================
# 🚀 启动主流程（示例）
# ===============================================================
if __name__ == '__main__':
    # 修正日期格式为 YYYY-MM-DD（零填充）
    dates = [f"2025-09-{i:02d}" for i in range(1, 31)]

    # 示例参数：止盈 10 点，止损 -50 点（保留你的原调用方式）
    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) if trades else 0.0
    total_fee = sum(5 + 10000 / t['entry'] * t['exit'] * 0.0005 for t in trades) if trades else 0.0

    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 k, v in stats.items():
        print(f"{v['name']} -> 笔数: {v['count']}, 胜数: {v['wins']}, 总点差: {v['total_profit']:.2f}")
