跳转至

参数优化指南

参数优化是量化策略开发中至关重要的一环。AKQuant 提供了强大的优化工具,帮助你探索参数对策略表现的影响,并评估策略的稳健性。

目前支持两种主要的优化模式:

  1. 网格搜索 (Grid Search): run_grid_search
  2. 滚动优化 (Walk-Forward Optimization): run_walk_forward

网格搜索是一种穷举式的参数优化方法。它通过遍历给定的参数组合,在同一段历史数据上运行回测,从而找到表现最优的参数组合。

适用场景

  • 探索策略对参数的敏感性。
  • 确定参数的大致合理范围。
  • 寻找策略在历史数据上的"理论上限"。

使用方法

使用 akquant.run_grid_search 函数:

from akquant import run_grid_search, Strategy

# 1. 定义策略
class MyStrategy(Strategy):
    def __init__(self, ma_period, stop_loss):
        # ...

# 2. 定义参数网格
param_grid = {
    "ma_period": [10, 20, 30],
    "stop_loss": [0.01, 0.02, 0.05]
}

# 3. 运行网格搜索
results = run_grid_search(
    strategy=MyStrategy,
    param_grid=param_grid,
    data=df,
    sort_by="sharpe_ratio",  # 按夏普比率排序
    ascending=False          # 降序
)

print(results.head())

参数模型驱动优化(推荐)

当你需要把策略接入页面配置(例如 Web UI / API)时,建议采用 PARAM_MODEL + param_grid 双层结构

  1. PARAM_MODEL(来自 akquant.params)用于单次回测参数校验与 schema 导出
  2. param_gridrun_grid_search 原生接口)用于离散参数组合搜索

这样既保留了优化内核的稳定性,也能让前端自动生成参数表单。

from akquant import (
    IntParam,
    ParamModel,
    Strategy,
    get_strategy_param_schema,
    validate_strategy_params,
    run_grid_search,
)


class SmaParams(ParamModel):
    fast_period: int = IntParam(10, ge=2, le=200, title="快线")
    slow_period: int = IntParam(30, ge=3, le=500, title="慢线")


class SmaStrategy(Strategy):
    PARAM_MODEL = SmaParams

    def __init__(self, fast_period: int = 10, slow_period: int = 30):
        self.fast_period = fast_period
        self.slow_period = slow_period


schema = get_strategy_param_schema(SmaStrategy)
runtime_params = validate_strategy_params(
    SmaStrategy,
    {"fast_period": 12, "slow_period": 36},
)

results = run_grid_search(
    strategy=SmaStrategy,
    data=df,
    param_grid={"fast_period": [5, 10, 15], "slow_period": [20, 30, 60]},
)

如果策略没有声明 PARAM_MODEL,适配层会回退到 __init__ 签名做基础推断,以兼容历史策略。

多目标优化 (Multi-Objective Optimization)

在实际交易中,单一指标往往存在局限性(例如仅看夏普比率可能选出只交易一次的幸存者)。AKQuant 支持基于多个指标进行排序:

results = run_grid_search(
    strategy=MyStrategy,
    param_grid=param_grid,
    data=df,
    sort_by=["sharpe_ratio", "total_return"], # 优先按夏普,其次按总收益
    ascending=[False, False]                  # 均为降序
)

结果筛选 (Result Filtering)

为了避免“幸存者偏差”或过滤掉不符合风控要求的参数组合(例如交易次数过少、最大回撤过大),你可以使用 result_filter 回调函数:

def result_filter(metrics):
    # 筛选条件:
    # 1. 交易次数 >= 50
    # 2. 夏普比率 > 1.0
    # 3. 最大回撤 < 20%
    return (
        metrics.get("trade_count", 0) >= 50 and
        metrics.get("sharpe_ratio", 0) > 1.0 and
        metrics.get("max_drawdown_pct", 1.0) < 0.2
    )

results = run_grid_search(
    ...,
    result_filter=result_filter
)

核心参数

  • strategy: 策略类。
  • param_grid: 参数字典,key 为参数名,value 为参数值列表。
  • data: 回测数据。
  • sort_by: 排序指标,支持字符串或字符串列表。
  • ascending: 排序方向,支持布尔值或布尔值列表。
  • result_filter: (可选) 结果筛选回调函数,用于过滤不符合条件的组合。
  • warmup_calc: (可选) 动态计算预热期的回调函数。
  • constraint: (可选) 参数约束回调函数,用于过滤无效组合。
  • forward_worker_logs: (可选) 并行优化时是否将子进程 self.log() 回传到主进程日志。
    • False (默认): 性能更优,日志可能在主进程不可见。
    • True: 主进程可聚合子进程日志,适合排障与教学演示。
  • strict_strategy_params: (默认 True,由 run_grid_search 注入到 run_backtest)。
    • 启用后会严格校验 param_grid 是否与策略构造函数参数匹配;
    • 发现未知参数时立即抛错,避免“静默回退”导致优化结果失真。

资源控制与异常处理 (Resource Control & Error Handling)

当参数组合数量庞大或某些组合可能导致死循环/内存溢出时,可以通过以下参数进行控制:

results = run_grid_search(
    ...,
    timeout=60.0,           # 单次任务最长运行 60 秒,超时则跳过
    max_tasks_per_child=1   # 强制每个 Worker 进程只执行 1 个任务后重启
)
  • timeout: 单个回测任务的超时时间(秒)。如果任务在指定时间内未完成,将被标记为失败并跳过。这对于防止某些参数导致的死循环非常有用。
  • max_tasks_per_child: Worker 进程重启频率。设置为 1 可以强制每次任务都使用新的进程,有效防止内存泄漏(OOM)或清理超时残留的线程资源。

Windows 并行执行注意事项(max_workers > 1

在 Windows 上,run_grid_search / run_walk_forward 的多进程并行使用 spawn 启动方式。此时:

  • 策略类必须定义在可导入模块中,不能定义在 __main__(例如脚本内临时类)。
  • 脚本入口需要使用 if __name__ == "__main__": 保护。
  • 这属于 Python 多进程机制限制,不是成交策略语义本身的问题

推荐写法:

from my_strategy_module import TailTradingStrategy
from akquant import run_grid_search


def main() -> None:
    results = run_grid_search(
        strategy=TailTradingStrategy,
        param_grid=param_grid,
        data=all_data,
        max_workers=4,
    )
    print(results.head())


if __name__ == "__main__":
    main()

并行日志可见性与提示规则

max_workers > 1 场景下,日志提示与 forward_worker_logs 的关系如下:

  • forward_worker_logs=False:会提示“子进程日志可能在主进程不可见”。
  • forward_worker_logs=True 且主进程存在有效 logger handler:启用日志回传,不再显示“不可见”提示。
  • forward_worker_logs=True 但主进程无有效 logger handler:会提示“请求了日志回传但主进程无可用 handler”。

持久化与断点续传 (Persistence & Resume)

对于参数组合极多(如 > 10,000 组)的场景,单机运行可能需要数小时甚至数天。如果中途断电或程序崩溃,重新运行将非常耗时。AKQuant 支持将优化结果实时写入 SQLite 数据库,并支持断点续传。

results = run_grid_search(
    ...,
    db_path="optimization_results.db"  # 指定数据库路径
)
  • db_path: SQLite 数据库文件路径。
    • 实时保存: 每完成一个参数组合的回测,结果(参数、指标、耗时、错误信息)都会立即写入数据库。
    • 断点续传: 程序启动时会自动检查数据库。如果发现某个参数组合已经运行过(基于参数的 JSON 匹配),将直接跳过,只运行剩余的任务。
    • 结果复用: 你可以编写脚本从数据库中读取结果进行后续分析,而无需重新运行回测。

⚠️ 风险提示

网格搜索容易导致 过拟合 (Overfitting)。选出的"最优参数"可能只是恰好适应了这段历史数据的噪声,在未来实盘中往往失效。切勿直接使用网格搜索的最优结果进行实盘。


2. 滚动优化 (Walk-Forward Optimization)

滚动优化(Walk-Forward Analysis/Optimization, WFO)是一种更接近实战的验证方法。它模拟了真实的时间流逝,将数据切分为多个 [训练集 | 测试集] 窗口,不断滚动进行"样本内优化"和"样本外验证"。

适用场景

  • 验证策略的真实稳健性。
  • 评估策略在"未知"数据上的表现。
  • 生成随时间动态调整的参数路径。

原理

WFO 的核心思想是:永远只用过去的数据来决定现在的参数

  1. 窗口 1:
    • 训练 (In-Sample): 使用 1-3月数据进行 Grid Search,找到最优参数 A。
    • 测试 (Out-of-Sample): 使用参数 A 在 4月数据上进行回测。
  2. 窗口 2:
    • 训练: 滚动到 2-4月,重新 Grid Search,找到最优参数 B。
    • 测试: 使用参数 B 在 5月数据上进行回测。
  3. 拼接: 将所有测试段的结果拼接,形成最终的资金曲线。

使用方法

使用 akquant.run_walk_forward 函数:

from akquant import run_walk_forward

# 运行 WFO
wfo_results = run_walk_forward(
    strategy=MyStrategy,
    param_grid=param_grid,
    data=df,
    train_period=250,      # 训练窗口长度 (例如250个Bar)
    test_period=60,        # 测试/滚动步长 (例如60个Bar)
    metric="sharpe_ratio", # 优化目标
    initial_cash=100_000.0,
    warmup_calc=warmup_calc, # 支持动态预热
    constraint=param_constraint, # 支持参数约束
    max_workers=4,
    forward_worker_logs=True,    # 样本内并行优化日志回传
    strict_strategy_params=True, # 严格参数校验(推荐保持开启)
)

# wfo_results 包含拼接后的资金曲线和每段使用的参数
print(wfo_results)

核心参数

  • train_period: 训练窗口长度 (Bar数量)。窗口越长,参数越稳定;窗口越短,适应变化越快。
  • test_period: 测试窗口长度 (Bar数量)。通常也是滚动的步长。
  • metric: 在训练集上选择最优参数的依据指标。支持单个字符串 (如 "sharpe_ratio") 或字符串列表 (如 ["sharpe_ratio", "total_return"])。
  • ascending: 排序方向,与 metric 对应。支持布尔值或布尔值列表 (默认 False,即降序)。
  • result_filter: (可选) 结果筛选回调函数,在每个训练窗口中过滤不符合条件的参数组合。
  • kwargs 透传规则:
    • 样本内优化阶段透传给 run_grid_search
    • 样本外验证阶段透传给 run_backtest
    • 因此可在 WFO 中继续使用 forward_worker_logsstrict_strategy_params

3. 高级功能

动态预热期 (Dynamic Warmup)

不同的参数组合可能需要不同的预热期(例如长均线策略需要更多历史数据)。你可以通过 warmup_calc 回调函数动态指定:

def warmup_calc(params):
    # 预热期 = 长期均线窗口 + 1
    return params["long_window"] + 1

run_grid_search(..., warmup_calc=warmup_calc)

参数约束 (Constraint)

有些参数组合在逻辑上是无效的(例如短期均线 > 长期均线)。通过 constraint 回调函数可以提前过滤这些组合,节省计算资源:

def param_constraint(params):
    # 只保留短期窗口 < 长期窗口的组合
    return params["short_window"] < params["long_window"]

run_grid_search(..., constraint=param_constraint)

4. Grid Search vs Walk-Forward 对比

特性 Grid Search (网格搜索) Walk-Forward (滚动优化)
数据使用 使用全部数据一次性优化 数据滚动切分,训练集/测试集严格分离
参数结果 1 组 全局静态参数 多组 动态变化的参数
过拟合风险 极高 (看着答案找最优解) (模拟真实未知环境)
核心目的 探索参数敏感性,找“理论上限” 验证策略稳健性,评估“实战预期”
代码对应 akquant.run_grid_search akquant.run_walk_forward