
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
很多量化交易初学者都有一个误区:花大量时间寻找那个"最优参数",以为找到了就能稳定赚钱。但现实往往是——在回测中表现完美的策略,一到实盘就崩溃。
问题出在哪里?验证方法太弱了。
今天这篇文章,我们通过一个完整的 Python 案例,带你了解如何用「参数扫描」和「鲁棒性测试」来判断一个策略是否真的可靠,而不是碰巧在某个参数下表现好。
传统做法是:测试一堆参数,选表现最好的那个。
正确做法是:测试一堆参数,看附近的参数表现是否也不错。
为什么?因为市场会变化,数据有噪声。如果你的策略只在某一个精确参数下有效,稍微偏一点就亏钱,那它大概率是"过拟合"的产物,实盘必死。
一个健壮的策略,应该像一座"小山丘"——在一个参数区间内都表现良好,而不是一根"针尖"——只在某个点上有效。
我们测试的策略逻辑很简单:
用一句话概括:逢低买入,动量确认后卖出。
这个逻辑是合理的——不是随便堆砌指标,而是有明确的交易思路。
我们不假设 WMA = 20 就是最好的,而是测试 16 到 24 的所有值:
import pandas as pd
import numpy as np
import vectorbt as vbt
# 参数扫描:测试 WMA 周期从 16 到 24
results = []
for wma_p in range(16, 25):
# 用当前 WMA 周期重新计算指标
tmp = df.copy()
# 计算加权移动平均线
weights = np.arange(1, wma_p + 1)
tmp['WMA'] = tmp['Close'].rolling(wma_p).apply(
lambda prices: np.dot(prices, weights) / weights.sum(),
raw=True
)
# 入场信号:开盘价低于 WMA
tmp["WMA_Open_Below"] = tmp["Open"] < tmp["WMA"]
# 出场信号:MACD 上穿信号线(这里简化,假设已计算好)
entries = tmp["WMA_Open_Below"].shift(1).fillna(False)
exits = tmp["MACD_cross_above_signal"].shift(1).fillna(False)
# 回测
pf = vbt.Portfolio.from_signals(
close=tmp["Open"],
entries=entries.to_numpy(),
exits=exits.to_numpy(),
init_cash=100_000, # 初始资金 10 万
fees=0.001, # 手续费 0.1%
slippage=0.002, # 滑点 0.2%
freq="1d"
)
# 记录结果
results.append({
"WMA_PERIOD": wma_p,
"Total Return": pf.total_return(),
"Sharpe": pf.sharpe_ratio(),
"MaxDD": pf.max_drawdown()
})
# 转换为 DataFrame 查看结果
results_df = pd.DataFrame(results).set_index("WMA_PERIOD")
print(results_df)好的结果应该是这样的:
关键观察点:
这说明策略的逻辑本身在起作用,而不是某个参数碰巧撞上了。
MACD 有三个参数:快线、慢线、信号线。我们用三维网格搜索来验证:
from itertools import product
# 定义参数范围
FAST_RANGE = range(10, 15) # 快线周期:10-14
SIGNAL_RANGE = range(7, 12) # 信号线周期:7-11
SLOW_RANGE = range(22, 31) # 慢线周期:22-30
records = []
# 遍历所有参数组合
for fast, signal, slow in product(FAST_RANGE, SIGNAL_RANGE, SLOW_RANGE):
# 用当前参数计算 MACD
tmp = df.copy()
tmp["EMA_fast"] = tmp["Close"].ewm(span=fast, adjust=False).mean()
tmp["EMA_slow"] = tmp["Close"].ewm(span=slow, adjust=False).mean()
tmp["MACD"] = tmp["EMA_fast"] - tmp["EMA_slow"]
tmp["Signal"] = tmp["MACD"].ewm(span=signal, adjust=False).mean()
# 出场信号:MACD 上穿信号线
tmp["MACD_prev"] = tmp["MACD"].shift(1)
tmp["Signal_prev"] = tmp["Signal"].shift(1)
exits = (tmp["MACD_prev"] <= tmp["Signal_prev"]) & (tmp["MACD"] > tmp["Signal"])
# 回测(入场条件不变)
pf = vbt.Portfolio.from_signals(
close=tmp["Open"],
entries=entries.shift(1).fillna(False).to_numpy(),
exits=exits.shift(1).fillna(False).to_numpy(),
init_cash=100_000,
fees=0.001,
slippage=0.002,
freq="1d"
)
# 记录参数和收益
records.append((fast, signal, slow, pf.total_return()))
# 转换为 DataFrame
grid_df = pd.DataFrame(records, columns=["fast", "signal", "slow", "ret"])
print(grid_df.describe())import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 提取数据
x = grid_df["fast"].values
y = grid_df["signal"].values
z = grid_df["slow"].values
r = grid_df["ret"].values # 收益率用颜色表示
# 创建 3D 散点图
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
sc = ax.scatter(x, y, z, c=r, cmap="viridis", s=60)
ax.set_xlabel("MACD 快线周期")
ax.set_ylabel("MACD 信号线周期")
ax.set_zlabel("MACD 慢线周期")
ax.set_title("MACD 参数性能分布图")
fig.colorbar(sc, ax=ax, label="总收益率")
plt.show()理想的结果是:图中有大片连续的"亮色区域"(高收益),而不是零星分散的几个点。
通过上述测试后,一个值得深入研究的策略应该满足:
只有同时满足这些条件,策略才值得进一步做「压力测试」(Stress Test)和「前推优化」(Walk-Forward Analysis)。
本文的核心观点很简单:好策略不是"找"出来的,而是"验证"出来的。
通过参数扫描,我们关注的不是"哪个参数最赚钱",而是"策略在多大范围内都能赚钱"。一个只在单一参数下表现好的策略,像针尖一样脆弱;而一个在参数区间内普遍有效的策略,像小山丘一样稳固。
记住这句话:
脆弱的策略有针尖,稳健的策略有山丘。
希望这篇文章能帮助你建立正确的策略验证思维,少走弯路!
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用