【Python量化实战全攻略 #31】策略参数遍历与优化手册:网格、随机搜索(附代码)
摘要:【Python量化实战全攻略 #31】策略参数遍历与优化手册:网格、随机搜索(附代码) 手册定位:「模块四·组合/风控/专题」第 7 篇,系列倒数第 2 篇。前面 #16 给了选股回测、#24 给了择时绩效,本篇把「策略 +
手册定位:「模块四·组合/风控/专题」第 7 篇,系列倒数第 2 篇。前面 #16 给了选股回测、#24 给了择时绩效,本篇把「策略 + 参数」的遍历优化讲透——网格搜索、随机搜索两种范式一次性给你,拿来就能给自己的策略找最优参数。
接口路径取自智兔数服官网文档页已验证路径,未虚构;本篇以 #16/#24 的回测结论为被优化对象。
1. 你将得到什么
读完本篇,你能直接拿到:
- 一套把「策略函数 + 参数」跑出绩效指标(年化/夏普/回撤)的通用框架;
- 网格搜索
grid_search:穷举参数组合,挑夏普最高的; - 随机搜索
random_search:大参数空间下比网格更省时; - 复用 #16(选股回测)与 #24(择时绩效)的结论做被优化对象,不重复造轮子;
- 5 个真实踩过的坑(过拟合、未来函数、样本内偏差、并行成本、参数稳定性)。
2. 取数约定
| 项 | 值 |
|---|---|
| 基础域名 | https://api.zhituapi.com |
| 鉴权 | 所有请求带 ?token=你的token |
| 股票代码格式 | 600519.SH(沪)/ 000001.SZ(深)/ 920547.BJ(北交所) |
| 返回 | JSON;本篇示例用 _MOCK=True 内置演示序列 |
真实调用把
TOKEN换成你的 token;本手册示例用_MOCK=True内置演示数据,逻辑与真实取数完全一致,先跑通再替换。价格序列来自 #02 的历史 K 线端点,本文复用其约定。
3. 端点分组地图(复用指针)
| 本篇要做 | 复用哪篇 | 端点/函数 |
|---|---|---|
| 价格序列 | #02 历史 K 线 | /hs/history/dkline/{代码}.{市场}(日线) |
| 选股回测骨架 | #16 选股回测 | backtest_groups / ic |
| 绩效指标 | #24 择时绩效 | strategy_curve / performance |
本篇不重复罗列上述端点,直接复用;核心是把「参数」变成遍历维度。
4. 核心模板函数(自包含可跑)
import time, requests, pandas as pd, numpy as np
BASE = "https://api.zhituapi.com"
TOKEN = "你的token"
_MOCK = False # 演示模式:True 时用内置数据,不发起网络请求
def _hit_key(path):
return "%s%s?token=%s" % (BASE, path, TOKEN)
def _get(path, default=None, tries=3):
url = _hit_key(path)
for i in range(tries):
try:
if _MOCK:
return default
r = requests.get(url, timeout=8)
r.raise_for_status()
return r.json()
except Exception:
if i == tries - 1:
return default
time.sleep(0.5 * (i + 1))
def simple_backtest(prices, n):
"""参数 n:动量窗口。返回绩效 dict(无未来函数:信号用 t-1)。"""
s = pd.Series(prices)
mom = s.diff(n)
signal = (mom > 0).astype(int)
ret = s.pct_change().fillna(0.0)
strat = ret * signal.shift(1)
cum = (1.0 + strat).cumprod()
total = float(cum.iloc[-1] - 1.0)
yrs = len(s) / 252.0
ann = (1.0 + total) ** (1.0 / max(yrs, 1e-9)) - 1.0
vol = strat.std() * (252 ** 0.5)
sharpe = (ann / vol) if vol > 0 else 0.0
peak = cum.cummax()
mdd = float((cum / peak - 1.0).min())
return {"n": n, "ann": ann, "sharpe": sharpe, "mdd": mdd}
def grid_search(prices, ns):
rows = [simple_backtest(prices, n) for n in ns]
best = max(rows, key=lambda r: r["sharpe"])
return rows, best
def random_search(prices, n_lo, n_hi, iters=20, seed=42):
rng = np.random.default_rng(seed)
best = None
for _ in range(iters):
n = int(rng.integers(n_lo, n_hi + 1))
r = simple_backtest(prices, n)
if best is None or r["sharpe"] > best["sharpe"]:
best = r
return best
def run_check():
global _MOCK
_MOCK = True
rng = np.random.default_rng(0)
prices = 100.0 * np.cumprod(1.0 + rng.normal(0.0005, 0.02, 504))
rows, best = grid_search(prices, [5, 10, 20, 40, 60])
rb = random_search(prices, 5, 60, iters=30)
assert best is not None and rb is not None
assert abs(best["sharpe"]) < 1e6 and abs(rb["sharpe"]) < 1e6
print("校验通过 | 网格最优 n=%d sharpe=%.3f | 随机最优 n=%d sharpe=%.3f"
% (best["n"], best["sharpe"], rb["n"], rb["sharpe"]))
_MOCK = False
if __name__ == "__main__":
run_check()
5. 跑通示例
把 TOKEN 换成你的 token、_MOCK=False 后,用 #02 的真实价格驱动回测:
# prices 换成 #02 拉到的收盘价序列
rows, best = grid_search(prices, range(5, 121, 5))
print("最优参数:", best)
输出即每个参数对应的绩效,以及夏普最高的那一组——直接拿去写策略配置。
6. 坑与注意事项
- 过拟合(最核心):参数越多、样本越短,越容易在历史上完美、实盘崩。用样本外或 walk-forward 验证,别只看全样本最优。
- 未来函数:信号必须用
shift(1),用当天信号算当天收益 = 偷看未来,回测虚高。 - 样本内偏差:网格最优往往是运气峰值,随机搜索多跑几组看稳定性,别押单一最优。
- 并行成本:大网格逐组串行很慢,参数独立可多进程/向量化;但单组回测要轻。
- 参数稳定性:好参数应在邻域也表现稳定,尖峰参数大概率是噪声,别选。
7. 小结与下一篇预告
本篇把「参数遍历」做成网格/随机两套通用框架,复用 #16/#24 的回测与绩效结论,是策略从「写得出」到「调得优」的关键一步。最后一篇讲一键回测与实盘信号导出——把整条研究链路落盘成可执行的信号文件。
下一篇预告:《【Python量化实战全攻略 #32】一键回测与实盘信号导出手册:从研究到落盘(附代码)》
8. 免责声明
本文所有接口路径均来自官方文档,示例数据为演示用途,不构成任何投资建议。实盘使用前请自行核对字段与参数稳健性。
关于数据来源:本文接口来自智兔数服(www.zhituapi.com)官方文档,覆盖沪深A股、指数、基金、可转债等 200+ 已验证接口路径,注册可领免费 token 做研究测试。
更多实战手册见技术博客:zhituapi.com/blog.html。
本系列《Python量化实战全攻略》共 32 篇,下一篇(#32)为系列收官:一键回测与实盘信号导出。