赛事前瞻有深度
AI 分析更客观

7.14 回测验证:用历史数据检验你的策略

验证
🕒 预计阅读:12 分钟 📅 更新时间:2026年09月14日 👁️ 0 次阅读 ✍️ 来源:球小策量化实验室
💡
提示:您当前为未登录状态,代码示例使用通用占位符展示。点击登录并免费领取 500 点体验金 后,所有代码将自动替换为您本人的专属 Key。

系统建好了,但它到底有没有效?这一篇讲怎么做一次可信的回测。

一、回测要回答的三个问题

问题 验证方法
方向判断准不准? 命中率统计
判断有区分度吗? 分层检验
长期能盈利吗? ROI 与最大回撤

三个都要看。只看命中率会被误导。

二、四项基础指标

指标 计算 为什么重要
命中率 命中场次 / 总场次 基础能力
ROI 总预期收益 / 总投入 是否真的能盈利
最大回撤 资产曲线最大跌幅 风险承受度
平均数据数值 命中场次的平均数据数值 判断是否只押低赔方向
import numpy as np
import pandas as pd

def backtest_score(df, stake=1.0):
    """回测核心指标计算

    df 需包含:hit(0/1)、odds(选择方向时数据数值)
    """
    d = df.dropna(subset=['hit', 'odds']).copy()
    if d.empty:
        return None

    n = len(d)
    hits = int(d['hit'].sum())
    hit_rate = hits / n

    # 每场数据偏差:命中赚 (数据数值-1)×初始配置,未命中亏初始配置
    d['pnl'] = np.where(d['hit'] == 1, (d['odds'] - 1) * stake, -stake)
    total_pnl = float(d['pnl'].sum())
    total_stake = n * stake
    roi = total_pnl / total_stake

    # 资产曲线与最大回撤
    curve = d['pnl'].cumsum()
    running_max = curve.cummax()
    drawdown = curve - running_max
    max_dd = float(drawdown.min())

    return {
        '场次': n,
        '命中数': hits,
        '命中率': round(hit_rate, 4),
        '总数据偏差': round(total_pnl, 2),
        'ROI': round(roi, 4),
        '最大回撤': round(max_dd, 2),
        '平均数据数值': round(float(d['odds'].mean()), 3),
        '命中平均数据数值': round(float(d[d['hit'] == 1]['odds'].mean()), 3) if hits else None,
    }

三、分层回测(比命中率更有综合性价比)

把预测按置信度分层,看命中率是否单调变化。

def layered_backtest(df, confidence_col='confidence', n_layers=4):
    """按置信度分层,检查命中率是否随置信度上升

    如果高置信度层的命中率并不比低置信度层高,
    说明你的「置信度」没有意义——这是很常见的发现。
    """
    d = df.dropna(subset=[confidence_col, 'hit']).copy()
    if len(d) < n_layers * 20:
        return '样本不足'

    d['layer'] = pd.qcut(d[confidence_col], q=n_layers, labels=False, duplicates='drop')
    out = d.groupby('layer').agg(
        场次=('hit', 'size'),
        命中率=('hit', 'mean'),
    ).round(4)
    out.index.name = '置信度分层(0=最低)'
    return out

解读

结果 结论
命中率随置信度单调上升 ✅ 置信度评分有意义
各层命中率接近 ❌ 置信度没有区分能力,需要重新设计
高置信层反而更低 ❌ 置信度算反了,或存在严重问题

四、回测的五个致命错误

错误一:数据穿越

用了未来信息。详见 7.10

自查:回测结果好得不像真的,几乎一定是穿越。

错误二:用事后数据数值

回测时用了赛后的收盘数据数值,而真实场景下你只能在赛前拿到某个时点的数据数值。

正确做法:固定用某个赛前时点(如赛前 6 小时)的数据数值。

错误三:忽略实际可执行性

有些数据数值在现实中拿不到(限额、下架)。回测时必须考虑可执行性。

错误四:只看命中率

策略 命中率 平均数据数值 ROI
A 65% 1.35 −12%
B 42% 3.20 +18%

A 命中率高但亏损,B 命中率低但盈利。这就是为什么必须看 ROI。

错误五:样本量不足

样本量 可信度
< 100 场 几乎无参考综合性价比
100 ~ 300 场 初步参考
300 ~ 1000 场 较为可信
> 1000 场 具备统计意义

五、走步式回测(最可信的方式)

def walk_forward_backtest(df, build_model_fn, window_weeks=8):
    """走步式回测:模拟真实使用场景

    用前 N 周数据建模,预测接下来 1 周,然后窗口前移。
    每一步都只用「当时可得」的数据。
    """
    df = df.sort_values('date').reset_index(drop=True)
    weeks = sorted(df['week'].unique())

    records = []
    for i in range(window_weeks, len(weeks)):
        train = df[df['week'].isin(weeks[i - window_weeks:i])]
        test = df[df['week'] == weeks[i]]
        if len(train)  0.5) == (row['outcome'] == 1)),
                'odds': row['odds'],
                'confidence': float(preds[k]),
            })

    return pd.DataFrame(records)

六、一份可信回测报告应该包含什么

说明
时间范围 明确起止日期
样本量 总场次数
覆盖赛事 哪些联赛/赛事
命中率 整体 + 分层
ROI 含计算口径说明
最大回撤 风险指标
基线对比 与简单策略的对比
失败样本 如实列出未命中的场次

最后一条最重要。
只展示成功案例的回测报告没有意义。可回溯的完整记录,是唯一能建立长期信任的东西。
这也是球小策坚持「历史记录不可删除」的原因(见 1.6)。

七、回测之后

拿到回测结果后,通常有三种结论:

结论 下一步
效果显著优于基线 小规模上线,持续监控
略优于基线 检查是否值得投入(考虑成本与精力)
不如基线或与基线持平 从头检查特征设计,或接受「市场是有效的」

第三种结论是最常见也最诚实的结果。能接受它,才能做出真正有综合性价比的东西。

下一步该看什么

常见问题

关于「回测要回答的三个问题」,应该怎么理解?
问题 验证方法 方向判断准不准? 命中率统计 判断有区分度吗? 分层检验 长期能盈利吗? ROI 与最大回撤 三个都要看。只看命中率会被误导。
关于「四项基础指标」,应该怎么理解?
指标 计算 为什么重要 命中率 命中场次 / 总场次 基础能力 ROI 总预期收益 / 总投入 是否真的能盈利 最大回撤 资产曲线最大跌幅 风险承受度 平均数据数值 命中场次的平均数据数值 判断是否只押低赔方向 import numpy as np import pandas as pd def backtest_score(df, stake=1.0): """回测核心指标计…
关于「分层回测(比命中率更有综合性价比)」,应该怎么理解?
把预测按置信度分层,看命中率是否单调变化。 def layered_backtest(df, confidence_col='confidence', n_layers=4): """按置信度分层,检查命中率是否随置信度上升 如果高置信度层的命中率并不比低置信度层高, 说明你的「置信度」没有意义——这是很常见的发现。 """ d = df.dropna(subset=[conf…
关于「解读」,应该怎么理解?
结果 结论 命中率随置信度单调上升 ✅ 置信度评分有意义 各层命中率接近 ❌ 置信度没有区分能力,需要重新设计 高置信层反而更低 ❌ 置信度算反了,或存在严重问题
错误一?数据穿越
用了未来信息。详见 7.10。 自查:回测结果好得不像真的,几乎一定是穿越。
回测时用了赛后的收盘数据数值,而真实场景下你只能在赛前拿到某个时点的数据数值。 正确做法:固定用某个赛前时点(如赛前 6 小时)的数据数值。
免责声明

本栏目内容为足球数据分析方法的教学与科普,全部指标、模型与推演过程仅用于体育数据科学研究与信息展示,不构成任何形式的预测承诺,亦不涉及任何交易行为。文中「市场参考值」「参考值」等表述,均指公开可得的市场公开信息,仅用于说明数据比对方法。请读者遵守所在地区法律法规,理性认识竞技体育的偶然性。

🏠
赛事
📊
数据
🧩
功能
👤
我的

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

微信扫一扫打赏

微信打赏二维码

登录

打开微信扫一扫,确认后自动完成登录