本 Python 框架旨在为基于技术指标和量价分析的CTA(商品交易顾问)类策略提供一个**模块化、可配置、可扩展的开发、回测与参数优化环境**

好的,这是带有详细中文注释的完整代码,以及对整个框架(包含参数优化)的最终详细总结。

一、 最终详细总结

本 Python 框架旨在为基于技术指标和量价分析的CTA(商品交易顾问)类策略提供一个模块化、可配置、可扩展的开发、回测与参数优化环境。其核心设计理念是将策略的不同部分解耦,方便独立开发、测试和组合。

核心模块与功能:

  1. 数据层 (模拟): generate_sample_data 函数用于生成模拟的OHLCV(开高低收量)时间序列数据,方便快速演示和测试。实际使用中应替换为真实历史数据的加载模块。
  2. 信号因子库 (SIGNAL_FACTOR_REGISTRY, signal_from_*, filter_signal_from_*):
    • 职责: 每个函数代表一个具体的交易信号生成逻辑或过滤条件逻辑。它接收市场数据 (data) 和参数 (params),直接输出一个标准的信号序列(主信号为 1 买入 / -1 卖出 / 0 无操作;过滤信号为 1 满足 / 0 不满足)。
    • 实现: 包含了基于移动平均线交叉、RSI阈值穿越、MACD交叉、布林带突破、唐奇安通道突破等生成主信号的因子,以及基于ADX强度、成交量变化的过滤信号因子。
    • 管理: SIGNAL_FACTOR_REGISTRY 字典方便按名称动态调用这些因子函数。
  3. 信号组合与过滤层 (generate_combined_signals):
    • 职责: 根据策略配置,选择一个主信号因子,并可选地应用一个或多个过滤信号因子(如ADX强度过滤、成交量过滤)。
    • 逻辑: 通常采用“与”逻辑,即只有当所有过滤条件都满足时(过滤信号为1),主信号才会被保留;否则,该时间点的最终信号置为0。
  4. 回测引擎 (run_backtest):
    • 核心功能: 模拟实际交易过程,按日(或其他周期)进行。它接收最终的交易信号序列。
    • 风险管理集成: 内置了止盈、止损(百分比、ATR、追踪)和时间止损逻辑。这些规则在每个交易周期优先于策略信号进行检查。
    • 交易模拟: 考虑了手续费 (commission_rate) 和滑点 (slippage),模拟了基本的交易成本。入场/出场通常假设在信号产生的下一个周期的开盘价执行(可配置)。
    • 状态管理: 跟踪当前现金、持仓数量、入场价格、入场时间、持仓期最高价、入场时ATR等状态。
    • 输出: 返回详细的资金曲线序列 (portfolio_value) 和交易记录列表 (trades_df)。
  5. 绩效评估层 (evaluate_performance):
    • 职责: 基于回测产生的资金曲线和交易记录,计算一系列标准的量化绩效指标。
    • 指标: 包括总回报率、年化回报率、夏普比率、最大回撤、总交易次数、胜率、盈亏比、最终组合价值等。
  6. 编排层 (run_single_strategy):
    • 职责: 作为一个协调者,将数据准备、信号生成、回测执行、绩效评估这几个步骤串联起来,完成对单个具体策略配置的回测。
  7. 参数优化层 (batch_optimize_strategy, generate_config_combinations):
    • 核心功能: 实现了参数网格搜索优化。用户可以定义一个基础策略配置 (base_config) 和一个参数网格 (param_grid),其中包含要优化的参数及其候选值列表。
    • 参数组合生成: 利用 itertools.product 生成所有可能的参数组合。
    • 自动化回测: 自动遍历每一种参数组合,调用 run_single_strategy 进行回测。
    • 结果聚合与排序: 将所有参数组合的回测结果(参数+绩效)汇总到 DataFrame 中,并根据用户指定的绩效指标 (optimize_metric) 进行排序,方便找出最优参数集。

框架特点:

  • 模块化: 各组件职责清晰,低耦合,易于理解、修改和扩展。
  • 配置驱动: 策略逻辑、参数、风险管理规则均通过配置字典定义,灵活性高。
  • 可扩展性: 添加新指标信号、过滤条件或风险管理方法,只需编写相应函数并注册/集成即可。
  • 参数优化: 内置了系统的参数遍历优化功能,是策略研发的关键步骤。
  • 易用性: 提供了清晰的函数接口和示例用法。

潜在改进方向:

  • 更专业的回测引擎: 使用如 backtrader, zipline-reloaded, vectorbt 等成熟库,以支持更复杂的订单类型、事件驱动、保证金模型等。
  • 数据管理: 集成更强大的数据加载、清洗、存储方案。
  • 高级优化算法: 引入如贝叶斯优化、遗传算法等更高效的参数搜索方法。
  • 因子分析: 增加独立评估因子有效性(如IC分析)的模块。
  • 可视化: 添加绘图功能,如资金曲线、指标、交易点位、参数热力图等。
  • 并行计算: 对参数优化过程进行并行化,加速回测。

总而言之,该框架提供了一个从策略逻辑定义、信号生成、风险管理集成、回测执行到参数优化的完整解决方案,特别适合进行基于技术分析的量化策略研究与开发。

二、 带有详细中文注释的完整代码

# 导入所需库
import pandas as pd
import numpy as np
import itertools # 用于生成参数组合的笛卡尔积
import copy    # 用于深度复制字典,避免修改原始配置
import traceback # 用于打印详细错误信息

# --- 0. 模拟数据生成 ---
def generate_sample_data(days=750):
    """
    生成模拟的 OHLCV(开高低收量)时间序列数据。
    用于快速演示和测试框架功能。
    实际应用中应替换为真实数据加载逻辑。

    Args:
        days (int): 需要生成的数据天数。

    Returns:
        pd.DataFrame: 包含 'Open', 'High', 'Low', 'Close', 'Volume' 列和 DatetimeIndex 的 DataFrame。
    """
    # 创建日期序列(交易日)
    dates = pd.date_range(end=pd.Timestamp.today(), periods=days, freq='B')
    data = pd.DataFrame(index=dates)
    # 生成随机价格,并添加一个简单的线性趋势
    data['Open'] = np.random.rand(days) * 50 + 100 + np.linspace(0, 40, days)
    # 基于开盘价生成高低价
    data['High'] = data['Open'] + np.random.rand(days) * 12
    data['Low'] = data['Open'] - np.random.rand(days) * 12
    # 基于高低价生成收盘价
    data['Close'] = data['Low'] + (data['High'] - data['Low']) * np.random.rand(days)
    # 生成随机成交量,并模拟价量关系(价格波动大时成交量倾向于放大)
    data['Volume'] = np.random.randint(10000, 60000, size=days) * (1 + (data['Close'] - data['Open']) / data['Open'] * 5).abs()
    # 保证 H >= O/C, L <= O/C
    data['High'] = data[['High', 'Open', 'Close']].max(axis=1)
    data['Low'] = data[['Low', 'Open', 'Close']].min(axis=1)
    # 对价格数据进行简单平滑,使其更像真实市场数据
    for col in ['Open', 'High', 'Low', 'Close']:
         data[col] = data[col].rolling(window=3, min_periods=1).mean()
    # 删除因滚动计算产生的初始 NaN 值
    data.dropna(inplace=True)
    return data

# --- 1. 信号因子库 ---
# 每个函数代表一个信号生成逻辑,输入数据和参数,输出信号序列 (1:买, -1:卖, 0:无)
# 或者过滤信号序列 (1:满足, 0:不满足)

def signal_from_sma_cross(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于价格穿越简单移动平均线 (SMA) 生成交易信号。
    金叉 (价格上穿均线) 为买入信号 (1),死叉 (价格下穿均线) 为卖出信号 (-1)。

    Args:
        data (pd.DataFrame): 包含 'Close' 列的市场数据。
        params (dict): 参数字典,应包含 'window' (SMA 的计算窗口期)。

    Returns:
        pd.Series: 交易信号序列。
    """
    window = params.get('window', 20) # 获取窗口期参数,默认20
    # 计算 SMA
    indicator = data['Close'].rolling(window=window, min_periods=window).mean()
    # 初始化信号序列,默认为 0 (无信号)
    signal = pd.Series(index=data.index, data=0.0)
    # 判断金叉:昨日收盘价 < 昨日SMA 且 今日收盘价 > 今日SMA
    signal[ (data['Close'].shift(1) < indicator.shift(1)) & (data['Close'] > indicator) ] = 1
    # 判断死叉:昨日收盘价 > 昨日SMA 且 今日收盘价 < 今日SMA
    signal[ (data['Close'].shift(1) > indicator.shift(1)) & (data['Close'] < indicator) ] = -1
    # 填充可能因 shift 产生的 NaN 为 0
    return signal.fillna(0)

def signal_from_ema_cross(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于价格穿越指数移动平均线 (EMA) 生成交易信号。
    逻辑与 SMA 交叉类似。

    Args:
        data (pd.DataFrame): 包含 'Close' 列的市场数据。
        params (dict): 参数字典,应包含 'window' (EMA 的计算窗口期/span)。

    Returns:
        pd.Series: 交易信号序列。
    """
    window = params.get('window', 20) # 获取窗口期参数,默认20
    # 计算 EMA
    indicator = data['Close'].ewm(span=window, adjust=False, min_periods=window).mean()
    signal = pd.Series(index=data.index, data=0.0)
    # 金叉
    signal[ (data['Close'].shift(1) < indicator.shift(1)) & (data['Close'] > indicator) ] = 1
    # 死叉
    signal[ (data['Close'].shift(1) > indicator.shift(1)) & (data['Close'] < indicator) ] = -1
    return signal.fillna(0)

def signal_from_rsi_threshold(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于相对强弱指数 (RSI) 的阈值穿越生成交易信号。

    Args:
        data (pd.DataFrame): 包含 'Close' 列的市场数据。
        params (dict): 参数字典,包含:
            'window' (int): RSI 计算窗口期。
            'upper_threshold' (int): 超买阈值。
            'lower_threshold' (int): 超卖阈值。
            'logic' (str): 'cross_from_bound' (从区域外穿越阈值) 或 'enter_zone' (进入区域)。

    Returns:
        pd.Series: 交易信号序列。
    """
    window = params.get('window', 14)
    upper_threshold = params.get('upper_threshold', 70)
    lower_threshold = params.get('lower_threshold', 30)
    logic = params.get('logic', 'cross_from_bound') # 默认使用穿越逻辑

    # --- RSI 计算 ---
    delta = data['Close'].diff()
    gain = (delta.where(delta > 0, 0.0)).rolling(window=window, min_periods=window).mean()
    loss = (-delta.where(delta < 0, 0.0)).rolling(window=window, min_periods=window).mean()
    loss = loss.replace(0, 1e-10) # 避免除以零错误
    rs = gain / loss
    indicator = 100.0 - (100.0 / (1.0 + rs))
    # --- End RSI 计算 ---

    signal = pd.Series(index=data.index, data=0.0)

    if logic == 'cross_from_bound':
        # 从超卖区下方上穿阈值 -> 买入信号
        signal[ (indicator.shift(1) <= lower_threshold) & (indicator > lower_threshold) ] = 1
        # 从超买区上方下穿阈值 -> 卖出信号
        signal[ (indicator.shift(1) >= upper_threshold) & (indicator < upper_threshold) ] = -1
    elif logic == 'enter_zone': # 进入超卖/超买区即给信号 (通常需要额外平仓逻辑)
        signal[ indicator < lower_threshold ] = 1
        signal[ indicator > upper_threshold ] = -1
    else:
        raise ValueError(f"未知的RSI信号逻辑: {logic}")

    return signal.fillna(0)

def signal_from_macd_cross(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于 MACD 指标的快线 (MACD Line) 与慢线 (Signal Line) 的交叉生成交易信号。
    金叉 (快线上穿慢线) 为买入信号 (1),死叉 (快线下穿慢线) 为卖出信号 (-1)。

    Args:
        data (pd.DataFrame): 包含 'Close' 列的市场数据。
        params (dict): 参数字典,包含:
            'fast_window' (int): 快线 EMA 周期。
            'slow_window' (int): 慢线 EMA 周期。
            'signal_window' (int): 信号线 EMA 周期。

    Returns:
        pd.Series: 交易信号序列。
    """
    fast_window = params.get('fast_window', 12)
    slow_window = params.get('slow_window', 26)
    signal_window = params.get('signal_window', 9)

    # --- MACD 计算 ---
    ema_fast = data['Close'].ewm(span=fast_window, adjust=False).mean()
    ema_slow = data['Close'].ewm(span=slow_window, adjust=False).mean()
    macd_line = ema_fast - ema_slow
    signal_line = macd_line.ewm(span=signal_window, adjust=False).mean()
    # --- End MACD 计算 ---

    signal = pd.Series(index=data.index, data=0.0)
    # 金叉: 昨日 MACD < 昨日 Signal 且 今日 MACD > 今日 Signal
    signal[ (macd_line.shift(1) < signal_line.shift(1)) & (macd_line > signal_line) ] = 1
    # 死叉: 昨日 MACD > 昨日 Signal 且 今日 MACD < 今日 Signal
    signal[ (macd_line.shift(1) > signal_line.shift(1)) & (macd_line < signal_line) ] = -1
    return signal.fillna(0)

def signal_from_bbands_breakout(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于价格突破布林带 (Bollinger Bands) 上下轨生成信号。
    突破上轨为买入信号 (1),跌破下轨为卖出信号 (-1)。
    注意:布林带突破常用于不同策略,如趋势跟踪(突破买入)或均值回归(突破后反向操作)。此实现为趋势跟踪用法。

    Args:
        data (pd.DataFrame): 包含 'Close' 列的市场数据。
        params (dict): 参数字典,包含:
            'window' (int): 计算均线和标准差的窗口期。
            'num_std_dev' (float): 标准差倍数。

    Returns:
        pd.Series: 交易信号序列。
    """
    window = params.get('window', 20)
    num_std_dev = params.get('num_std_dev', 2.0)

    # --- 布林带计算 ---
    close_sma = data['Close'].rolling(window=window, min_periods=window).mean()
    close_std = data['Close'].rolling(window=window, min_periods=window).std()
    upper_band = close_sma + (close_std * num_std_dev)
    lower_band = close_sma - (close_std * num_std_dev)
    # --- End 布林带计算 ---

    signal = pd.Series(index=data.index, data=0.0)
    # 突破上轨: 昨日收盘价 <= 昨日上轨 且 今日收盘价 > 今日上轨
    signal[ (data['Close'].shift(1) <= upper_band.shift(1)) & (data['Close'] > upper_band) ] = 1
    # 跌破下轨: 昨日收盘价 >= 昨日下轨 且 今日收盘价 < 今日下轨
    signal[ (data['Close'].shift(1) >= lower_band.shift(1)) & (data['Close'] < lower_band) ] = -1
    return signal.fillna(0)

def signal_from_donchian_breakout(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于价格突破唐奇安通道 (Donchian Channel) 生成信号(类似海龟交易法则入场)。
    价格创 N 日新高 (突破上轨) 为买入信号 (1),价格创 N 日新低 (跌破下轨) 为卖出信号 (-1)。
    通常使用前一日的通道进行判断。

    Args:
        data (pd.DataFrame): 包含 'High', 'Low' 列的市场数据。
        params (dict): 参数字典,包含 'window' (int): 通道计算窗口期。

    Returns:
        pd.Series: 交易信号序列。
    """
    window = params.get('window', 20)
    # 计算前一日的 N 日最高价 (上轨)
    upper_band = data['High'].shift(1).rolling(window=window, min_periods=window).max()
    # 计算前一日的 N 日最低价 (下轨)
    lower_band = data['Low'].shift(1).rolling(window=window, min_periods=window).min()

    signal = pd.Series(index=data.index, data=0.0)
    # 突破上轨: 今日最高价 > 昨日 N 日最高价
    signal[ data['High'] > upper_band ] = 1
    # 跌破下轨: 今日最低价 < 昨日 N 日最低价
    signal[ data['Low'] < lower_band ] = -1
    # 处理可能的信号冲突(例如日内同时突破上下轨),这里简单处理,优先买入(信号1)
    # 更复杂的处理可能需要日内逻辑或延迟判断
    return signal.fillna(0)

# --- 过滤器信号因子 (返回 1 表示条件满足, 0 表示不满足) ---

def filter_signal_from_adx_strength(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于 ADX 指标判断趋势强度,生成过滤信号。
    当 ADX 大于指定阈值时,表示趋势明显,过滤器信号为 1;否则为 0。

    Args:
        data (pd.DataFrame): 包含 'High', 'Low', 'Close' 列的市场数据。
        params (dict): 参数字典,包含:
            'window' (int): ADX 计算窗口期。
            'threshold' (float): ADX 强度阈值。

    Returns:
        pd.Series: 过滤信号序列 (1 或 0)。
    """
    window = params.get('window', 14)
    threshold = params.get('threshold', 25.0)

    # --- ADX 计算 (简化版 Wilder's Smoothing) ---
    # 参考 TA-Lib 或其他可靠库获取更精确的实现
    move_up = data['High'].diff()
    move_down = -data['Low'].diff()
    plus_dm = np.where((move_up > move_down) & (move_up > 0), move_up, 0.0)
    minus_dm = np.where((move_down > move_up) & (move_down > 0), move_down, 0.0)
    # TR (True Range)
    high_low = data['High'] - data['Low']
    high_close_prev = np.abs(data['High'] - data['Close'].shift(1))
    low_close_prev = np.abs(data['Low'] - data['Close'].shift(1))
    tr = pd.concat([high_low, high_close_prev, low_close_prev], axis=1).max(axis=1, skipna=False)
    # ATR (Average True Range) using Wilder's smoothing (SMA for initial value, then EMA with alpha=1/N)
    atr = tr.ewm(alpha=1/window, adjust=False, min_periods=window).mean()
    atr = atr.replace(0, 1e-10) # 防御除零错误
    # +DI / -DI
    plus_di = 100.0 * (pd.Series(plus_dm).ewm(alpha=1/window, adjust=False, min_periods=window).mean() / atr)
    minus_di = 100.0 * (pd.Series(minus_dm).ewm(alpha=1/window, adjust=False, min_periods=window).mean() / atr)
    # DX
    di_sum = (plus_di + minus_di).replace(0, 1e-10) # 防御除零错误
    dx = 100.0 * (np.abs(plus_di - minus_di) / di_sum)
    # ADX (Smoothed DX)
    adx = dx.ewm(alpha=1/window, adjust=False, min_periods=window * 2 - 1).mean() # ADX通常需要更长的平滑期
    # --- End ADX Calculation ---

    filter_signal = pd.Series(index=data.index, data=0.0)
    # 当 ADX 大于阈值时,过滤器信号为 1
    filter_signal[adx > threshold] = 1
    return filter_signal.fillna(0)

def filter_signal_from_volume_increase(data: pd.DataFrame, params: dict) -> pd.Series:
    """
    基于当前成交量是否高于其移动平均值生成过滤信号。
    当成交量高于均线时,过滤器信号为 1;否则为 0。

    Args:
        data (pd.DataFrame): 包含 'Volume' 列的市场数据。
        params (dict): 参数字典,包含 'window' (int): 成交量均线计算窗口期。

    Returns:
        pd.Series: 过滤信号序列 (1 或 0)。
    """
    window = params.get('window', 20)
    # 计算成交量的简单移动平均
    volume_sma = data['Volume'].rolling(window=window, min_periods=window).mean()
    filter_signal = pd.Series(index=data.index, data=0.0)
    # 当今日成交量 > 今日成交量均线时,信号为 1
    filter_signal[data['Volume'] > volume_sma] = 1
    return filter_signal.fillna(0)


# --- 信号因子注册表 ---
# 将所有信号因子函数映射到名称,方便动态调用
SIGNAL_FACTOR_REGISTRY = {
    # 主信号因子
    'sma_cross': signal_from_sma_cross,
    'ema_cross': signal_from_ema_cross,
    'rsi_threshold': signal_from_rsi_threshold,
    'macd_cross': signal_from_macd_cross,
    'bbands_breakout': signal_from_bbands_breakout,
    'donchian_breakout': signal_from_donchian_breakout,
    # 过滤信号因子
    'adx_strength_filter': filter_signal_from_adx_strength,
    'volume_increase_filter': filter_signal_from_volume_increase,
}


# --- 2. 信号组合与过滤层 ---
def generate_combined_signals(data: pd.DataFrame,
                              main_signal_config: dict,
                              filter_signal_configs: list = None):
    """
    组合主信号和过滤信号,生成最终的交易信号。

    Args:
        data (pd.DataFrame): 原始市场数据 (OHLCV)。
        main_signal_config (dict): 主信号的配置,格式如 {'name': '因子名称', 'params': {参数字典}}。
        filter_signal_configs (list, optional): 过滤信号的配置列表,每个元素格式同 main_signal_config。

    Returns:
        pd.Series: 最终合并后的交易信号序列 (1: 买, -1: 卖, 0: 无)。
    """
    # 获取主信号配置
    main_signal_name = main_signal_config['name']
    main_params = main_signal_config.get('params', {})

    # 检查主信号因子是否已注册
    if main_signal_name not in SIGNAL_FACTOR_REGISTRY:
        raise ValueError(f"未注册的主信号因子: {main_signal_name}")

    # --- 1. 生成主信号 ---
    main_signal_func = SIGNAL_FACTOR_REGISTRY[main_signal_name]
    # 调用因子函数,传入数据的副本以防被修改(虽然良好实践是不修改输入)
    main_signal = main_signal_func(data.copy(), main_params)

    # 检查返回值是否有效
    if main_signal is None or not isinstance(main_signal, pd.Series):
         raise ValueError(f"主信号因子 {main_signal_name} 未返回有效的 Pandas Series")

    # --- 2. 应用过滤信号 ---
    final_signal = main_signal.copy() # 从主信号开始
    if filter_signal_configs: # 如果配置了过滤器
        for filter_config in filter_signal_configs:
            filter_name = filter_config['name']
            filter_params = filter_config.get('params', {})

            # 检查过滤器因子是否已注册
            if filter_name not in SIGNAL_FACTOR_REGISTRY:
                print(f"警告: 未注册的过滤信号因子: {filter_name},将跳过此过滤器。")
                continue

            filter_func = SIGNAL_FACTOR_REGISTRY[filter_name]
            # 调用过滤器因子函数获取过滤信号 (1 或 0)
            filter_signal = filter_func(data.copy(), filter_params) # 同样使用副本

            # 检查返回值
            if filter_signal is None or not isinstance(filter_signal, pd.Series):
                print(f"警告: 过滤信号因子 {filter_name} 未返回有效的 Pandas Series,将跳过此过滤器。")
                continue

            # 应用过滤:只有当过滤信号为 1 时,主信号才有效,否则最终信号置为 0
            # 这里假设所有过滤器都必须满足 (AND 逻辑)
            final_signal[filter_signal == 0] = 0

    # 填充可能产生的 NaN 为 0
    final_signal = final_signal.fillna(0)

    return final_signal


# --- 3. 回测引擎 (包含止盈止损) ---
def run_backtest(data: pd.DataFrame, signals: pd.Series,
                 initial_capital: float = 100000.0,
                 commission_rate: float = 0.0005, # 单边手续费率 (万分之五)
                 slippage: float = 0.0002,       # 单边滑点率 (万分之二)
                 stop_loss_config: dict = None,  # 止损配置字典
                 take_profit_config: dict = None, # 止盈配置字典
                 time_stop_bars: int = None):     # 时间止损 K 线数量
    """
    执行基于信号的回测,并集成止盈、止损、时间止损等风险管理规则。

    Args:
        data (pd.DataFrame): 包含 OHLCV 数据的 DataFrame。
        signals (pd.Series): 最终的交易信号序列 (1: 买, -1: 卖, 0: 无)。
        initial_capital (float): 初始资金。
        commission_rate (float): 单边手续费率。
        slippage (float): 单边滑点率 (模拟成交价的不利变动)。
        stop_loss_config (dict, optional): 止损配置, e.g.,
            {'type': 'percentage', 'value': 0.05} # 百分比止损
            {'type': 'atr', 'multiplier': 2.0, 'atr_params': {'window': 14}} # ATR止损
            {'type': 'trailing', 'value': 0.10} # 追踪止损
        take_profit_config (dict, optional): 止盈配置, e.g.,
            {'type': 'percentage', 'value': 0.10} # 百分比止盈
        time_stop_bars (int, optional): 持仓达到指定 K 线数后强制平仓。

    Returns:
        tuple: 包含:
            pd.Series: 每日投资组合净值序列。
            pd.DataFrame: 交易记录 DataFrame。
            dict: 回测绩效总结字典。
    """
    # --- 初始化状态变量 ---
    capital = initial_capital       # 当前现金
    position = 0                    # 当前持仓数量 (股/手, >0 多头, <0 空头(暂未支持), 0 空仓)
    entry_price = 0.0               # 当前持仓的入场均价
    entry_date_index = -1           # 入场时的 K 线索引 (用于时间止损)
    highest_price_since_entry = 0.0 # 入场后的最高价 (用于追踪止损)
    current_atr = 0.0               # 入场时的 ATR 值 (用于 ATR 止损)
    portfolio_value = pd.Series(index=data.index, dtype=float) # 记录每日组合净值
    trades = []                     # 记录交易详情列表

    # --- 数据准备 ---
    # 复制数据以防修改原始 DataFrame
    df_for_backtest = data.copy()
    # 检查是否需要 ATR 止损,如果需要且数据中没有 'atr' 列,则计算
    use_atr_stop = (stop_loss_config and stop_loss_config.get('type') == 'atr')
    if use_atr_stop and 'atr' not in df_for_backtest.columns:
        print("回测引擎: 检测到 ATR 止损配置,但数据缺少 'atr' 列,正在计算...")
        # --- ATR 计算 (如果需要) ---
        atr_params = stop_loss_config.get('atr_params', {})
        atr_window = atr_params.get('window', 14)
        high_low = df_for_backtest['High'] - df_for_backtest['Low']
        high_close_prev = np.abs(df_for_backtest['High'] - df_for_backtest['Close'].shift(1))
        low_close_prev = np.abs(df_for_backtest['Low'] - df_for_backtest['Close'].shift(1))
        tr = pd.concat([high_low, high_close_prev, low_close_prev], axis=1).max(axis=1)
        df_for_backtest['atr'] = tr.ewm(alpha=1/atr_window, adjust=False, min_periods=atr_window).mean()
        # 向前填充初始的 NaN 值,确保早期 K 线也有 ATR 参考
        df_for_backtest['atr'] = df_for_backtest['atr'].fillna(method='bfill')
        print("回测引擎: ATR 计算完成。")
        # --- End ATR Calculation ---

    # --- 逐 K 线回测循环 ---
    for i in range(len(df_for_backtest)):
        current_date = df_for_backtest.index[i]
        # 获取当前 K 线的价格信息
        current_open = df_for_backtest['Open'].iloc[i]
        current_high = df_for_backtest['High'].iloc[i]
        current_low = df_for_backtest['Low'].iloc[i]
        current_close = df_for_backtest['Close'].iloc[i]
        # 获取当天的最终交易信号
        current_signal = signals.iloc[i]

        # --- 0. 开盘前计算当日初始组合价值 ---
        # 如果持有仓位,价值 = 现金 + 持仓市值 (按开盘价计)
        if position > 0:
            portfolio_value.iloc[i] = capital + position * current_open
        # 如果空仓,价值 = 现金
        else:
            portfolio_value.iloc[i] = capital

        # --- 1. 检查是否需要平仓 (止盈/止损/时间/策略信号) ---
        # 仅当持有仓位时检查
        exit_signal_triggered = False # 标记是否触发了任何平仓条件
        exit_reason = ""             # 记录平仓原因
        exit_price = 0.0             # 记录平仓成交价

        if position > 0: # 假设只做多
            # 更新持仓期间遇到的最高价
            highest_price_since_entry = max(highest_price_since_entry, current_high)

            # a) 检查止损
            if stop_loss_config:
                sl_type = stop_loss_config.get('type')
                sl_value = stop_loss_config.get('value')
                sl_multiplier = stop_loss_config.get('multiplier')
                stop_price = -np.inf # 初始化止损价

                if sl_type == 'percentage' and sl_value:
                    stop_price = entry_price * (1.0 - sl_value)
                    exit_reason = f"止损(百分比 {sl_value*100:.1f}%)"
                elif sl_type == 'atr' and sl_multiplier and current_atr > 0:
                    # 使用入场时的 ATR 计算固定止损位
                    stop_price = entry_price - (current_atr * sl_multiplier)
                    exit_reason = f"止损(ATR {sl_multiplier:.1f}x)"
                elif sl_type == 'trailing' and sl_value:
                    # 追踪止损价 = 入场后最高价 * (1 - 追踪比例)
                    stop_price = highest_price_since_entry * (1.0 - sl_value)
                    exit_reason = f"追踪止损({sl_value*100:.1f}%)"

                # 如果当日最低价触及或跌破止损价
                if current_low <= stop_price:
                    exit_signal_triggered = True
                    # 模拟以止损价成交,考虑滑点 (对卖出不利)
                    # 实际成交价可能是止损价或开盘价中对我们更不利的那个(如果开盘就跳空低于止损价)
                    exit_price = min(stop_price, current_open) * (1.0 - slippage)

            # b) 检查止盈 (如果未触发止损)
            if not exit_signal_triggered and take_profit_config:
                tp_type = take_profit_config.get('type')
                tp_value = take_profit_config.get('value')
                target_price = np.inf # 初始化止盈目标价

                if tp_type == 'percentage' and tp_value:
                    target_price = entry_price * (1.0 + tp_value)
                    exit_reason = f"止盈(百分比 {tp_value*100:.1f}%)"
                # 可以扩展其他止盈类型,如固定点数、ATR倍数等

                # 如果当日最高价触及或超过止盈价
                if current_high >= target_price:
                    exit_signal_triggered = True
                    # 模拟以止盈价成交,考虑滑点 (对卖出不利)
                    # 实际成交价可能是止盈价或开盘价中对我们更有利的那个(如果开盘就跳空高于目标价)
                    exit_price = max(target_price, current_open) * (1.0 - slippage)


            # c) 检查时间止损 (如果都未触发)
            # i 是当前 K 线索引,entry_date_index 是入场 K 线索引
            if not exit_signal_triggered and time_stop_bars and (i - entry_date_index) >= time_stop_bars:
                exit_signal_triggered = True
                exit_reason = f"时间止损({time_stop_bars} K线)"
                # 时间止损通常在满足条件的下一根 K 线开盘执行
                if i + 1 < len(df_for_backtest):
                    exit_price = df_for_backtest['Open'].iloc[i+1] * (1.0 - slippage)
                else: # 如果是最后一天
                    exit_price = current_close * (1.0 - slippage) # 以当日收盘价退出

            # d) 检查策略本身的卖出信号 (如果都未触发)
            # 策略信号通常也在下一根 K 线开盘执行
            if not exit_signal_triggered and current_signal == -1:
                # 检查是否不是最后一天,避免索引越界
                if i + 1 < len(df_for_backtest):
                    exit_signal_triggered = True
                    exit_reason = "策略信号"
                    exit_price = df_for_backtest['Open'].iloc[i+1] * (1.0 - slippage)
                # 如果是最后一天产生的卖出信号,也需要处理
                elif i == len(df_for_backtest) - 1:
                     exit_signal_triggered = True
                     exit_reason = "策略信号(最后一天)"
                     exit_price = current_close * (1.0 - slippage)

        # --- 2. 执行平仓交易 ---
        if exit_signal_triggered and position > 0:
            # 确保退出价格有效 (例如时间止损在最后一天时 exit_price 可能未设置)
            if exit_price <= 0:
                exit_price = current_close * (1.0 - slippage) # 保底用收盘价

            # 计算卖出收入 (扣除手续费)
            revenue = position * exit_price * (1.0 - commission_rate)
            capital += revenue # 更新现金
            # 记录交易详情
            # 交易日期通常记录为实际执行的日期 (下一天开盘或当天触发)
            trade_exec_date = df_for_backtest.index[i+1] if exit_reason not in ["策略信号(最后一天)"] and i + 1 < len(df_for_backtest) else current_date
            trades.append((trade_exec_date, 'SELL', exit_price, position, revenue, exit_reason))
            # print(f"{trade_exec_date.date()} SELL ({exit_reason}) @ {exit_price:.2f}, Qty: {position}, Rev: {revenue:.2f}, Cap: {capital:.2f}")

            # 重置持仓状态
            position = 0
            entry_price = 0.0
            entry_date_index = -1
            highest_price_since_entry = 0.0
            current_atr = 0.0

        # --- 3. 检查并执行入场交易 (仅当当前无持仓时) ---
        # 策略的买入信号通常也在下一根 K 线开盘执行
        if position == 0 and current_signal == 1:
             # 检查是否不是最后一天
             if i + 1 < len(df_for_backtest):
                # 获取下一根 K 线的开盘价作为理论入场价
                trade_price = df_for_backtest['Open'].iloc[i+1]
                # 计算实际买入价 (考虑滑点,对买入不利)
                buy_price = trade_price * (1.0 + slippage)
                # 简化:全仓买入 (实际应考虑资金管理和仓位控制)
                # 计算理论上能买多少股/手
                quantity_to_buy = int(capital / buy_price)
                # 计算买入成本 (包含手续费)
                cost = quantity_to_buy * buy_price * (1.0 + commission_rate)

                # 确保资金充足且能买入至少1单位
                if capital >= cost and quantity_to_buy > 0:
                    # 更新状态
                    position = quantity_to_buy
                    capital -= cost
                    entry_price = buy_price # 记录入场成本价
                    entry_date_index = i + 1 # 记录入场 K 线的索引
                    # 用入场当天的最高价初始化追踪止损用的最高价
                    highest_price_since_entry = df_for_backtest['High'].iloc[i+1]
                    # 如果使用 ATR 止损,记录入场时的 ATR 值
                    if use_atr_stop and 'atr' in df_for_backtest.columns:
                        entry_atr = df_for_backtest['atr'].iloc[i+1]
                        # 处理可能的 NaN 值
                        current_atr = entry_atr if pd.notna(entry_atr) else 0
                    # 记录交易
                    trades.append((df_for_backtest.index[i+1], 'BUY', buy_price, quantity_to_buy, cost, "策略信号"))
                    # print(f"{df_for_backtest.index[i+1].date()} BUY @ {buy_price:.2f}, Qty: {quantity_to_buy}, Cost: {cost:.2f}, Cap Left: {capital:.2f}")


        # --- 4. 收盘后更新当日最终组合价值 ---
        # 如果持有仓位,价值 = 现金 + 持仓市值 (按收盘价计)
        if position > 0:
            portfolio_value.iloc[i] = capital + position * df_for_backtest['Close'].iloc[i]
        # 如果空仓,价值 = 现金
        else:
            portfolio_value.iloc[i] = capital

        # --- 5. 处理 NaN (主要发生在回测初期) ---
        # 如果当前价值是 NaN,用前一天的价值填充
        if i > 0 and pd.isna(portfolio_value.iloc[i]):
             portfolio_value.iloc[i] = portfolio_value.iloc[i-1]
        # 如果第一天就是 NaN,用初始资金填充
        elif i == 0 and pd.isna(portfolio_value.iloc[i]):
             portfolio_value.iloc[i] = initial_capital

    # --- 回测循环结束 ---

    # --- 整理交易记录 ---
    trades_df = pd.DataFrame(trades, columns=['Date', 'Type', 'Price', 'Quantity', 'Amount', 'Reason'])
    if not trades_df.empty:
         # 将日期设为索引
         trades_df.set_index('Date', inplace=True)

    # --- 填充资金曲线开头的 NaN (如果还有) ---
    # 向前填充,确保资金曲线连续
    portfolio_value.ffill(inplace=True)
    # 如果最开始仍有 NaN,用初始资金填充
    portfolio_value.fillna(initial_capital, inplace=True)

    # --- 计算绩效指标 ---
    performance_summary = evaluate_performance(portfolio_value, trades_df, initial_capital)

    # 返回结果
    return portfolio_value, trades_df, performance_summary


# --- 4. 绩效评估层 ---
def evaluate_performance(portfolio_value: pd.Series, trades_df: pd.DataFrame, initial_capital: float) -> dict:
    """
    根据资金曲线和交易记录计算常用的回测绩效指标。

    Args:
        portfolio_value (pd.Series): 每日组合净值序列。
        trades_df (pd.DataFrame): 交易记录 DataFrame。
        initial_capital (float): 初始资金。

    Returns:
        dict: 包含各项绩效指标的字典。
    """
    # 防御性检查:如果资金曲线为空或无效,返回默认值
    if portfolio_value.empty or portfolio_value.isnull().all() or len(portfolio_value) < 2:
        return {
            '总回报率': 0.0, '年化回报率': 0.0, '夏普比率': 0.0,
            '最大回撤': 0.0, '胜率': 0.0, '盈亏比': 'N/A',
            '总交易次数': 0, '最终组合价值': initial_capital
        }

    # --- 计算年化相关的交易日数 ---
    try:
        # 尝试推断数据频率
        trading_days_per_year = 252 # 默认按中国股市交易日数
        time_diff = portfolio_value.index.to_series().diff().median()
        # 如果数据是日历日,使用 365
        if time_diff == pd.Timedelta(days=1):
            trading_days_per_year = 365
        # 这里可以根据需要添加对其他频率(周、月、小时)的判断
    except Exception:
        trading_days_per_year = 252 # 推断失败,使用默认值

    # --- 计算核心指标 ---
    # 总回报率 = (最终价值 / 初始价值) - 1
    final_value = portfolio_value.iloc[-1]
    total_return = (final_value / initial_capital) - 1.0

    # 年化回报率 = (1 + 总回报率)^(1 / 年数) - 1
    start_date = portfolio_value.index[0]
    end_date = portfolio_value.index[-1]
    years = (end_date - start_date).days / 365.25 # 计算回测覆盖的总年数
    # 避免年数为0或负数
    if years <= 0:
        annualized_return = total_return # 如果时间太短,年化无意义,显示总回报
    else:
        annualized_return = ((1.0 + total_return) ** (1.0 / years)) - 1.0

    # 夏普比率 = (年化(平均日收益率 - 无风险利率)) / 年化(日收益率标准差)
    # 假设无风险利率为 0
    daily_returns = portfolio_value.pct_change().dropna()
    # 检查是否有有效的日收益率数据且标准差不为0
    if not daily_returns.empty and daily_returns.std() > 1e-8:
        # 年化波动率 = 日标准差 * sqrt(年交易日数)
        # 年化收益率 = 日均收益率 * 年交易日数 (近似)
        # 夏普 = (日均收益 / 日标准差) * sqrt(年交易日数)
        sharpe_ratio = (daily_returns.mean() / daily_returns.std()) * np.sqrt(trading_days_per_year)
    else:
        sharpe_ratio = 0.0 # 无法计算夏普比率

    # 最大回撤
    # 计算累积最高价值
    cumulative_max = portfolio_value.cummax()
    # 计算回撤 = (当前价值 - 累积最高价值) / 累积最高价值
    drawdown = (portfolio_value - cumulative_max) / cumulative_max.replace(0, 1e-10) # 防止除以零
    # 最大回撤是回撤序列的最小值
    max_drawdown = drawdown.min() if not drawdown.empty else 0.0

    # --- 计算交易相关指标 ---
    total_trades = 0        # 总交易次数 (一买一卖算一次)
    winning_trades = 0      # 盈利交易次数
    total_profit = 0.0      # 总盈利金额
    total_loss = 0.0        # 总亏损金额 (取绝对值)

    if not trades_df.empty:
        # 筛选出买入和卖出交易
        buy_trades = trades_df[trades_df['Type'] == 'BUY']
        sell_trades = trades_df[trades_df['Type'] == 'SELL']
        # 简化计算:假设交易严格配对 (一买对应一卖)
        num_pairs = min(len(buy_trades), len(sell_trades))
        total_trades = num_pairs

        if num_pairs > 0:
            # 遍历所有完整交易对
            for i in range(num_pairs):
                # 获取买入成本和卖出收入
                # 注意:这里的 Amount 列记录的是成本(负)或收入(正)可能更好,但当前实现是成本和收入绝对值
                # 需要根据 run_backtest 中 trades.append 的逻辑调整
                # 假设 Amount 对 BUY 是 cost,对 SELL 是 revenue
                buy_cost = buy_trades['Amount'].iloc[i] # 买入的总花费
                sell_revenue = sell_trades['Amount'].iloc[i] # 卖出的总收入
                # 计算单次交易盈亏
                profit = sell_revenue - buy_cost
                if profit > 0:
                    winning_trades += 1
                    total_profit += profit
                else:
                    # total_loss 记录亏损总额(正值)
                    total_loss += abs(profit)

    # 胜率 = 盈利次数 / 总次数
    win_rate = winning_trades / total_trades if total_trades > 0 else 0.0
    # 盈亏比 = 总盈利 / 总亏损
    # 如果没有亏损,盈亏比为无穷大
    profit_factor = total_profit / total_loss if total_loss > 0 else np.inf

    # 返回包含所有指标的字典
    return {
        '总回报率': total_return,
        '年化回报率': annualized_return,
        '夏普比率': sharpe_ratio,
        '最大回撤': max_drawdown,
        '总交易次数': total_trades,
        '胜率': win_rate,
        # 将无穷大的盈亏比表示为字符串 'inf'
        '盈亏比': f"{profit_factor:.2f}" if profit_factor != np.inf else 'inf',
        '最终组合价值': final_value
    }


# --- 5. 编排与批量优化层 ---

# --- 辅助函数 ---
def set_nested_value(d: dict, keys: list, value):
    """
    根据键的列表 (路径) 安全地设置嵌套字典的值。
    如果中间的键不存在,会自动创建空字典。

    Args:
        d (dict): 目标字典。
        keys (list): 字符串列表,表示从外到内的键。
        value: 要设置的值。
    """
    # 遍历到倒数第二个键
    for key in keys[:-1]:
        # 如果键不存在,或对应的值不是字典,则创建一个新字典
        if key not in d or not isinstance(d[key], dict):
            d[key] = {}
        d = d[key] # 进入下一层
    # 设置最后一个键的值
    d[keys[-1]] = value

def generate_config_combinations(base_config: dict, param_grid: dict) -> list:
    """
    根据基础配置模板和参数网格,生成所有可能的具体配置组合。

    Args:
        base_config (dict): 策略的基础配置模板字典。
        param_grid (dict): 参数网格字典。
            键 (str): 参数在 base_config 中的路径,用 '.' 分隔嵌套层级 (e.g., 'main_signal.params.window')。
                      对于列表中的字典,使用索引 (e.g., 'filters.0.params.threshold')。
            值 (list): 该参数的所有候选值列表 (e.g., [10, 20, 30])。

    Returns:
        list: 包含所有具体配置字典的列表。每个字典都是一个可用于回测的完整配置。
    """
    # 如果参数网格为空,直接返回基础配置(不需要优化)
    if not param_grid:
        return [base_config]

    # 获取参数网格中的所有参数名称和对应的候选值列表
    param_names = list(param_grid.keys())
    param_values_lists = list(param_grid.values())

    config_list = [] # 用于存储所有生成的具体配置
    # 使用 itertools.product 计算所有参数候选值列表的笛卡尔积
    # 例如,如果 param_values_lists = [[10, 20], [0.05, 0.07]]
    # 则 product 会生成 (10, 0.05), (10, 0.07), (20, 0.05), (20, 0.07)
    for current_combination_values in itertools.product(*param_values_lists):
        # 对每个参数值的组合:
        # 1. 创建基础配置的深层副本,确保每个组合的配置是独立的
        specific_config = copy.deepcopy(base_config)
        # 2. 将当前组合中的值,根据对应的参数名路径,设置到副本配置中
        for i, param_name in enumerate(param_names):
            keys = param_name.split('.') # 将路径字符串按 '.' 分割成键的列表
            value_to_set = current_combination_values[i] # 获取当前组合中该参数的值
            set_nested_value(specific_config, keys, value_to_set) # 设置到配置副本中
        # 3. 将这个具体的配置字典添加到列表中
        config_list.append(specific_config)

    return config_list

# --- 核心执行函数 ---
def run_single_strategy(data: pd.DataFrame, config: dict, initial_capital: float = 100000.0):
    """
    执行单次完整的策略回测。
    接收一个具体的、完整的策略配置字典。

    Args:
        data (pd.DataFrame): 原始市场数据 (OHLCV)。
        config (dict): 具体的策略配置字典,包含信号、过滤器、风险管理等所有参数。
        initial_capital (float): 初始资金。

    Returns:
        dict: 包含该配置的回测结果,格式为 {'config': 配置字符串, 'performance': 绩效字典}。
              可选地,可以修改此函数返回 portfolio_value 和 trades_df 用于详细分析。
    """
    # 复制数据,以防信号生成或回测修改原始数据
    df = data.copy()

    # 1. 根据配置生成最终交易信号
    signals = generate_combined_signals(
        data=df, # 传递副本,因子函数可能会在内部计算指标
        main_signal_config=config['main_signal'],
        filter_signal_configs=config.get('filters') # filters 是可选的
    )

    # 2. 执行回测
    portfolio_value, trades_df, performance_summary = run_backtest(
        data=df, # 传递原始数据副本以获取价格
        signals=signals, # 使用组合后的信号
        initial_capital=initial_capital,
        # 从配置中获取交易成本和风险管理参数,如果缺失则使用默认值
        commission_rate=config.get('commission_rate', 0.0005),
        slippage=config.get('slippage', 0.0002),
        stop_loss_config=config.get('stop_loss'),
        take_profit_config=config.get('take_profit'),
        time_stop_bars=config.get('time_stop')
    )

    # 3. 准备返回结果
    # 将配置字典转换为字符串,方便在结果 DataFrame 中存储和显示
    clean_config = {k: str(v) for k, v in config.items()}

    result = {
        'config': clean_config,          # 字符串化的配置信息
        'performance': performance_summary # 绩效指标字典
        # --- 可选返回项 ---
        # 'portfolio_curve': portfolio_value, # 资金曲线序列
        # 'trades': trades_df               # 交易记录 DataFrame
    }
    return result


def batch_optimize_strategy(data: pd.DataFrame,
                            base_config: dict,
                            param_grid: dict,
                            initial_capital: float = 100000.0,
                            optimize_metric: str = '夏普比率'):
    """
    执行参数优化:批量回测给定策略基础配置下的所有参数组合。

    Args:
        data (pd.DataFrame): 原始市场数据 (OHLCV)。
        base_config (dict): 策略的基础配置模板。
        param_grid (dict): 参数网格字典,定义要优化的参数及其候选值。
        initial_capital (float): 初始资金。
        optimize_metric (str): 用于对结果进行排序的绩效指标名称 (应与 evaluate_performance 返回的键匹配)。

    Returns:
        pd.DataFrame: 包含每个参数组合的配置信息和绩效结果的 DataFrame,
                      按指定的 optimize_metric 降序排列。
    """
    # 1. 使用辅助函数生成所有待测试的具体配置列表
    specific_configs = generate_config_combinations(base_config, param_grid)
    total_configs = len(specific_configs)
    print(f"参数优化开始,共需测试 {total_configs} 种参数组合...")

    all_results = [] # 用于存储每次回测的结果

    # 2. 遍历所有生成的具体配置
    for i, config in enumerate(specific_configs):
        print(f"\n--- 正在测试组合 {i+1}/{total_configs} ---")

        # 打印当前测试组合中变化的参数值,方便跟踪进度
        current_params_desc = {}
        for param_name in param_grid.keys():
            keys = param_name.split('.')
            value = config
            try: # 安全地获取嵌套字典的值
                for key in keys: value = value[key]
                current_params_desc[param_name] = value
            except (KeyError, TypeError): # 处理路径错误或中间不是字典的情况
                current_params_desc[param_name] = "获取失败"
        print(f"当前参数: {current_params_desc}")

        try:
            # 对当前配置执行单次回测
            # 传入数据的副本,确保每次回测基于原始数据
            result = run_single_strategy(data.copy(), config, initial_capital)

            # 准备将结果存入 DataFrame 的行数据
            # flat_result = {}
            # 将当前变化的参数作为列(可选,如果参数路径不复杂)
            # for k, v in current_params_desc.items():
            #    flat_result[f"param_{k.replace('.', '_')}"] = v # 清理参数名作为列名

            # 或者,将完整的配置字符串作为列(更通用,但可能较长)
            flat_result = {f"config_{k}": v for k, v in result['config'].items()}

            # 合并绩效指标
            flat_result.update(result['performance'])
            all_results.append(flat_result)

            # 打印关键绩效指标
            perf = result['performance']
            print("测试完成。绩效:")
            # 优先显示优化目标指标
            print(f"  {optimize_metric}: {perf.get(optimize_metric, 'N/A')}")
            # 可以根据需要打印更多指标
            # print(f"  年化回报率: {perf.get('年化回报率', 0):.4f}")
            # print(f"  最大回撤: {perf.get('最大回撤', 0):.4f}")

        except Exception as e:
            # 如果当前配置的回测失败,记录错误信息
            print(f"组合 {i+1} 测试失败: {e}")
            traceback.print_exc() # 打印详细的错误堆栈信息
            # 记录失败信息和对应的参数
            flat_result = {f"config_{k}": str(v) for k, v in config.items()} # 记录失败时的配置
            flat_result['Error'] = str(e) # 添加错误信息列
            all_results.append(flat_result)

    # --- 3. 结果汇总与排序 ---
    # 将所有结果合并成一个 DataFrame
    results_df = pd.DataFrame(all_results)

    # 清理列名,去除可能影响后续操作的特殊字符(例如 evaluate_performance 返回的键可能包含中文)
    # 这里简单替换非字母、数字、下划线的字符为空字符串
    results_df.columns = results_df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True)

    # 根据指定的优化指标对结果进行排序
    # 清理优化指标名称以匹配清理后的列名
    sort_col = optimize_metric.replace('[^A-Za-z0-9_]+', '', regex=True)

    if sort_col in results_df.columns:
        print(f"\n按指标 '{optimize_metric}' (列名: '{sort_col}') 对结果进行排序...")
        # 在排序前,将指标列转换为数值类型,无法转换的设为 NaN
        # 将 NaN 填充为负无穷,这样在降序排序时它们会排在最后
        results_df[sort_col] = pd.to_numeric(results_df[sort_col], errors='coerce').fillna(-np.inf)
        # 按指标列降序排序
        results_df = results_df.sort_values(by=sort_col, ascending=False, na_position='last')
    else:
        print(f"警告: 找不到用于排序的指标列 '{sort_col}'。结果将不会排序。")
        # 检查是否存在 'Error' 列,如果存在,可以考虑将其排在前面或后面
        if 'Error' in results_df.columns:
            print("检测到错误列,可能影响排序。")

    return results_df


# --- 6. 示例用法 ---
if __name__ == "__main__":
    # 设置 Pandas 显示选项,方便查看 DataFrame 结果
    pd.set_option('display.max_columns', None) # 显示所有列
    pd.set_option('display.width', 2000)     # 设置显示宽度
    pd.set_option('display.max_colwidth', 100) # 设置列的最大宽度

    # 1. 加载/生成数据
    print("="*20 + " 1. 生成模拟数据 " + "="*20)
    historical_data = generate_sample_data(days=750) # 生成约 3 年的日线数据
    print(f"数据已生成,共 {len(historical_data)} 条记录。")
    print("数据预览 (前3行):")
    print(historical_data.head(3))
    print("\n")

    # 2. 定义要进行参数优化的策略的基础配置
    # 假设我们要优化一个基于 EMA 交叉,并带有 ADX 强度过滤和 ATR 止损的策略
    print("="*20 + " 2. 定义基础策略与参数网格 " + "="*20)
    base_strategy_config_to_optimize = {
        'main_signal': {
            'name': 'ema_cross', # 主信号使用 EMA 交叉
            'params': {
                'window': 20 # EMA 窗口期,将被优化
            }
        },
        'filters': [
            {
                'name': 'adx_strength_filter', # 使用 ADX 强度过滤
                'params': {
                    'window': 14,      # ADX 计算窗口期,将被优化
                    'threshold': 20    # ADX 强度阈值,将被优化
                }
            }
        ],
        'stop_loss': {
            'type': 'atr',            # 使用 ATR 止损
            'multiplier': 2.0,        # ATR 倍数,将被优化
            'atr_params': {'window': 14} # 计算 ATR 时的窗口期,也将被优化
        },
        # 可以添加其他固定或待优化的配置项
        # 'take_profit': {'type': 'percentage', 'value': 0.15}, # 例如,固定的止盈
        'commission_rate': 0.0005,
        'slippage': 0.0002
    }
    print("基础策略配置模板:")
    import json
    print(json.dumps(base_strategy_config_to_optimize, indent=2)) # 打印基础配置

    # 3. 定义参数网格 (指定要遍历的参数及其候选值)
    parameter_optimization_grid = {
        # EMA 窗口期
        'main_signal.params.window': [15, 20, 30],
        # ADX 过滤器的阈值
        'filters.0.params.threshold': [18, 22, 25], # filters 是列表,用索引 .0 访问第一个过滤器
        # ADX 计算窗口期
        'filters.0.params.window': [10, 14],
        # ATR 止损的乘数
        'stop_loss.multiplier': [1.5, 2.0, 2.5],
        # ATR 计算窗口期
        'stop_loss.atr_params.window': [10, 14]
    }
    print("\n参数优化网格:")
    print(json.dumps(parameter_optimization_grid, indent=2))
    print("\n")

    # 4. 执行参数优化
    print("="*20 + " 3. 开始执行参数优化回测 " + "="*20)
    optimization_results = batch_optimize_strategy(
        data=historical_data,
        base_config=base_strategy_config_to_optimize,
        param_grid=parameter_optimization_grid,
        initial_capital=100000.0,
        optimize_metric='夏普比率' # 设置优化目标为夏普比率
    )
    print("\n" + "="*20 + " 4. 参数优化完成 " + "="*20)

    # 5. 显示优化结果
    print("参数优化结果总结 (按夏普比率降序排列):")
    #
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值