# -*- coding: utf-8 -*-
import backtrader as bt
from datetime import datetime, timedelta
from typing import Optional, List
from enum import Enum
import math

import queue
import threading


__all__ = ["订单类型", "市场类型", "自适应市场仓位", "交易信号", "批次", "订单执行器", "高级策略基类_", "高级策略基类", "自定义实时数据源", "回测"]


# ---------- 订单类型枚举 ----------
class 订单类型(str, Enum):
    市价 = "Market"
    限价 = "Limit"
    止损 = "Stop"
    止损限价 = "StopLimit"


# ---------- 市场类型 ----------
class 市场类型(str, Enum):
    股票 = "stock"
    期货 = "futures"
    加密货币 = "crypto"


# ==================== 自适应市场仓位计算器 (Sizer) ====================
class 自适应市场仓位(bt.Sizer):
    """
    根据市场类型计算下单数量，支持：
    - 股票：100股整数倍
    - 加密货币：最小数量精度（如0.001）
    - 期货：整数手，支持合约乘数
    - 杠杆与保证金占用
    - 风险百分比/固定金额/固定数量模式
    修正：使用 self.strategy.broker 获取账户信息
    """

    params = (
        ("市场类型", 市场类型.加密货币),
        ("风险百分比", None),
        ("固定金额", None),
        ("固定数量", None),
        ("杠杆", 1.0),
        ("股票每手股数", 100),
        ("加密货币最小数量", 0.001),
        ("期货合约乘数", 1.0),
        ("期货最小手数", 1),
        ("允许部分开仓", False),
    )

    def _getsizing(self, 佣金信息, 可用现金, 数据, 是否买入):
        """返回最终下单数量（股/币/手）"""
        价格 = 数据.close[0]

        # 1. 确定名义目标价值（单位：基础货币）
        # 修正：通过 strategy 获取总资产
        总资产 = self.strategy.broker.getvalue()
        if self.p.固定金额 is not None:
            目标价值 = self.p.固定金额
        elif self.p.风险百分比 is not None:
            目标价值 = 总资产 * (self.p.风险百分比 / 100.0)
        elif self.p.固定数量 is not None:
            原始数量 = self.p.固定数量
            return self._应用市场规则(原始数量, 价格, 可用现金, 是否买入)
        else:
            raise ValueError("必须指定 固定金额、风险百分比 或 固定数量 之一")

        # 2. 根据市场类型将目标价值转换为理论数量
        市场 = self.p.市场类型
        if 市场 == 市场类型.期货:
            每手价值 = 价格 * self.p.期货合约乘数
            理论手数 = 目标价值 / 每手价值 / self.p.杠杆
            原始数量 = 理论手数
        else:
            理论数量 = 目标价值 / 价格 / self.p.杠杆
            原始数量 = 理论数量

        return self._应用市场规则(原始数量, 价格, 可用现金, 是否买入)

    def _应用市场规则(self, 原始数量, 价格, 可用现金, 是否买入):
        市场 = self.p.市场类型
        if 市场 == 市场类型.股票:
            手 = self.p.股票每手股数
            最终数量 = int(原始数量 // 手) * 手
        elif 市场 == 市场类型.加密货币:
            最小量 = self.p.加密货币最小数量
            最终数量 = math.floor(原始数量 / 最小量) * 最小量
        elif 市场 == 市场类型.期货:
            最小手 = self.p.期货最小手数
            最终数量 = int(原始数量 // 最小手) * 最小手
        else:
            最终数量 = 原始数量

        if 最终数量 <= 0:
            return 0

        # 验证资金是否足够
        if 市场 == 市场类型.期货:
            每手价值 = 价格 * self.p.期货合约乘数
            所需保证金 = 最终数量 * 每手价值 / self.p.杠杆
        else:
            所需保证金 = 最终数量 * 价格 / self.p.杠杆

        if 所需保证金 > 可用现金:
            if not self.p.允许部分开仓:
                return 0
            else:
                # 调整至最大可开数量
                if 市场 == 市场类型.期货:
                    最大手数 = int(可用现金 * self.p.杠杆 / (价格 * self.p.期货合约乘数))
                    最大手数 = max(最大手数 // self.p.期货最小手数 * self.p.期货最小手数, 0)
                    return 最大手数
                else:
                    最大数量 = int(可用现金 * self.p.杠杆 / 价格)
                    if 市场 == 市场类型.股票:
                        最大数量 = max(最大数量 // self.p.股票每手股数 * self.p.股票每手股数, 0)
                    elif 市场 == 市场类型.加密货币:
                        最大数量 = math.floor(最大数量 / self.p.加密货币最小数量) * self.p.加密货币最小数量
                    return 最大数量
        return 最终数量


# ==================== 交易信号对象 ====================
class 交易信号:
    """完整买卖点信息"""

    def __init__(
        self,
        方向: str,
        原始数量: int = 0,
        参考价格: float = None,
        时间戳: datetime = None,
        止损价: Optional[float] = None,
        止盈价: Optional[float] = None,
        移动回撤比例: Optional[float] = None,
        订单类型: 订单类型 = 订单类型.市价,
        限价: Optional[float] = None,
        止损触发价: Optional[float] = None,
        有效期: Optional[int] = None,
        交易编号: Optional[int] = None,
        市场类型: 市场类型 = 市场类型.加密货币,
        杠杆: float = 1.0,
        信号来源: str = "",
    ):
        self.方向 = 方向
        self.原始数量 = 原始数量
        self.参考价格 = 参考价格 or 0.0
        self.时间戳 = 时间戳 or datetime.now()
        self.止损价 = 止损价
        self.止盈价 = 止盈价
        self.移动回撤比例 = 移动回撤比例
        self.订单类型 = 订单类型
        self.限价 = 限价
        self.止损触发价 = 止损触发价
        self.有效期 = 有效期
        self.交易编号 = 交易编号
        self.市场类型 = 市场类型
        self.杠杆 = 杠杆
        self.信号来源 = 信号来源


# ==================== 批次对象 ====================
class 批次:
    """单个持仓批次，支持独立风控"""

    def __init__(self, 交易编号: int, 方向: str, 数量: int, 入场价: float, 止损价: float = None, 止盈价: float = None, 移动回撤比例: float = None, 状态: str = "待成交"):
        self.交易编号 = 交易编号
        self.方向 = 方向
        self.数量 = 数量
        self.入场价 = 入场价
        self.止损价 = 止损价
        self.止盈价 = 止盈价
        self.移动回撤比例 = 移动回撤比例
        self.状态 = 状态  # 待成交/持有/平仓中/已平仓
        # 移动止损追踪值
        self.最高价跟踪 = 入场价
        self.最低价跟踪 = 入场价
        self.当前移动止损价 = None
        if 移动回撤比例 is not None:
            if 方向 == "long":
                self.当前移动止损价 = 入场价 * (1 - 移动回撤比例)
            else:
                self.当前移动止损价 = 入场价 * (1 + 移动回撤比例)
        # 关联订单（用于OCO）
        self.止损订单 = None
        self.止盈订单 = None
        self.风控已挂 = False

    def 更新移动止损(self, 最高价: float, 最低价: float, 当前价: float) -> bool:
        """基于最高/最低价更新移动止损，返回是否触发"""
        if self.移动回撤比例 is None:
            return False
        if self.方向 == "long":
            if 最高价 > self.最高价跟踪:
                self.最高价跟踪 = 最高价
                新止损 = self.最高价跟踪 * (1 - self.移动回撤比例)
                if self.当前移动止损价 is None or 新止损 > self.当前移动止损价:
                    self.当前移动止损价 = 新止损
            # 使用最低价检查是否触发
            return 最低价 <= self.当前移动止损价 if self.当前移动止损价 is not None else False
        else:
            if 最低价 < self.最低价跟踪:
                self.最低价跟踪 = 最低价
                新止损 = self.最低价跟踪 * (1 + self.移动回撤比例)
                if self.当前移动止损价 is None or 新止损 < self.当前移动止损价:
                    self.当前移动止损价 = 新止损
            return 最高价 >= self.当前移动止损价 if self.当前移动止损价 is not None else False

    def 检查静态风控(self, 当前价: float) -> bool:
        if self.止损价 is not None:
            if self.方向 == "long" and 当前价 <= self.止损价:
                return True
            if self.方向 == "short" and 当前价 >= self.止损价:
                return True
        if self.止盈价 is not None:
            if self.方向 == "long" and 当前价 >= self.止盈价:
                return True
            if self.方向 == "short" and 当前价 <= self.止盈价:
                return True
        return False


# ==================== 订单执行器 ====================
class 订单执行器:
    """
    负责将交易信号转换为实际订单，集成Sizer、OCO风控单、部分成交处理
    """

    def __init__(self, 策略实例, 批次列表: List, 日志函数):
        self.策略 = 策略实例
        self.批次列表 = 批次列表
        self.日志 = 日志函数

    def 执行(self, 信号: 交易信号):
        """主入口：下单并部署风控"""
        # 1. 计算最终数量
        if 信号.原始数量 > 0:
            数量 = 信号.原始数量
        else:
            # 调用策略的 _getsize 方法，它会使用绑定的 Sizer
            数量 = self.策略.getsizing(self.策略.data, isbuy=(信号.方向 == "long"))
        if 数量 <= 0:
            self.日志(f"数量为0，忽略信号: {信号.信号来源}")
            return

        # 2. 处理有效期
        有效期日期 = None
        if 信号.有效期 is not None:
            有效期日期 = self.策略.data.datetime.date(0) + timedelta(days=信号.有效期)

        # 3. 先创建批次（状态=待成交），确保订单未成交时已预留位置
        批次对象 = 批次(交易编号=信号.交易编号 or self.策略.获取下一个交易编号(), 方向=信号.方向, 数量=数量, 入场价=信号.参考价格, 止损价=信号.止损价, 止盈价=信号.止盈价, 移动回撤比例=信号.移动回撤比例, 状态="待成交")
        self.批次列表.append(批次对象)
        信号.交易编号 = 批次对象.交易编号

        # 4. 提交主订单
        主订单 = self._提交主订单(信号, 数量, 有效期日期)
        if 主订单 is None:
            # 订单创建失败，删除批次
            self.批次列表.remove(批次对象)
            return

        # 5. 将订单与批次关联
        主订单.关联批次 = 批次对象
        批次对象.主订单引用 = 主订单

        self.日志(f"提交订单: {信号.方向} {数量}@{'市价' if 信号.订单类型 == 订单类型.市价 else 信号.限价} 止损={信号.止损价} 止盈={信号.止盈价} 移动={信号.移动回撤比例}")

    def _提交主订单(self, 信号: 交易信号, 数量: int, 有效期日期):
        """根据订单类型创建Backtrader订单"""
        if 信号.订单类型 == 订单类型.市价:
            if 信号.方向 == "long":
                return self.策略.buy(size=数量, exectype=bt.Order.Market)
            else:
                return self.策略.sell(size=数量, exectype=bt.Order.Market)
        elif 信号.订单类型 == 订单类型.限价:
            if 信号.限价 is None:
                self.日志("限价单缺少限价")
                return None
            if 信号.方向 == "long":
                return self.策略.buy(size=数量, price=信号.限价, exectype=bt.Order.Limit, valid=有效期日期)
            else:
                return self.策略.sell(size=数量, price=信号.限价, exectype=bt.Order.Limit, valid=有效期日期)
        elif 信号.订单类型 == 订单类型.止损:
            if 信号.止损触发价 is None:
                self.日志("止损单缺少触发价")
                return None
            if 信号.方向 == "long":
                return self.策略.sell(size=数量, price=信号.止损触发价, exectype=bt.Order.Stop, valid=有效期日期)
            else:
                return self.策略.buy(size=数量, price=信号.止损触发价, exectype=bt.Order.Stop, valid=有效期日期)
        else:
            self.日志("不支持止损限价单")
            return None


# ==================== 完整高级策略基类（全中文） ====================
class 高级策略基类_(bt.Strategy):
    """
    支持完整买卖点信号、多批次独立风控、自适应市场资金管理、OCO模拟、全局风控
    子类需实现 生成买卖点 方法
    """

    params = (
        # 资金与市场
        ("市场类型", 市场类型.加密货币),
        ("风险百分比", 2.0),
        ("杠杆", 1.0),
        ("允许部分开仓", False),
        # 股票
        ("股票每手股数", 100),
        # 加密货币
        ("加密货币最小数量", 0.001),
        # 期货
        ("期货合约乘数", 1.0),
        ("期货最小手数", 1),
        # 批次管理
        ("最大批次数", 5),
        ("允许加多", True),
        ("允许加空", True),
        # 风控
        ("默认止损比例", 0.02),
        ("默认止盈比例", 0.03),
        ("默认移动回撤比例", 0.01),
        ("启用移动止损", True),
        ("使用OCO订单", True),  # 是否使用真实OCO订单（依赖券商支持）
        ("全局最大回撤限制", 20.0),
        ("每日亏损限制", 5.0),
        ("回撤后全部平仓", True),
        # 其他
        ("数据预热周期", 30),  # 指标计算需要的最小K线数
    )

    def __init__(self):
        super().__init__()
        # 批次管理
        self.批次列表: List[批次] = []
        self.下一个交易编号 = 1
        # 全局风控
        self.每日起始净值 = None
        self.上次日期 = None
        self.峰值净值 = None
        self.全局风控触发 = False
        # 订单暂存与部分成交累计
        self.订单累计成交 = {}  # {order.ref: 已成交数量}
        # 自定义Sizer
        self.sizer = 自适应市场仓位(
            市场类型=self.p.市场类型,
            风险百分比=self.p.风险百分比,
            杠杆=self.p.杠杆,
            允许部分开仓=self.p.允许部分开仓,
            股票每手股数=self.p.股票每手股数,
            加密货币最小数量=self.p.加密货币最小数量,
            期货合约乘数=self.p.期货合约乘数,
            期货最小手数=self.p.期货最小手数,
        )
        # 注册Sizer（关键！）
        self.setsizer(self.sizer)

        # 执行器
        self.执行器 = 订单执行器(self, self.批次列表, self.日志)

    def 日志(self, 文本: str):
        dt = self.datas[0].datetime.datetime(0)
        print(f"[{dt}] {文本}")

    def 获取下一个交易编号(self) -> int:
        编号 = self.下一个交易编号
        self.下一个交易编号 += 1
        return 编号

    # ---------- 批次管理 ----------
    def 移除批次(self, 批次对象: 批次):
        if 批次对象 in self.批次列表:
            self.批次列表.remove(批次对象)
            self.日志(f"移除批次 {批次对象.交易编号}")

    def 平仓批次(self, 批次对象: 批次, 平仓数量: int = None):
        """发起市价平仓，将批次标记为平仓中"""
        if 平仓数量 is None:
            平仓数量 = 批次对象.数量
        if 平仓数量 <= 0 or 平仓数量 > 批次对象.数量:
            return
        if 批次对象.方向 == "long":
            订单 = self.sell(size=平仓数量, exectype=bt.Order.Market)
        else:
            订单 = self.buy(size=平仓数量, exectype=bt.Order.Market)

        订单.关联批次 = 批次对象
        订单.平仓数量 = 平仓数量
        批次对象.状态 = "平仓中"
        self.日志(f"提交平仓订单 批次{批次对象.交易编号} 数量{平仓数量}")

    def 全部平仓(self):
        """清空所有批次"""
        for 批次 in self.批次列表[:]:
            self.平仓批次(批次)

    # ---------- 风控管理（使用High/Low） ----------
    def 管理批次风控(self):
        最高价 = self.data.high[0]
        最低价 = self.data.low[0]
        收盘价 = self.data.close[0]
        for 批次 in self.批次列表[:]:
            if 批次.状态 != "持有":
                continue
            # 移动止损触发
            if self.p.启用移动止损 and 批次.移动回撤比例 is not None:
                if 批次.更新移动止损(最高价, 最低价, 收盘价):
                    self.日志(f"批次{批次.交易编号} 移动止损触发")
                    self.平仓批次(批次)
                    continue
            # 静态止损止盈触发
            if 批次.检查静态风控(收盘价):
                self.日志(f"批次{批次.交易编号} 静态止损/止盈触发")
                self.平仓批次(批次)

    # ---------- 挂载OCO风控单（仅在主订单成交后调用） ----------
    def _挂载风控单_OCO(self, 批次: 批次, 当前价格: float):
        """在主订单成交后，挂止损止盈单，使用真正的 OCO (oco 参数)"""
        if not self.p.使用OCO订单:
            return
        if 批次.风控已挂:
            return
        数量 = 批次.数量
        if 批次.方向 == "long":
            # 多头：止损单是卖出止损，止盈单是卖出限价
            stop_order = None
            limit_order = None
            if 批次.止损价 is not None:
                stop_order = self.sell(size=数量, price=批次.止损价, exectype=bt.Order.Stop, transmit=False)
            if 批次.止盈价 is not None:
                limit_order = self.sell(size=数量, price=批次.止盈价, exectype=bt.Order.Limit, transmit=False)
            # 建立 OCO 关系
            if stop_order and limit_order:
                stop_order.oco = limit_order
                limit_order.oco = stop_order
                # 最后一个订单必须 transmit=True 才会发送
                limit_order.transmit = True
                stop_order.transmit = False
            elif stop_order:
                stop_order.transmit = True
            elif limit_order:
                limit_order.transmit = True
            # 记录订单引用
            批次.止损订单 = stop_order
            批次.止盈订单 = limit_order
        else:
            # 空头：止损单是买入止损，止盈单是买入限价
            stop_order = None
            limit_order = None
            if 批次.止损价 is not None:
                stop_order = self.buy(size=数量, price=批次.止损价, exectype=bt.Order.Stop, transmit=False)
            if 批次.止盈价 is not None:
                limit_order = self.buy(size=数量, price=批次.止盈价, exectype=bt.Order.Limit, transmit=False)
            if stop_order and limit_order:
                stop_order.oco = limit_order
                limit_order.oco = stop_order
                limit_order.transmit = True
                stop_order.transmit = False
            elif stop_order:
                stop_order.transmit = True
            elif limit_order:
                limit_order.transmit = True
            批次.止损订单 = stop_order
            批次.止盈订单 = limit_order
        批次.风控已挂 = True
        if stop_order or limit_order:
            self.日志(f"批次{批次.交易编号} 已挂载OCO风控单")

    # ---------- 全局风控 ----------
    def 检查全局风控(self) -> bool:
        if self.全局风控触发:
            return False
        当前净值 = self.broker.getvalue()
        今日日期 = self.datas[0].datetime.date(0)
        if 今日日期 != self.上次日期:
            self.每日起始净值 = 当前净值
            self.上次日期 = 今日日期
        if self.峰值净值 is None or 当前净值 > self.峰值净值:
            self.峰值净值 = 当前净值
        回撤 = (当前净值 - self.峰值净值) / self.峰值净值
        if 回撤 < -self.p.全局最大回撤限制 / 100:
            self.日志(f"最大回撤超过 {self.p.全局最大回撤限制}%，触发全局风控")
            if self.p.回撤后全部平仓:
                self.全部平仓()
            self.全局风控触发 = True
            return False
        if self.每日起始净值:
            日亏损 = (当前净值 - self.每日起始净值) / self.每日起始净值
            if 日亏损 < -self.p.每日亏损限制 / 100:
                self.日志(f"每日亏损超过 {self.p.每日亏损限制}%，暂停开仓")
                return False
        return True

    # ---------- 订单回调（处理成交、部分成交、取消） ----------
    def notify_order(self, 订单):
        if 订单.status in [订单.Submitted, 订单.Accepted]:
            return

        # 处理平仓订单成交
        if 订单.status == 订单.Completed and hasattr(订单, "平仓数量"):
            if hasattr(订单, "关联批次"):
                批次 = 订单.关联批次
                平仓数量 = 订单.平仓数量
                批次.数量 -= 平仓数量
                self.日志(f"批次{批次.交易编号} 平仓成交 {平仓数量}，剩余 {批次.数量}")
                if 批次.数量 <= 0:
                    self.移除批次(批次)
                else:
                    批次.状态 = "持有"
            return

        # 处理开仓订单成交或部分成交
        if 订单.status in [订单.Completed, 订单.Partial]:
            if hasattr(订单, "关联批次"):
                批次 = 订单.关联批次
                # 累计成交数量处理（防止部分成交多次回调）
                累计 = self.订单累计成交.get(订单.ref, 0)
                本次新增 = 订单.executed.size - 累计
                if 本次新增 > 0:
                    # 更新批次数量（如果之前没有成交过，则设置数量为新增；否则累加）
                    if 累计 == 0:
                        批次.数量 = 本次新增
                    else:
                        批次.数量 += 本次新增
                    self.订单累计成交[订单.ref] = 订单.executed.size
                    self.日志(f"批次{批次.交易编号} 新增成交 {本次新增}，累计批次数量 {批次.数量}")
                    # 如果是首次成交，更新入场价并挂载OCO
                    if 累计 == 0:
                        批次.入场价 = 订单.executed.price
                        # 更新移动止损初始值
                        if 批次.移动回撤比例:
                            if 批次.方向 == "long":
                                批次.当前移动止损价 = 批次.入场价 * (1 - 批次.移动回撤比例)
                            else:
                                批次.当前移动止损价 = 批次.入场价 * (1 + 批次.移动回撤比例)
                        批次.状态 = "持有"
                        # 挂载OCO风控单（若使用）
                        self._挂载风控单_OCO(批次, 订单.executed.price)
                # 如果订单完全成交，清理累计记录
                if 订单.status == 订单.Completed and 订单.ref in self.订单累计成交:
                    del self.订单累计成交[订单.ref]
                return

        # 订单取消/拒绝/过期：删除关联的预创建批次
        if 订单.status in [订单.Canceled, 订单.Margin, 订单.Rejected, 订单.Expired]:
            if hasattr(订单, "关联批次"):
                批次 = 订单.关联批次
                if 批次 in self.批次列表:
                    self.批次列表.remove(批次)
                    self.日志(f"订单未成交，删除预创建批次{批次.交易编号}")
            # 清理累计成交记录
            if 订单.ref in self.订单累计成交:
                del self.订单累计成交[订单.ref]
            self.日志(f"订单状态: {订单.getstatusname()}")

    # ---------- 核心主循环 ----------
    def 生成买卖点(self) -> Optional[交易信号]:
        """子类必须实现，返回交易信号或None"""
        raise NotImplementedError("子类必须实现 生成买卖点 方法")

    def next(self):
        # 数据预热检查
        if len(self.data) < self.p.数据预热周期:
            return

        # 全局风控检查（决定是否允许开新仓）
        允许开仓 = self.检查全局风控()
        # 管理已有批次的风控（平仓）
        self.管理批次风控()
        # 生成信号并执行（仅在允许开仓且未达最大批次数时）
        if 允许开仓 and len(self.批次列表) < self.p.最大批次数:
            信号 = self.生成买卖点()
            if 信号:
                self.执行器.执行(信号)


# ==================== 示例：简单双均线策略（演示多批次加仓） ====================
class 示例双均线策略(高级策略基类_):
    """使用双均线金叉死叉产生买卖点，支持加仓"""

    params = (
        ("快线周期", 10),
        ("慢线周期", 30),
        ("止损比例", 0.02),
        ("止盈比例", 0.03),
        ("移动回撤比例", 0.01),
        ("加仓间隔K线数", 5),  # 距离上次开仓至少多少根K线才允许再次加仓
    )

    def __init__(self):
        super().__init__()
        self.快线 = bt.indicators.SMA(self.data.close, period=self.p.快线周期)
        self.慢线 = bt.indicators.SMA(self.data.close, period=self.p.慢线周期)
        self.交叉 = bt.indicators.CrossOver(self.快线, self.慢线)
        self.上次开仓K线 = {"long": -100, "short": -100}  # 记录最近开仓的K线索引

    def 生成买卖点(self):
        # 获取当前持有批次的方向统计（不限制唯一方向，允许双向锁仓，但加仓控制分开）
        多头批次数 = sum(1 for b in self.批次列表 if b.方向 == "long" and b.状态 == "持有")
        空头批次数 = sum(1 for b in self.批次列表 if b.方向 == "short" and b.状态 == "持有")
        当前K线索引 = len(self.data)

        # 金叉做多（允许加多）
        if self.交叉[0] == 1:
            if self.p.允许加多 and 多头批次数 < self.p.最大批次数:
                # 加仓间隔控制
                if 当前K线索引 - self.上次开仓K线["long"] >= self.p.加仓间隔K线数:
                    信号 = 交易信号(方向="long", 原始数量=0, 参考价格=self.data.close[0], 止损价=self.data.close[0] * (1 - self.p.止损比例), 止盈价=self.data.close[0] * (1 + self.p.止盈比例), 移动回撤比例=self.p.移动回撤比例 if self.p.启用移动止损 else None, 订单类型=订单类型.市价, 信号来源="双均线金叉")
                    self.上次开仓K线["long"] = 当前K线索引
                    return 信号

        # 死叉做空（允许加空）
        if self.交叉[0] == -1:
            if self.p.允许加空 and 空头批次数 < self.p.最大批次数:
                if 当前K线索引 - self.上次开仓K线["short"] >= self.p.加仓间隔K线数:
                    信号 = 交易信号(方向="short", 原始数量=0, 参考价格=self.data.close[0], 止损价=self.data.close[0] * (1 + self.p.止损比例), 止盈价=self.data.close[0] * (1 - self.p.止盈比例), 移动回撤比例=self.p.移动回撤比例 if self.p.启用移动止损 else None, 订单类型=订单类型.市价, 信号来源="双均线死叉")
                    self.上次开仓K线["short"] = 当前K线索引
                    return 信号
        return None


# ==================== 修正后的随机数据类（用于演示） ====================
class 随机数据(bt.feeds.DataBase):
    def __init__(self, start_date=datetime(2020, 1, 1), 最大条数=10000):
        super().__init__()
        self.关闭价 = 100.0
        self._date = start_date
        self._条数计数 = 0
        self.最大条数 = 最大条数

    def _load(self):
        # 达到最大条数后停止提供数据
        if self._条数计数 >= self.最大条数:
            return False

        # 递增日期
        self._date += timedelta(days=1)
        # 随机生成价格
        self.关闭价 += np.random.randn() * 2
        self.lines.open[0] = self.关闭价 + np.random.randn() * 0.5
        self.lines.high[0] = self.关闭价 + abs(np.random.randn() * 1)
        self.lines.low[0] = self.关闭价 - abs(np.random.randn() * 1)
        self.lines.close[0] = self.关闭价
        self.lines.volume[0] = 10000 + int(abs(np.random.randn() * 5000))
        self.lines.datetime[0] = bt.date2num(self._date)

        self._条数计数 += 1
        return True


class 自定义实时数据源(bt.feed.DataBase):
    """
    一个用于模拟实时数据推送的数据源，继承自Backtrader的DataBase。
    在实际应用中，你需要将“模拟生成数据”的部分，替换为接收WebSocket等推送数据的代码。
    """

    def __init__(self, 数据队列: queue.Queue, 观察员: "观察者", 魔法, **魔法参数):
        # 调用父类初始化方法
        super(自定义实时数据源, self).__init__()
        # 存储外部传入的数据队列，用于接收实时数据
        self.数据队列 = 数据队列
        # 定义一个标志，用于控制数据加载的停止
        self.正在运行 = False
        self.观察员 = 观察员
        self.魔法 = 魔法
        self.__魔法参数 = 魔法参数
        self.已有数据 = False

    def start(self):
        """
        数据源开始工作时的初始化操作。
        这里可以不执行任何操作，或者启动数据接收线程等。
        """
        print(f"[{datetime.now()}] 自定义数据源已启动...")
        self.正在运行 = True

        def 运行回测():
            self.观察员.读取任意数据(self.魔法, **self.__魔法参数)
            self.正在运行 = False

        回测线程 = threading.Thread(target=运行回测, daemon=True)
        回测线程.start()

    def stop(self):
        """
        数据源停止时的清理操作。
        这里我们将正在运行的标志设为False，停止数据加载。
        """
        self.正在运行 = False
        print(f"[{datetime.now()}] 自定义数据源已停止。")

    def _load(self):
        """
        (核心方法) Backtrader会循环调用此方法来获取数据。
        每次被调用时，都应该返回新的数据行（一个K线/一个数据点）。
        如果没有新数据，返回 False，Backtrader会等待下次调用。
        """
        if not self.正在运行:
            return False
        # 当没有新数据且系统仍在运行时，进入等待
        # 从外部队列获取新数据

        while True:
            try:
                data_point = self.数据队列.get(timeout=0.5)
                break
            except queue.Empty:
                if not self.已有数据:
                    continue
                else:
                    if self.正在运行:
                        continue
                    return False
        # 解析数据元组
        dt, o, h, l, c, v, oi = data_point

        # 将数据写入Backtrader的数据线 (lines)
        self.lines.datetime[0] = bt.date2num(dt)
        self.lines.open[0] = o
        self.lines.high[0] = h
        self.lines.low[0] = l
        self.lines.close[0] = c
        self.lines.volume[0] = v
        self.lines.openinterest[0] = oi
        self.已有数据 = True
        # 返回 True 表示成功加载一行数据
        return True


class 高级策略基类(bt.Strategy):
    params = (
        ("允许做多", True),
        ("允许做空", True),
        ("资金类型", "现金"),  # '现金' 或 '总权益'
        ("仓位比例", 0.8),
        ("最小交易单位", 1),
        ("使用限价单", True),  # True时开仓使用限价单，False使用市价单
        ("限价偏移", 0.01),  # 限价单相对于当前价的偏移比例
        ("止损比例", 0.05),  # 固定止损比例（如0.05）
        ("止损类型", "市价"),  # '市价' 或 '限价'（止损单类型）
        ("移动止损比例", None),  # 移动止损回撤比例
    )

    def __init__(self):
        self.止损单 = None
        self.最高价跟踪 = None
        self.最低价跟踪 = None
        self.待处理订单 = None
        self.待处理方向 = None
        self.待处理限价 = None

    def 日志(self, 文本, 时间=None):
        时间 = 时间 or self.datas[0].datetime.datetime(0)
        print(f"{时间} {文本}")

    def 计算目标数量(self, 价格):
        """根据资金类型和仓位比例计算目标数量"""
        现金 = self.broker.getcash()
        总权益 = self.broker.getvalue()
        if self.params.资金类型 == "现金":
            可用资金 = 现金
        else:
            可用资金 = 总权益
        投入资金 = 可用资金 * self.params.仓位比例
        数量 = int(投入资金 / 价格)
        数量 = max(数量, self.params.最小交易单位)
        return 数量

    def 提交限价单(self, 数据, 是否做多, 价格, 数量):
        if 是否做多:
            订单 = self.buy(data=数据, exectype=bt.Order.Limit, price=价格, size=数量)
        else:
            订单 = self.sell(data=数据, exectype=bt.Order.Limit, price=价格, size=数量)
        self.日志(f"提交限价单: {'买入' if 是否做多 else '卖出'} 价格={价格:.2f} 数量={数量}")
        return 订单

    def 提交市价单(self, 数据, 是否做多, 数量):
        if 是否做多:
            订单 = self.buy(data=数据, exectype=bt.Order.Market, size=数量)
        else:
            订单 = self.sell(data=数据, exectype=bt.Order.Market, size=数量)
        self.日志(f"提交市价单: {'买入' if 是否做多 else '卖出'} 数量={数量}")
        return 订单

    def 提交止损单(self, 数据, 是否做多, 触发价):
        数量 = abs(self.position.size)
        if 数量 == 0:
            return None
        # 根据止损类型选择订单类型
        if self.params.止损类型 == "市价":
            exectype = bt.Order.Stop
        else:  # 限价止损
            exectype = bt.Order.StopLimit
        if 是否做多:
            订单 = self.sell(data=数据, exectype=exectype, price=触发价, size=数量)
        else:
            订单 = self.buy(data=数据, exectype=exectype, price=触发价, size=数量)
        self.日志(f"提交止损单: 触发价={触发价:.2f} 数量={数量}")
        return 订单

    def 取消止损单(self):
        if self.止损单 and self.止损单.alive():
            self.cancel(self.止损单)
            self.日志("取消现有止损单")
        self.止损单 = None

    def 更新移动止损(self, 是否做多, 当前价格):
        if 是否做多:
            if self.最高价跟踪 is None or 当前价格 > self.最高价跟踪:
                self.最高价跟踪 = 当前价格
            return self.最高价跟踪 * (1 - self.params.移动止损比例)
        else:
            if self.最低价跟踪 is None or 当前价格 < self.最低价跟踪:
                self.最低价跟踪 = 当前价格
            return self.最低价跟踪 * (1 + self.params.移动止损比例)

    def 设置初始止损(self, 是否做多, 入场价):
        if self.params.止损比例 is not None:
            # 固定止损
            止损价 = 入场价 * (1 - self.params.止损比例) if 是否做多 else 入场价 * (1 + self.params.止损比例)
            self.日志(f"初始固定止损价: {止损价:.2f}")
            self.止损单 = self.提交止损单(self.data, 是否做多, 止损价)
        elif self.params.移动止损比例 is not None:
            # 移动止损初始单
            self.最高价跟踪 = 入场价 if 是否做多 else None
            self.最低价跟踪 = 入场价 if not 是否做多 else None
            止损价 = self.更新移动止损(是否做多, 入场价)
            self.日志(f"初始移动止损价: {止损价:.2f}")
            self.止损单 = self.提交止损单(self.data, 是否做多, 止损价)

    def 更新止损订单(self, 是否做多, 当前价格):
        if self.params.移动止损比例 is None or self.止损单 is None:
            return
        新止损价 = self.更新移动止损(是否做多, 当前价格)
        if 新止损价 is None:
            return
        当前止损价 = self.止损单.price
        if (是否做多 and 新止损价 > 当前止损价) or (not 是否做多 and 新止损价 < 当前止损价):
            self.日志(f"移动止损: {当前止损价:.2f} -> {新止损价:.2f}")
            self.取消止损单()
            self.止损单 = self.提交止损单(self.data, 是否做多, 新止损价)

    def 开仓(self, 数据, 是否做多, 限价=None):
        # 检查方向是否允许
        if (是否做多 and not self.params.允许做多) or (not 是否做多 and not self.params.允许做空):
            self.日志("方向不允许")
            return

        # 如果已有持仓，先平仓
        if self.position:
            self.日志("已有持仓，先平仓")
            self.平仓(数据)

        当前价 = 数据.close[0]

        # 确定实际使用的限价和订单类型
        if 限价 is not None:
            # 显式传入限价，强制使用限价单，忽略参数 '使用限价单'
            实际限价 = 限价
            使用限价单标志 = True
        else:
            # 未传入限价，根据策略参数决定
            使用限价单标志 = self.params.使用限价单
            if 使用限价单标志:
                实际限价 = 当前价 * (1 - self.params.限价偏移) if 是否做多 else 当前价 * (1 + self.params.限价偏移)
            else:
                实际限价 = 当前价  # 用于计算数量，实际订单为市价单

        # 计算目标数量（基于实际限价或当前价）
        数量 = self.计算目标数量(实际限价)
        if 数量 == 0:
            self.日志("无法开仓：计算数量为0")
            return

        # 提交订单
        if 使用限价单标志:
            订单 = self.提交限价单(数据, 是否做多, 实际限价, 数量)
        else:
            订单 = self.提交市价单(数据, 是否做多, 数量)

        if 订单:
            self.待处理订单 = 订单
            self.待处理方向 = 是否做多
            self.待处理限价 = 实际限价

    def 平仓(self, 数据):
        if self.position:
            self.取消止损单()
            数量 = abs(self.position.size)
            if self.position.size > 0:
                self.sell(data=数据, exectype=bt.Order.Market, size=数量)
            else:
                self.buy(data=数据, exectype=bt.Order.Market, size=数量)
            self.日志(f"平{'多' if self.position.size > 0 else '空'}仓: 数量={数量}")
            self.最高价跟踪 = None
            self.最低价跟踪 = None

    def notify_order(self, 订单):
        if 订单.status in [订单.Completed]:
            方向 = "买入" if 订单.isbuy() else "卖出"
            self.日志(f"{方向}成交, 价格={订单.executed.price:.2f}, 数量={订单.executed.size}")

            # 开仓成交后设置止损
            if self.待处理订单 == 订单:
                self.设置初始止损(self.待处理方向, 订单.executed.price)
                self.待处理订单 = None
                self.待处理方向 = None

            # 若平仓后无持仓，取消止损单（已做）
            if self.position.size == 0:
                self.取消止损单()

        elif 订单.status in [订单.Canceled, 订单.Margin, 订单.Rejected]:
            self.日志(f"订单失败: {订单.getstatusname()}")
            if self.待处理订单 == 订单:
                self.待处理订单 = None

    def notify_trade(self, 交易):
        if 交易.isclosed:
            self.日志(f"交易结束, 净利润={交易.pnlcomm:.2f}")
            print()

    def next(self):
        pass


class 回测(高级策略基类):
    params = (
        ("资金类型", "总权益"),
        ("仓位比例", 0.95),
        ("最小交易单位", 0.001),
        ("使用限价单", True),  # 改为 True 启用限价单
        ("限价偏移", 0.002),
        ("止损比例", 0.05),
        ("止损类型", "市价"),
        ("移动止损比例", None),
        ("观察员", None),
    )

    def __init__(self):
        super().__init__()
        self.已处理信号 = set()

    def 获取开仓限价(self, 是否做多):
        # 根据缠论分型计算限价，若无则返回 None 使用基类默认逻辑
        try:
            最新K = self.观察员.缠论K线序列[-1]
            return 最新K.分型特征值
        except:
            pass
        return None

    def next(self):
        # 1. 更新移动止损（基类方法）
        if self.position:
            self.更新止损订单(self.position.size > 0, self.data.close[0])

        # 2. 检查信号
        买信号 = self.检查买信号()
        卖信号 = self.检查卖信号()
        当前K序号 = self.p.观察员.当前缠K.序号
        信号ID = f"{当前K序号}_买{买信号}_卖{卖信号}"
        if 信号ID in self.已处理信号:
            return
        self.已处理信号.add(信号ID)

        # 3. 执行交易（优先处理平仓，再开仓）
        if 买信号 and self.position.size < 0:
            self.平仓(self.data)  # 空仓反手前先平空
        if 卖信号 and self.position.size > 0:
            self.平仓(self.data)  # 多仓反手前先平多

        if 买信号 and not self.position:
            限价 = self.获取开仓限价(True)
            self.开仓(self.data, 是否做多=True, 限价=限价)
        elif 卖信号 and not self.position:
            限价 = self.获取开仓限价(False)
            self.开仓(self.data, 是否做多=False, 限价=限价)

    def 检查买信号(self):
        if self.p.观察员.笔序列:
            k线 = self.p.观察员.缠论K线序列[-1]
            首 = True if k线.买卖点信息 and "买" in next(iter(k线.买卖点信息)) else False
            if 首:
                原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
                差值 = self.p.观察员.当前缠K.序号 - k线.序号
                self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
            k线 = self.p.观察员.缠论K线序列[-2]

            尾 = True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "买" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
            if 尾:
                原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
                差值 = self.p.观察员.当前缠K.序号 - k线.序号
                self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
            return 首 or 尾

    def 检查卖信号(self):
        if self.p.观察员.笔序列:
            k线 = self.p.观察员.缠论K线序列[-1]
            首 = True if k线.买卖点信息 and "卖" in next(iter(k线.买卖点信息)) else False
            if 首:
                原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
                差值 = self.p.观察员.当前缠K.序号 - k线.序号
                self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
            k线 = self.p.观察员.缠论K线序列[-2]

            尾 = True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "卖" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
            if 尾:
                原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
                差值 = self.p.观察员.当前缠K.序号 - k线.序号
                self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
            return 首 or 尾

    def log(self, 文本, dt=None):
        dt = dt or bt.num2date(self.data.datetime[0])
        print(f"[{dt.strftime('%Y-%m-%d %H:%M')}] {self.p.符号} | {文本}")


# ==================== 回测运行入口 ====================
if __name__ == "__main__":
    import numpy as np

    cerebro = bt.Cerebro()

    # 使用修正后的随机数据，生成 500 个交易日
    data = 随机数据(start_date=datetime(2022, 1, 1))
    cerebro.adddata(data)

    # 添加策略（使用示例双均线策略）
    cerebro.addstrategy(
        示例双均线策略,
        市场类型=市场类型.加密货币,
        风险百分比=2.0,  # 每笔使用2%资金
        杠杆=5.0,
        最大批次数=8,  # 最多同时持有3个批次
        允许加多=True,
        允许加空=True,
        启用移动止损=True,
        使用OCO订单=True,  # 尝试使用真实OCO（Backtrader模拟支持）
        全局最大回撤限制=20,
        每日亏损限制=5,
        数据预热周期=30,
        加仓间隔K线数=5,
    )

    # 配置 Broker
    cerebro.broker.setcash(100000.0)
    cerebro.broker.set_slippage_perc(perc=0.001)  # 滑点0.1%
    # 定义填充器，参数 perc 表示百分比，取值范围 0.0 到 100.0
    filler = bt.broker.fillers.FixedBarPerc(perc=30)

    # 将填充器应用到 Broker
    cerebro.broker.set_filler(filler)

    # 运行回测
    print("初始资金: {:.2f}".format(cerebro.broker.getvalue()))
    results = cerebro.run()
    print("最终资金: {:.2f}".format(cerebro.broker.getvalue()))

    # 可选：绘制图表（需要安装 matplotlib）
    # cerebro.plot()
