← 返回首页

从零入门期货程序化:第5集 - 回测:用历史数据验证你的策略到底能不能赚钱

第5集:回测——用历史数据验证你的策略到底能不能赚钱


上一集你写了第一个突破策略。程序能看行情、能判断条件、能下单、能止盈止损。

但有一个问题你没回答:这个策略到底能不能赚钱?

你不知道。

也许能。也许不能。也许在螺纹钢上行,但在豆粕上亏。也许在2024年赚了30%,但在2025年亏了20%。

不知道。全凭感觉。

这就是大部分手动交易者的状态。他们的策略是"我觉得行",不是"数据告诉我行"。

这一集,我要教你怎么把"我觉得行"变成"数据告诉我行"。

这个东西叫回测(Backtesting)。它是程序化交易最核心的能力——没有之一。


一、回测是什么?一个比方

想象你发明了一种扑克打法。你觉得这种打法很好,能在赌桌上赢钱。

你怎么验证?

方法一:上去直接打,用真钱。赢了当然好,输了——你的钱就没了。

方法二:找过去1000场牌局的记录,假装你当时在场,用你的打法"虚拟参与"每一局。看这1000局下来,你是赚了还是亏了。

回测就是方法二。

你不需要真金白银去试。你只需要拿过去的历史行情数据,让你的策略"虚拟"跑一遍,看结果。

如果过去三年你的策略能稳定赚钱,那它大概率在未来也能赚钱(不是100%保证,但比瞎猜强太多了)。

如果过去三年你的策略一直在亏——那你千万别拿真钱去试,先改策略。

这就是回测的价值:用低成本(零风险)验证你的策略。


二、回测需要什么?三个东西

做一次回测,你需要准备三个东西:

需要什么说明哪里弄
历史行情数据过去的K线数据(开盘、最高、最低、收盘、成交量)交易所官网、期货数据网站、CTP自带的历史数据接口
策略代码上一集写的突破策略你已经有了
回测引擎一个能"模拟"K线逐根推进的程序我们自己写一个

下面,一个一个来。


三、第一步:获取历史行情数据

你的策略做的是螺纹钢,所以你需要螺纹钢的历史K线数据。

数据长这样(CSV格式):

date,open,high,low,close,volume
2024-01-02,3850,3878,3842,3865,152340
2024-01-03,3866,3892,3858,3885,167890
2024-01-04,3887,3910,3870,3902,145230
...

每一行是一根日线K线:日期、开盘价、最高价、最低价、收盘价、成交量。

去哪拿数据?

方法一:CTP接口拿(你已经有CTP了)

CTP行情服务器除了实时推送行情,还可以查询历史数据。但SimNow的历史数据有限,通常只保留最近几个月。

方法二:免费数据网站(推荐新手)

用AKShare下载螺纹钢历史数据:

import akshare as ak

下载螺纹钢主力合约的日线数据

df = ak.futures_zh_daily_sina(symbol="RB0")

RB0 = 螺纹钢主力合约(自动跟踪)

保存为CSV

df.to_csv("rb_daily.csv", index=False) print(f"下载完成:{len(df)} 条数据")

三行代码,几秒钟就下载好了。通常是过去几年的日线数据,几千条,完全够用。

方法三:手动下载CSV

有些网站提供现成的CSV文件下载。搜索"螺纹钢历史日线数据 CSV"就能找到。

不管你用什么方法,最终目标都是拿到一个CSV文件,里面是日期、开高低收、成交量。


四、第二步:写一个回测引擎

"回测引擎"听起来很高大上,其实就是一个循环:

for 每一根K线:
    把这根K线喂给策略
    策略判断:买?卖?不动?
    记录结果
最后算总账

就这么简单。不需要什么框架,不需要什么黑科技。你自己写一个,50行代码搞定。

创建文件 backtest.py

import pandas as pd
import matplotlib.pyplot as plt

====================================

读取历史数据

====================================

df = pd.read_csv("rb_daily.csv")

确保列名正确(不同数据源的列名可能不一样)

df.columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'hold']

按日期排序

df = df.sort_values('date').reset_index(drop=True) print(f"数据加载完成:{len(df)} 根K线") print(f"日期范围:{df['date'].iloc[0]} 到 {df['date'].iloc[-1]}") print("-" * 50)

====================================

策略参数

====================================

PROFIT_TARGET = 50 # 止盈50点 STOP_LOSS = 30 # 止损30点 INITIAL_CAPITAL = 100000 # 初始资金10万

====================================

回测核心循环

====================================

capital = INITIAL_CAPITAL position = 0 # 0=空仓,1=持多 entry_price = 0 # 入场价 trades = [] # 记录每笔交易 equity_curve = [] # 资金曲线(每天的总资产) for i in range(len(df)): row = df.iloc[i] # ---- 当天收盘时的总资产 ---- # 空仓:总资产 = 现金 # 持仓:总资产 = 现金 + 持仓市值 if position > 0: # 螺纹钢1手=10吨,每跳1点=10元 unrealized_pnl = (row['close'] - entry_price) * 10 * position total_equity = capital + unrealized_pnl else: total_equity = capital equity_curve.append({ 'date': row['date'], 'equity': total_equity, 'position': position }) # ---- 空仓时:检查买入条件 ---- if position == 0 and i > 0: prev = df.iloc[i - 1] # 上一根K线("昨天") # 条件1:昨天是阳线(收盘 > 开盘) is_yang = prev['close'] > prev['open'] # 条件2:今天最高价突破昨天高点 if is_yang and row['high'] > prev['high']: # 用昨天高点+1点作为买入价(保守一点) buy_price = prev['high'] + 1 margin = buy_price * 10 * 0.10 # 螺纹钢保证金约10% if capital >= margin: position = 1 entry_price = buy_price capital -= margin # 扣除保证金 trades.append({ 'date': row['date'], 'action': 'BUY', 'price': buy_price, 'reason': f'突破昨日高点 {prev["high"]}' }) # ---- 持仓时:检查止盈止损 ---- if position > 0: profit = row['high'] - entry_price # 用最高价判断(最乐观) # 先检查止盈(价格先到了哪个就执行哪个) if profit >= PROFIT_TARGET: sell_price = entry_price + PROFIT_TARGET pnl = (sell_price - entry_price) * 10 * position capital += margin + pnl trades.append({ 'date': row['date'], 'action': 'SELL', 'price': sell_price, 'pnl': pnl, 'reason': f'止盈 +{PROFIT_TARGET}点' }) position = 0 entry_price = 0 elif row['low'] - entry_price <= -STOP_LOSS: sell_price = entry_price - STOP_LOSS pnl = (sell_price - entry_price) * 10 * position capital += margin + pnl trades.append({ 'date': row['date'], 'action': 'SELL', 'price': sell_price, 'pnl': pnl, 'reason': f'止损 -{STOP_LOSS}点' }) position = 0 entry_price = 0 # ---- 日线结束时如果还持仓,按收盘价计算盈亏 ---- # (这里简化处理:不隔夜持仓,收盘强制平仓) if position > 0: sell_price = row['close'] pnl = (sell_price - entry_price) * 10 * position capital += margin + pnl trades.append({ 'date': row['date'], 'action': 'SELL', 'price': sell_price, 'pnl': pnl, 'reason': '收盘平仓' }) position = 0 entry_price = 0

====================================

输出结果

====================================

final_equity = equity_curve[-1]['equity'] total_return = (final_equity - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100 print("=" * 50) print("📊 回测结果") print("=" * 50) print(f"初始资金:{INITIAL_CAPITAL:,.0f} 元") print(f"最终资金:{final_equity:,.0f} 元") print(f"总收益率:{total_return:+.2f}%") print(f"交易次数:{len(trades) // 2} 次({len(trades)} 笔)")

计算胜率

wins = sum(1 for t in trades if t.get('pnl', 0) > 0) losses = sum(1 for t in trades if t.get('pnl', 0) < 0) total_closed = wins + losses if total_closed > 0: print(f"盈利次数:{wins}") print(f"亏损次数:{losses}") print(f"胜率:{wins / total_closed * 100:.1f}%")

计算最大回撤

equity_series = [e['equity'] for e in equity_curve] peak = equity_series[0] max_drawdown = 0 for eq in equity_series: if eq > peak: peak = eq dd = (peak - eq) / peak * 100 if dd > max_drawdown: max_drawdown = dd print(f"最大回撤:{max_drawdown:.2f}%")

打印所有交易记录

print("\n📋 交易记录:") print("-" * 70) print(f"{'日期':<12} {'操作':<6} {'价格':<10} {'盈亏':>10} {'原因'}") print("-" * 70) for t in trades: pnl_str = f"{t.get('pnl', 0):+.0f}元" if 'pnl' in t else "" print(f"{t['date']:<12} {t['action']:<6} {t['price']:<10.0f} {pnl_str:>10} {t['reason']}")

====================================

画资金曲线图

====================================

dates = [e['date'] for e in equity_curve] equities = [e['equity'] for e in equity_curve] plt.figure(figsize=(12, 6)) plt.plot(dates, equities, color='#FF6B35', linewidth=1.5) plt.axhline(y=INITIAL_CAPITAL, color='#58A6FF', linestyle='--', alpha=0.5, label='初始资金') plt.title('突破策略回测 - 资金曲线', fontsize=16) plt.xlabel('日期', fontsize=12) plt.ylabel('资金 (元)', fontsize=12) plt.legend() plt.grid(True, alpha=0.3)

旋转x轴标签(日期太多的话)

plt.xticks(rotation=45) plt.tight_layout() plt.savefig('backtest_result.png', dpi=150) print(f"\n📈 资金曲线已保存:backtest_result.png")


五、逐段拆解——回测引擎在干什么

数据加载

df = pd.read_csv("rb_daily.csv")
df = df.sort_values('date')

把CSV读进来,按日期排序。确保K线是从早到晚排列的——回测必须从第一根开始,一根一根往后推。

回测循环——核心

for i in range(len(df)):
    row = df.iloc[i]
    # 判断买入/卖出...

这就像"时间机器"——你站在第一根K线开始,一天一天往后走。每一"天",你的策略看当天的数据,决定买不买、卖不卖。

买入逻辑

if is_yang and row['high'] > prev['high']:
    buy_price = prev['high'] + 1

跟上一集的策略一模一样:昨天阳线+今天突破昨天高点。回测里唯一不同的是——用 prev['high'] + 1 作为买入价,因为回测中我们假设"突破后以高于突破点1个点的价格成交"。

这叫滑点(Slippage)——真实交易中,你看到突破价的时候,那个价格已经过去了,你实际买到的价格通常比突破价高一点。回测中考虑滑点,结果更接近实盘。

止盈止损

if profit >= PROFIT_TARGET:
    sell_price = entry_price + PROFIT_TARGET
elif row['low'] - entry_price <= -STOP_LOSS:
    sell_price = entry_price - STOP_LOSS

用当天最高价判断止盈,最低价判断止损。这是一种简化——实际上止盈止损可能在盘中任意时刻触发,但日线回测我们只能用高低价来近似。

日线回测 vs 分钟线回测:

新手先用日线回测,验证大方向。后面有经验了再用分钟线回测做精细验证。

资金曲线

equity_curve.append({'date': row['date'], 'equity': total_equity})

每天记录一次你的"总资产"。把这些点连起来画成图,就是你的资金曲线——从第一天的10万到最后一天的总资产,中间所有起伏一目了然。


六、看懂回测结果——五个核心指标

跑完回测,你会看到一堆数字。哪些才是重点?

指标一:总收益率

最终资金 ÷ 初始资金 - 1 = 总收益率

比如10万变成12万,总收益率就是20%。

但光看收益率不够。你需要知道——这个收益率是在多长时间内取得的?如果是三年20%,年化才6%,不如存银行。如果是一年20%,那就不错了。

指标二:胜率

盈利次数 ÷ 总交易次数 = 胜率

很多新手迷信高胜率,觉得"80%的交易都赚钱"就是好策略。错。

一个胜率40%的策略,如果每次赚的钱是每次亏的钱的2倍(盈亏比2:1),长期也是赚钱的:

胜率 × 盈亏比才是关键,不是胜率本身。

指标三:最大回撤

资金曲线从最高点到最低点的最大跌幅。

比如你的资金从10万涨到了13万,然后跌到了10.5万,最大回撤就是 (13-10.5)/13 ≈ 19.2%。

最大回撤告诉你:最坏的情况下,你会亏多少?

20%回撤意味着你在某个阶段亏掉了20%的利润(或本金)。你能扛得住吗?如果你每次看到账户亏20%就受不了、开始怀疑策略、频繁改参数——那你需要降低仓位,或者选一个回撤更小的策略。

指标四:盈亏比

平均每笔盈利 ÷ 平均每笔亏损 = 盈亏比

我们设的止盈50点、止损30点,理论盈亏比 = 50÷30 ≈ 1.67。

但实际盈亏比通常比理论值低(因为滑点、手续费)。回测跑出来的才是真实的盈亏比。

指标五:交易次数

总交易了多少次。

太少(比如一年只有3次)——结果可能有偶然性,说明策略太保守,机会太少。

太多(比如一年500次)——可能过度交易,大部分是小噪音,而且手续费会吃掉利润。

一般一年20-60次比较合理,具体看你的策略周期。


七、常见回测陷阱——别被"好结果"骗了

陷阱一:未来函数

你用了"未来"的数据来判断"过去"的操作。

举个例子:你在回测代码里写了 if row['close'] > row['open'](用收盘价判断)。但现实中,收盘价要到收盘才知道。你不能在盘中用收盘价做判断——那叫"知道了答案再做题"。

正确做法:只用你能实时获取的数据(当前价、最新K线)。未来函数会让回测结果虚高,实际交易会让你大跌眼镜。

陷阱二:过度拟合

你调参数调到完美匹配了历史数据,但未来不适用。

比如你试了20组止盈止损参数,选了收益率最高的一组(止盈47点、止损23点)。这组参数可能恰好在这个时间段表现最好,但换个时间段就崩了。

防范方法:

陷阱三:忽略手续费和滑点

回测里每一笔交易都有成本:

如果不扣这些成本,回测结果会比实盘好很多。新手最容易犯这个错误。

建议:回测里每笔交易扣2-3个点的滑点+实际手续费。

陷阱四:样本太少

你用3个月的数据做回测,结果"年化收益率80%"——别信。3个月的数据太少,偶然性太大,什么结论都不可靠。

至少用一年的数据。两年更好。


八、回测结果怎么看?一个判断框架

跑完回测后,按这个框架判断策略是否值得上实盘:

条件标准
总收益为正✅ 必须满足
最大回撤 < 20%✅ 新手建议
胜率 > 35%✅ 配合盈亏比看
盈亏比 > 1.5✅ 配合胜率看
交易次数 20-60次/年✅ 太多太少都不好
最近一年仍在盈利✅ 不能只有早年好

如果以上全部满足 → 可以小仓位实盘试跑。

如果有一项不满足 → 回去调策略或参数,再回测一遍。

如果多项不满足 → 换一个策略思路。


九、下一步:从回测到实盘

回测通过之后,不要直接大仓位实盘。按这个顺序来:

第一步:模拟盘验证(1-2周)

用SimNow模拟账户跑你的策略,跟实盘完全一样的环境,但用的是假钱。

看看:

第二步:小仓位实盘(1手,1-2个月)

模拟盘没问题了,切到实盘。但只做1手——就算全亏了也亏不了多少。

第三步:正常仓位

小仓位跑了1-2个月,跟回测结果差距不大(收益率在预期范围内),就可以用正常仓位了。


这一集你学到了什么

1. 回测就是用历史数据"虚拟"跑你的策略——零成本、零风险验证

2. 三个准备:历史数据 + 策略代码 + 回测引擎(50行代码)

3. 回测引擎的本质是一个循环:逐根K线推进,每根判断买卖

4. 五个核心指标:总收益率、胜率、最大回撤、盈亏比、交易次数

5. 四个陷阱:未来函数、过度拟合、忽略成本、样本太少

6. 实盘三步走:模拟盘 → 小仓位 → 正常仓位


全系列完结

五集走完了。你从一个"程序化交易是什么"的新手,到现在:

这五集给了你一个完整的入门路径——从概念到实操,从环境搭建到策略开发,从实盘下单到回测验证。

但程序化交易的路才刚开始。

后面你可以探索的方向:

这些都是进阶内容。如果你有兴趣继续深入,欢迎关注果子量化(gocxh.com),我们会持续分享。

记住一句话:程序化交易不是让你变富的魔法,而是让你不再凭感觉交易的工具。

工具有了,接下来看你怎么用。

感谢你读完了这个系列。如果你觉得有用,分享给身边做期货的朋友——也许你帮他省了两年弯路。


*本系列面向零基础读者。有疑问加微信 futures886 或在评论区留言。*

*果子量化(gocxh.com)— AI驱动的期货程序化交易平台*


果子量化 - AI驱动的期货程序化交易平台

官网:gocxh.com | 微信:futures886