【Python量化策略实战 #08】回测跑得好,实盘就拉胯?策略上线前必做的 4 项检查
系列收官篇。前 7 篇我们完成了选股、择时、仓位、回测框架、参数优化。本篇聚焦回测到实盘的最小闭环——把纸上收益变成可执行、可追踪、可复盘的模拟盘系统。
一、为什么回测≠实盘
回测时你默认了几件事:
- 你想买就能按收盘价买到
- 买卖没有摩擦成本
- 停牌日数据会自动跳过
- 再平衡频率不影响结果
实盘里,以上全部不成立。手续费、滑点、停牌、再平衡延迟,每一项都会蚕食收益。本文用 4 个检查项,帮你把回测修成"能上线"的样子。
二、本文你将得到什么
- 成本模型:手续费 + 滑点一起算,代码里一行搞定
- 再平衡频率对比:每日/每周/每月调仓,哪种最适合你的策略
- 模拟盘 SQLite 记录表:每笔交易自动落库,方便复盘审计
- 基于真实行情数据的完整可运行代码
三、检查项 1:数据对齐与停牌处理
好消息是,如果你用统一的 API 拉日线数据(如 stock_history),天然就是交易日对齐的——非交易日根本没有行,不需要手动剔除。
但要留意:跨股票回测时,不同票的停牌日不同,需要用 merge 对齐后再计算信号。单票策略则无需额外处理。
四、检查项 2:成本模型(手续费 + 滑点)
很多回测框架默认成本为 0,导致高频策略被严重高估。一个务实的成本模型:
- 手续费:券商佣金 + 印花税 + 过户费 ≈ 单边千 1~千 1.5
- 滑点:流动性差的票,下单价与成交价偏差 ≈ 单边千 0.5~千 1
- 合计:单边 TOTAL_COST = 0.0015(保守估计)
代码实现只需在成交时扣一次:
TOTAL_COST = 0.0015 # 手续费+滑点,单边
买入成交时
equity *= (1 - TOTAL_COST)
不要小看这千 1.5。一个月均交易 4 次的策略,年化成本 ≈ 0.0015 × 2 × 4 × 12 = 14.4%。高频策略如果没有足够超额收益,直接被成本吃掉。
五、检查项 3:再平衡频率对比
同样的双均线策略,每日调仓 vs 每周调仓 vs 每月调仓,结果可能完全不同。
我们用同一组参数(短 5 / 长 15)跑三种频率:
from mairui import Client
import pandas as pd
import numpy as np
client = Client("LICENCE-66D8-9F96-0C7F0FBCD073")
STOCK = "600519"
raw = client.stock_history(STOCK, "d", "f")
df = pd.DataFrame(raw)
df["date"] = pd.to_datetime(df["t"])
df = df.sort_values("date").reset_index(drop=True)
TOTAL_COST = 0.0015
def backtest_freq(prices, short=5, long=15, rebalance="daily"):
ma_s = prices["c"].rolling(short).mean()
ma_l = prices["c"].rolling(long).mean()
signal = (ma_s > ma_l).astype(int)
pos = 0
equity = 1.0
trades = 0
for i in range(1, len(prices)):
do_trade = False
if rebalance == "daily":
do_trade = True
elif rebalance == "weekly":
do_trade = prices["date"].iloc[i].weekday() == 0
elif rebalance == "monthly":
do_trade = prices["date"].iloc[i].day == 1
target = 1 if signal.iloc[i] == 1 else 0
if do_trade and target != pos:
pos = target
trades += 1
if pos == 1:
equity *= (1 - TOTAL_COST)
ret = prices["c"].iloc[i] / prices["pc"].iloc[i] - 1.0
if pos == 1:
equity *= (1 + ret)
r = pd.Series([...]) # 日收益率序列略
sharpe = np.sqrt(252) * r.mean() / r.std() if r.std() > 1e-12 else 0.0
cum = pd.Series([...])
dd = (cum / cum.cummax() - 1).min()
return {
"rebalance": rebalance,
"total_return": round((equity - 1) * 100, 2),
"sharpe": round(sharpe, 3),
"max_drawdown": round(dd * 100, 2),
"trades": trades,
}
for freq in ["daily", "weekly", "monthly"]:
res = backtest_freq(df, rebalance=freq)
print(f"{freq:8s} | total={res['total_return']:6.2f}% | sharpe={res['sharpe']:6.3f} | "
f"dd={res['max_drawdown']:6.2f}% | trades={res['trades']}")
演示数据输出(50 条日线):
daily | total= 1.66% | sharpe= 0.850 | dd= -2.55% | trades=4
weekly | total= -0.69% | sharpe=-0.389 | dd= -2.75% | trades=4
monthly | total= 0.00% | sharpe= 0.000 | dd= 0.00% | trades=0
解读:
- 演示窗口内 daily 略优于 weekly,但差距不大
- monthly 未触发交易(数据窗口仅约 2.5 个月,月初未穿越均线)
- 核心结论:再平衡频率不是越频繁越好,而是要匹配策略信号的天然周期。均线类策略一般日线或周线即可,日内高频才需每日多次。
六、检查项 4:模拟盘 SQLite 记录
回测再漂亮也只是数字。要上线,必须先跑模拟盘——用真实行情信号,记录"如果今天执行,会买什么、卖什么"。
我们用 SQLite 建一张最小交易记录表:
import sqlite3
conn = sqlite3.connect("paper_trading.db")
cur = conn.cursor()
cur.execute("""
CREATE TABLE trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
strategy TEXT,
stock TEXT,
date TEXT,
action TEXT,
price REAL,
shares INTEGER,
cost REAL,
note TEXT
)
""")
每次交易后写入
cur.execute("""
INSERT INTO trades (strategy, stock, date, action, price, shares, cost, note)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", ("MA5_15", "600519", "2025-10-15", "BUY", 11.04, 100, 2.76, "demo"))
conn.commit()
演示数据写入 4 笔交易后查询:
记录笔数: 4, 累计交易成本: ¥11.10
(1, 'MA5_15', '600519', '2025-10-15', 'BUY', 11.04, 100, 2.76, 'demo')
(2, 'MA5_15', '600519', '2025-10-31', 'SELL', 10.96, 100, 2.74, 'demo')
(3, 'MA5_15', '600519', '2025-11-06', 'BUY', 11.15, 100, 2.79, 'demo')
(4, 'MA5_15', '600519', '2025-11-28', 'SELL', 11.25, 100, 2.81, 'demo')
模拟盘的核心价值:
- 信号是否"可执行"(有没有停牌、涨跌停导致无法成交)
- 成本是否被低估(真实手续费 + 滑点)
- 策略是否有"未来函数"(用到了当天收盘后才能知道的信息)
建议至少跑 1~3 个月模拟盘,记录每日信号与实际能成交价格的差异,再决定是否上实盘。
七、常见坑
- 滑点低估:小盘股或开盘集合竞价阶段,滑点可能远超千 1。保守回测建议单边成本按千 2~千 3 试。
- 再平衡与信号冲突:如果你的策略是"收盘后计算信号、次日开盘执行",回测时必须用次日开盘价而非收盘价计算收益,否则会引入未来函数。
- 模拟盘不复盘:很多人跑模拟盘只记收益,不记"为什么今天没成交"。SQLite 的 note 字段就是用来写原因的。
- 忽视资金管理:回测默认全仓进出,实盘需留现金应对连续止损。建议回测时加入"最大仓位 80%"约束。
八、小结与系列回顾
至此,「Python 量化策略实战」系列 8 篇全部完结。我们从零跑通了:
篇号 主题 核心能力
01 单因子选股 动量 top10 + 固定持仓回测
02 多因子合成 mom/vol/turn 三因子打分
03 双均线择时 金叉死叉 + 成交量过滤
04 波动率突破 Donchian 通道 + ATR 过滤
05 仓位管理 等权 / 风险平价 / Kelly 公式
06 向量化回测框架 50 行通用引擎
07 参数优化 网格搜索 + Walk-Forward 防过拟合
08 上线 checklist 成本/再平衡/模拟盘 SQLite
下一步建议:
- 把 #06 的通用回测框架与 #07 的参数优化结合起来,做一个"策略工厂"
- 用 #08 的 SQLite 模拟盘记录跑 3 个月真实信号跟踪
- 订阅稳定行情数据源,保证回测与实盘数据一致
如果你需要稳定、低延迟的 A 股行情 API 来支撑策略落地,可以参考麦蕊智数的开发者文档获取接口列表与免费试用方案。
免责声明:本文仅供技术学习交流,不构成任何投资建议。量化策略回测表现不代表未来收益,投资有风险,决策需谨慎。

浙公网安备 33010602011771号