学习

⌂学习首页◈学习路线

练习

⌁行情练习◷回放↻复习

我的学习

▥统计☆收藏⌕搜索✦AI

学习原则

先理解风险,再练习决策。

Trade Buty免费 · 中立
👤 登录
📚学习路线📈行情⏮回放✎复习🔍搜索🤖AI👤 登录
Trade Buty

面向全球中文用户的免费中立交易教育平台。分级课程(学)× 真实行情图表与回放(练)。

⚠️ 风险提示:本站全部内容仅用于学习与研究,不构成任何投资建议。市场有风险,投资需谨慎。

导航

学习路线行情回放搜索AI统计隐私政策内容来源 kline-buty反馈建议
© 2026 sun1090 · MIT License内容来源 kline-buty

本页目录

  • 一、最小回测流程
  • 1.1 完整可运行示例:双均线策略回测
  • 二、信号生成与仓位
  • 2.1 信号 → 目标仓位
  • 2.2 成交价假设
  • 三、回测引擎选择:自己写 vs backtrader vs vn.py
  • 四、绩效指标计算
  • 五、回测结果怎么读
  • 5.1 资金曲线 vs 基准
  • 5.2 回撤区间分布
  • 六、第一次认知回测过拟合
  • 七、把回测跑成「研究流程」
  • 7.1 样本内 / 样本外切分
  • 7.2 实验留档
  • 八、下一步

篇章进度

15 · 量化实战篇

前面的篇章教你怎么看懂市场、管好仓位,10-系统对接篇教软件团队怎么做交易系统。本篇把视角落在两者之间最朴素的位置——一个懂点代码的个人交易者,怎么从零开始把「数据 → 回测 → 策略 →(合规)自动化」这条完整链路真正跑通。

0/5 课0%

下一篇章 →

26 · 数据解读实战篇→

市场每天都在发布数据:CPI、非农、PMI、央行决议、财报、行业库存……但 90% 的人只看到了「利多利空」四个字,然后就急着下单。

学习路线/15 · 量化实战篇
课程单元 03/3 / 5 节

03 · 第一个回测

用完整可运行 Python 代码跑通读数据、信号、模拟成交、绩效的最小回测闭环与过拟合认知。

📖 约 7 分钟阅读
本页目录▾
  • 一、最小回测流程
  • 1.1 完整可运行示例:双均线策略回测
  • 二、信号生成与仓位
  • 2.1 信号 → 目标仓位
  • 2.2 成交价假设
  • 三、回测引擎选择:自己写 vs backtrader vs vn.py
  • 四、绩效指标计算
  • 五、回测结果怎么读
  • 5.1 资金曲线 vs 基准
  • 5.2 回撤区间分布
  • 六、第一次认知回测过拟合
  • 七、把回测跑成「研究流程」
  • 7.1 样本内 / 样本外切分
  • 7.2 实验留档
  • 八、下一步

从「有数据」到「有结论」的一步。本篇用一份完整可运行的 Python 代码,带你跑通「读数据 → 生成信号 → 模拟成交 → 计算绩效」的最小回测闭环,并学会看懂回测结果、第一次理解什么是过拟合。

免责声明:本站全部内容仅用于学习与研究,不构成任何投资建议。市场有风险,投资需谨慎。


一、最小回测流程

回测的本质是:在历史数据上重放你的规则,假装当时你在场。四个环节缺一不可:

text
读数据 → 生成信号 → 模拟成交(含手续费/滑点)→ 计算绩效

从回测到实盘的完整流程:样本内/样本外、模拟盘与修正回路

1.1 完整可运行示例:双均线策略回测

前置条件:已按 02-数据获取实战↗ 拉好 data/600519_daily_hfq.csv(后复权,含中文列名)。

示例代码仅教学用,实盘风险自负:

python
import numpy as np
import pandas as pd

FEE_RATE = 0.001        # 单边手续费(按成交金额比例,示例值)
SLIPPAGE = 0.0002       # 单边滑点(按成交金额比例,示例值)

def load_data(path):
    df = pd.read_csv(path)
    df.columns = [c.strip() for c in df.columns]
    rename = {"日期": "date", "开盘": "open", "收盘": "close",
              "最高": "high", "最低": "low", "成交量": "volume"}
    df = df.rename(columns=rename)
    df["date"] = pd.to_datetime(df["date"])
    return df.set_index("date").sort_index()

def backtest_double_ma(df, fast=5, slow=20, fee=FEE_RATE, slip=SLIPPAGE):
    df = df.copy()
    # 1. 生成信号
    df["ma_fast"] = df["close"].rolling(fast).mean()
    df["ma_slow"] = df["close"].rolling(slow).mean()
    df["signal"] = (df["ma_fast"] > df["ma_slow"]).astype(int)

    # 2. 模拟成交:信号次日生效(避免未来函数),全仓进出
    df["position"] = df["signal"].shift(1).fillna(0)
    df["ret"] = df["close"].pct_change().fillna(0)
    df["strat_ret"] = df["position"] * df["ret"]

    # 3. 交易成本:持仓变化的那天扣掉手续费+滑点
    changed = df["position"] != df["position"].shift(1)
    df.loc[changed, "strat_ret"] -= (fee + slip)
    df["equity"] = (1 + df["strat_ret"]).cumprod()
    return df

def report(df, benchmark=None):
    equity = df["equity"]
    total = equity.iloc[-1] / equity.iloc[0] - 1
    ann = (1 + total) ** (252 / len(df)) - 1
    dd = equity / equity.cummax() - 1
    r = {
        "总收益": f"{total:.2%}",
        "年化收益": f"{ann:.2%}",
        "最大回撤": f"{dd.min():.2%}",
        "夏普比率": f"{df['strat_ret'].mean() / df['strat_ret'].std() * np.sqrt(252):.2f}",
        "日胜率": f"{(df['strat_ret'] > 0).mean():.2%}",
        "交易次数": f"{int((df['position'] != df['position'].shift(1)).sum())}",
    }
    if benchmark is not None:
        r["基准同期收益"] = f"{benchmark.iloc[-1] / benchmark.iloc[0] - 1:.2%}"
    return r

df = load_data("data/600519_daily_hfq.csv")
result = backtest_double_ma(df, fast=5, slow=20)
print(report(result))
print(result[["close", "ma_fast", "ma_slow", "position", "equity"]].tail())

运行后你会看到一份包含总收益、年化、最大**回撤、夏普、胜率**的报表——这就是你第一个「真实」回测。


二、信号生成与仓位

上面示例里最关键的两个设计决策:

2.1 信号 → 目标仓位

信号(signal)表达的是「想不想买」,仓位(position)表达的是「现在持没持有」。二者之间隔着一拍:

text
t 日收盘:算出 ma_fast > ma_slow → 信号 = 1(想买)
t+1 日开盘:执行 → 仓位 = 1(持有)

signal.shift(1) 就是这一拍的实现——信号与成交必须错开一个 bar,否则就是未来函数(见 01-量化工具链 7.3)。

2.2 成交价假设

回测里「成交在哪根 K 线的哪个价」直接决定结果可信度:

假设简单真实
信号 bar 的收盘价成交容易实现信号本身靠收盘价算出,已偷看未来 ❌
次日开盘价成交推荐个人起步现实:开盘价是可成交的参考价,仍偏理想 ⭐
次日开盘价 + 滑点更贴近现实滑点估计不准也会引入误差
按日内高低点/成交量加权复杂越复杂越难验证,先用简单假设

💡 成交价假设的起步建议

次日开盘价 + 比例滑点,然后通过改滑点幅度做敏感性测试(滑点翻倍收益变化多大),比纠结模型精度更重要。


三、回测引擎选择:自己写 vs backtrader vs vn.py

方案上手成本能力适用
自己写(如第一节示例)低,几十行日线、单一标的、规则简单研究初期、理解原理,强烈建议先做一次
backtrader中多标的多周期、滑点/佣金模型、内置分析器从自写升级的成熟事件驱动框架
vectorbt中向量化、参数扫描极快参数遍历量大的研究场景
vn.py高回测 + 实盘一体化,支持柜台/交易所想直接通向实盘工程化的重型方案

边界建议:

  • 信号基于日线、单标的、规则固定 → 自己写,20 行搞定,还能完全理解每个细节;
  • 要扫几百组参数、画复杂资金曲线 → 换 backtrader 或 vectorbt,省的是体力;
  • 要做多标的组合、事件驱动、tick 级 → 上 vn.py,但它复杂度会吃掉你大量研究时间。

💡 关键认知

引擎只是载体,规则才是策略。换引擎前后,同一策略同一数据应产出几乎一致的结果——这是验证自己理解正确的试金石。


四、绩效指标计算

四个必算指标,全部由净值序列(equity)推导(示例代码仅教学用,实盘风险自负):

python
ret = df["strat_ret"]          # 策略日收益序列
equity = df["equity"]          # 净值

total = equity.iloc[-1] / equity.iloc[0] - 1
n_years = len(df) / 252
ann = (1 + total) ** (1 / n_years) - 1                    # 年化(几何)
dd = equity / equity.cummax() - 1                          # 回撤序列
max_dd = dd.min()                                          # 最大回撤
sharpe = ret.mean() / ret.std() * np.sqrt(252)             # 年化夏普
win_rate = (ret > 0).mean()                                # 日胜率
指标含义怎么读
年化收益折算成一年能赚多少低于货基/国债就说明策略不创造价值
最大回撤历史上从峰值亏多少决定你能不能拿得住,比收益更重要
夏普比率每单位波动换多少收益>1 合格,>2 优秀,回测里 >3 先怀疑未来函数
胜率赚钱的日子占比趋势策略胜率天然低(30-40%),别单独看

💡 个人交易者最直觉的体检指标

**收益回撤比(年化收益 / 最大回撤)**是个人交易者最直觉的体检指标:1 以下几乎不值得做,2 以上才算及格线。


五、回测结果怎么读

5.1 资金曲线 vs 基准

把策略净值和基准(沪深 300 指数同期)画在一张图上,看的是相对表现:

python
import matplotlib.pyplot as plt

bench = pd.read_csv("data/沪深300指数.csv", parse_dates=["date"])  # 自行获取
bench["equity"] = (1 + bench["close"].pct_change().fillna(0)).cumprod()
ax = result["equity"].plot(label="策略", figsize=(12, 6))
bench.set_index("date")["equity"].plot(ax=ax, label="沪深300")
plt.legend(); plt.title("策略 vs 基准")
plt.show()
  • 跑赢基准且回撤可控 → 策略有研究价值;
  • 全程跑不赢基准 → 直接买指数基金更省心;
  • 大幅跑赢但曲线在最后一段陡峭上升 → 优先怀疑数据或代码 bug,而不是策略英明。

5.2 回撤区间分布

最大回撤只是最坏的一天,更该看回撤都发生在哪:

python
# 列出每次回撤的起止与幅度
dd = result["equity"] / result["equity"].cummax() - 1
peak = 0; draws = []
for i, v in enumerate(dd):
    if v == 0:
        peak = i
    else:
        draws.append((peak, i, v))
d = min(draws, key=lambda x: x[2])
print(f"最深回撤区间: {result.index[d[0]]} ~ {result.index[d[1]]}, 幅度 {d[2]:.2%}")

如果回撤集中在某一年(如 2022 熊市),说明策略本质是「牛市放大器」;如果回撤均匀分布,说明策略在低相关行情中挣扎——这决定你后续改进方向。


🛑 参数不敏感的高原才是真正有效的区域

任何参数组合都能在历史中「事后」找到盈利点,这是规律不是能力。真正有效的是参数不敏感的区域——一段高原,而不是一座尖峰。 相邻参数组绩效应平滑过渡,5/20 一枝独秀、4/19 就崩,多半是过拟合。

六、第一次认知回测过拟合

把第一节代码的 fast/slow 改成 3/17,也许收益更高;改成 8/32,也许最大回撤更小。然后呢?

python
# 参数遍历:扫一组均线参数
for fast in [3, 5, 10, 15]:
    for slow in [20, 30, 40, 60]:
        if fast >= slow:
            continue
        r = backtest_double_ma(df, fast=fast, slow=slow)
        ann = (1 + r["equity"].iloc[-1] / r["equity"].iloc[0]) ** (252 / len(r)) - 1
        print(f"f{fast}/s{slow}: 年化 {ann:.2%}")

你会得到一个「最佳参数」——但这份最佳是在这段历史上挑出来的,不是预测出来的。参数越多、历史越短、挑出来的组合越精致,越说明你在拟合噪音而非规律。这就是**过拟合**:均线 5/20 好于 3/17,不代表未来仍然如此。

判断要点:

  • 相邻参数组的绩效应该是平滑过渡的(5/20 好,6/22 也不差);若 5/20 一枝独秀、4/19 就崩,多半是过拟合;
  • 任何参数组合都能在历史中「事后」找到盈利点,这是规律不是能力;
  • 真正有效的是参数不敏感的区域——一段高原,而不是一座尖峰。

过拟合示意:参数尖峰与高原、样本内外资金曲线对比


七、把回测跑成「研究流程」

单次回测 = 一次观察,不是结论。最小研究流程是:参数域设计 + 样本内/样本外切分。

7.1 样本内 / 样本外切分

python
split = int(len(df) * 0.7)
train, test = df.iloc[:split], df.iloc[split:]

# 样本内:用来选参数(允许稍微优化)
best = None
for fast in [3, 5, 10]:
    for slow in [20, 30, 40]:
        if fast >= slow:
            continue
        r = backtest_double_ma(train, fast=fast, slow=slow)
        score = (r["equity"].iloc[-1] / r["equity"].iloc[0]) ** (252 / len(r)) - 1
        if best is None or score > best[2]:
            best = (fast, slow, score)
print("样本内最优:", best)

# 样本外:用样本内选出的参数,只在没见过的数据上跑一次
r_test = backtest_double_ma(test, fast=best[0], slow=best[1])
print("样本外年化:", f"{(r_test['equity'].iloc[-1]/r_test['equity'].iloc[0]) ** (252/len(r_test)) - 1:.2%}")

规则:

  1. 参数只能在样本内挑;
  2. 样本外只跑一次,跑完不回去调参数(再调就废了);
  3. 样本外绩效与样本内差距过大(衰减 > 50%)→ 样本内大概率是过拟合;
  4. 进阶:滚动窗口前推(rolling walk-forward),比一次性切分更接近实盘节奏。

7.2 实验留档

每次回测都记录:数据范围、参数、手续费滑点假设、commit 号、结果数字(模板见 01-量化工具链 6.2)。没有记录的结论等于没结论——三个月后你会彻底忘记这次回测为什么这么做。


八、下一步

回测跑通了,先别急着实盘。先去 05-策略代码实战↗ 看看更多策略形态,再回到 04-实盘自动化↗ 搞清楚合规边界与自动化现实,最后决定要不要把策略推向真实资金。


⚠️ 风险提示

回测收益 ≠ 实盘收益。回测天然低估滑点、高估成交、遗漏现实约束(涨跌停买不进、**流动性**不足、停牌无法交易);经过参数优化的策略还叠加了过拟合风险。单次回测、参数遍历的高收益都不能作为投资依据;务必采用「样本内选参、样本外验证、留档可复现」的研究流程,并以模拟盘长期验证后再考虑小资金实盘。本文代码与结论仅用于学习与研究,不构成任何投资建议。

📝 量化实战篇 · 随堂测

3 道概念题 · 即时判分

📖 学完这篇,去看真盘

在真实行情图表里找找这篇内容提到的概念,看懂了再继续。

打开实时行情 →
🤖问 AI: 03 · 第一个回测→

相关课程

  • →01 · 量化工具链
  • →02 · 数据获取实战
  • →04 · 实盘自动化
  • →05 · 策略代码实战

下一篇

04 · 实盘自动化

→