"""
量化交易回测系统 - 优化版
功能：基于包住形态的交易信号识别和回测分析
作者：量化交易团队
版本：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 分钟数据）
# ===============================================================

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：信号类型
    entry_price：开仓价格
    entry_time：开仓时间
    next_15min_time：15分钟未来行情

    """
    # 查 15 分钟之间的 1 分钟数据
    future_candles = get_future_data_1min(entry_time, next_15min_time)
    if not future_candles:
        return None, 0, 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  # 止损价位

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

        if direction == "long":  # 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']

    # 未触发止盈止损，用最后一根收盘价平仓
    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(dates, tp, sl):
    """
    datas：日期的列表

    :param dates:
    :param tp:
    :param sl:
    :return:
    """

    all_data = []
    for date_str in dates:
        all_data.extend(get_data_by_date(Weex15, date_str))  # 获取每天的数据，15分钟k线数据

    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 = []

    for idx in range(1, len(all_data) - 1):
        prev, curr = all_data[idx - 1], all_data[idx]  # 前一笔，当前一笔
        entry_candle = all_data[idx + 1]  # 下一笔开仓k线

        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'])  # 开仓价格
        exit_price, diff, exit_time = simulate_trade(
            direction,
            entry_price,
            entry_candle['id'],
            next_15min_time,
            tp=tp,
            sl=sl
        )

        if exit_price is None:
            continue

        stats[signal]['count'] += 1
        stats[signal]['total_profit'] += diff
        if diff > 0:
            stats[signal]['wins'] += 1

        trades.append({
            "entry_time": datetime.datetime.fromtimestamp(entry_candle['id'] / 1000),
            "exit_time": datetime.datetime.fromtimestamp(exit_time / 1000),
            "signal": stats[signal]['name'],
            "direction": "做多" if direction == "long" else "做空",
            "entry": entry_price,
            "exit": exit_price,
            "diff": diff
        })

    return trades, stats


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

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

    # for i in range(5, 11):
    #     for i1 in range(25, 51):

    trades, stats = backtest(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"止盈：{i1}, 止损：{i}")

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

    # if total_profit > total_fee * 0.1:
    #     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}")
    #
    #     print("\n===== 信号统计 =====")
    #
    # for k, v in stats.items():
    #     win_rate = (v['wins'] / v['count'] * 100) if v['count'] > 0 else 0
    #     print(
    #         f"{v['name']} ({k}) - 信号数: {v['count']} | 胜率: {win_rate:.2f}% | 总盈利: {v['total_profit']:.2f}")


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