Install
openclaw skills install @gechengling/security-quant-backtestAI-powered quantitative backtesting tool for China A-share strategies, supporting design, historical tests, performance attribution, walk-forward analysis, a...
openclaw skills install @gechengling/security-quant-backtestEnglish: AI-powered quantitative backtesting laboratory — covers strategy design, historical backtesting, performance attribution, walk-forward analysis, and Monte Carlo simulation. Built for quant analysts and algorithmic traders.
中文: 量化回测实验室——覆盖策略设计、历史回测、绩效归因、前向分析、蒙特卡洛模拟。适用:量化分析师、算法交易者、Python回测开发。
| 动态类型 | 内容摘要 | 影响范围 | 回测侧应对动作 | 责任岗 | 优先级 |
|---|---|---|---|---|---|
| 证券监管 | 2026年A股量化资金占比30%-40%,回测需考虑拥挤度因子 | 回测框架需增加拥挤度、压力测试和合规成本模块 | 回测报告增加拥挤度指标与容量估算 | 研究 | 高 |
| 证券监管 | 2026年3月量化踩踏事件:回测模型需加入极端行情压力测试 | 回测框架需增加拥挤度、压力测试和合规成本模块 | 回测加入流动性枯竭情景的损失估算 | 研究 | 高 |
| 证券监管 | 算法监管趋严,高频策略回测需考虑合规成本 | 回测框架需增加拥挤度、压力测试和合规成本模块 | 成本模型中单列合规与报告成本 | 合规 | 高 |
| 程序化交易 | 程序化交易报告与异常交易监控要求细化 | 高频与中高频策略 | 回测中增加报撤单频率与异常交易约束 | 合规 | 中 |
| 交易成本 | 佣金、印花税与过户费口径需与实盘一致 | 成本模型、净收益指标 | 成本参数按最新费率更新并标注生效日期 | 研究 | 高 |
| 数据质量 | 回测输入数据需可追溯 | 行情与财务数据 | 数据源、复权方式与取数日期须在报告中标注 | 研究 | 高 |
| 投资者保护 | 量化产品业绩展示与宣传表述趋严 | 回测业绩对外展示 | 回测结果标注为模拟结果、不构成收益承诺 | 合规 | 高 |
| 风控要求 | 策略容量与集中度管理要求提升 | 策略规模与持仓集中度 | 输出策略容量估算与集中度约束 | 风控 | 中 |
数据截止: 2026-09-12 | 来源:证监会、交易所公开规则、行业公开信息 声明: 以上动态供参考,具体以官方最新发布为准
动态解读示例(四类高频场景)
| Pain Point / 痛点 | Impact / 影响 | Solution / 本Skill解决方案 | 量化基线指标 / Baseline |
|---|---|---|---|
| 未来函数 | 回测虚高,实盘亏损 | 信号对齐检查+严格回测规范 | 前视检查项通过率 100% |
| 过拟合 | 参数过度优化,实盘失效 | 样本外测试+统计显著性检验 | 样本外/样本内收益比 ≥0.6 |
| 滑点假设 | 低估交易成本,实盘收益缩水 | 多场景滑点模拟 | 至少3档滑点情景对比 |
| 幸存者偏差 | 只用现存股票,忽视退市股 | 使用完整历史数据 | 含退市标的的完整样本 |
| 执行缺口 | 回测vs实盘收益差异大 | 分层回测+执行模拟 | 回测与实盘差异 ≤20% |
| 容量未估 | 规模扩大后收益快速衰减 | 容量估算模型 | 给出容量上限与衰减曲线 |
| 参数敏感 | 参数微调收益剧变 | 参数敏感性热力图 | 邻域内收益波动 <30% |
| 成本失真 | 未含印花税/合规成本 | 完整成本模型 | 成本项完整率 100% |
English Triggers: quantitative backtesting, algorithmic trading, strategy research, Python backtest, performance analysis, Monte Carlo, walk-forward analysis, A-share strategy
中文触发词(优先): 量化回测 / 算法交易 / 策略研究 / Python回测 / 绩效归因 / 蒙特卡洛 / 前向分析 / 趋势跟踪 / 均值回归 / 配对交易 / 双均线 / 海龟策略 / RSI策略 / 布林带策略 / 策略优化 / 参数寻优 / 机器学习选股 / Alpha因子 / 多因子策略
import pandas as pd
import numpy as np
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
class BacktestEngine:
"""量化回测引擎"""
def __init__(self, initial_capital: float = 1000000,
commission_rate: float = 0.0003,
stamp_tax: float = 0.001,
slippage: float = 0.001):
"""
Args:
initial_capital: 初始资金
commission_rate: 佣金费率(含规费)
stamp_tax: 印花税率(仅卖出)
slippage: 滑点(百分比)
"""
self.initial_capital = initial_capital
self.commission_rate = commission_rate
self.stamp_tax = stamp_tax
self.slippage = slippage
# 持仓状态
self.cash = initial_capital
self.position = {} # {stock_code: shares}
self.equity_curve = []
self.trades = []
def run(self, data: pd.DataFrame, signals: pd.DataFrame,
strategy_name: str = "Strategy") -> dict:
"""
执行回测
Args:
data: 价格数据(含收盘价、开盘价、最高、最低价)
signals: 交易信号(1=买入, -1=卖出, 0=持有)
strategy_name: 策略名称
"""
results = []
for date in data.index:
price = data.loc[date, "close"]
# 获取当日信号
if date in signals.index:
signal = signals.loc[date]
if signal == 1: # 买入信号
self._buy(date, price, self.cash * 0.95) # 保留5%现金
elif signal == -1: # 卖出信号
self._sell(date, price)
# 更新权益
portfolio_value = self._calculate_portfolio_value(price)
self.equity_curve.append({
"date": date,
"portfolio_value": portfolio_value,
"cash": self.cash
})
return self._generate_report(strategy_name)
def _buy(self, date, price, target_amount):
"""买入执行(含滑点+佣金)"""
buy_price = price * (1 + self.slippage)
shares = int(target_amount / buy_price / 100) * 100 # 100股整数
if shares > 0:
cost = shares * buy_price
commission = cost * self.commission_rate
if cost + commission <= self.cash:
self.cash -= (cost + commission)
self.trades.append({
"date": date, "action": "BUY",
"price": buy_price, "shares": shares,
"commission": commission
})
def _sell(self, date, price):
"""卖出执行(含滑点+佣金+印花税)"""
sell_price = price * (1 - self.slippage)
for stock, shares in list(self.position.items()):
if shares > 0:
proceeds = shares * sell_price
commission = proceeds * self.commission_rate
tax = proceeds * self.stamp_tax
self.cash -= (commission + tax)
self.cash += proceeds
self.trades.append({
"date": date, "action": "SELL",
"price": sell_price, "shares": shares,
"commission": commission, "tax": tax
})
def _calculate_portfolio_value(self, current_price):
"""计算组合市值"""
position_value = sum(
shares * current_price
for stock, shares in self.position.items()
)
return self.cash + position_value
def _generate_report(self, strategy_name: str) -> dict:
"""生成回测报告"""
equity_df = pd.DataFrame(self.equity_curve)
equity_df.set_index("date", inplace=True)
equity_df["returns"] = equity_df["portfolio_value"].pct_change()
# 核心指标计算
total_return = (equity_df["portfolio_value"].iloc[-1] /
self.initial_capital - 1) * 100
annual_return = ((1 + total_return/100) **
(252/len(equity_df)) - 1) * 100
volatility = equity_df["returns"].std() * np.sqrt(252) * 100
sharpe_ratio = (annual_return - 2.75) / volatility # 假设无风险利率2.75%
# 最大回撤
cummax = equity_df["portfolio_value"].cummax()
drawdown = (equity_df["portfolio_value"] - cummax) / cummax
max_drawdown = drawdown.min() * 100
# 卡尔玛比率
calmar_ratio = annual_return / abs(max_drawdown) if max_drawdown != 0 else 0
return {
"strategy": strategy_name,
"period": f"{equity_df.index[0].date()} to {equity_df.index[-1].date()}",
"total_return": round(total_return, 2),
"annual_return": round(annual_return, 2),
"volatility": round(volatility, 2),
"sharpe_ratio": round(sharpe_ratio, 2),
"max_drawdown": round(max_drawdown, 2),
"calmar_ratio": round(calmar_ratio, 2),
"total_trades": len([t for t in self.trades if t["action"] == "BUY"]),
"win_rate": self._calculate_win_rate(),
"equity_curve": equity_df
}
def _calculate_win_rate(self) -> float:
"""计算胜率"""
if len(self.trades) < 2:
return 0
buy_trades = [t for t in self.trades if t["action"] == "BUY"]
sell_trades = [t for t in self.trades if t["action"] == "SELL"]
if len(sell_trades) == 0:
return 0
wins = sum(
1 for i, sell in enumerate(sell_trades)
if i < len(buy_trades) and
sell["price"] > buy_trades[i]["price"]
)
return wins / len(sell_trades) * 100
回测结果解读表(指标要配套看,不能只看收益)
| 指标 | 数值 | 参考区间 | 解读 |
|---|---|---|---|
| 总收益 | +186.4% | — | 需结合回测期长度 |
| 年化收益 | +16.8% | 10%-25% | 处于合理区间 |
| 年化波动率 | 22.4% | <25% | 波动偏高但可接受 |
| 夏普比率 | 0.63 | >0.5(含无风险2.75%) | 风险调整后收益一般 |
| 最大回撤 | −27.6% | <30% | 接近上限,需关注 |
| 卡尔玛比率 | 0.61 | >0.5 | 回撤控制尚可 |
| 交易次数 | 86次/年 | — | 换手偏高,成本敏感 |
| 胜率 | 48% | — | 低于50%,靠盈亏比取胜 |
关键提示:若胜率低于50%而收益为正,说明依赖少数大盈利单,需检验是否由个别极端行情贡献(做剔除前10%最佳交易后的收益检验)。
未来函数检查清单(回测虚高的头号原因)
| 检查项 | 错误写法 | 正确做法 |
|---|---|---|
| 信号时点 | 用当日收盘价生成信号并当日成交 | 用T日收盘生成信号,T+1开盘成交 |
| 财务数据 | 使用报告期财务数据但在报告发布前使用 | 按公告日(而非报告期)对齐数据 |
| 复权处理 | 用后复权价判断当时市值 | 历史价格用对应时点的前复权口径 |
| 指数成分 | 用当前成分股回溯历史 | 使用历史成分股名单 |
| 停牌处理 | 停牌日仍可交易 | 停牌日不可成交,信号顺延 |
| 涨跌停 | 涨跌停仍按目标价成交 | 涨跌停日按不可成交或部分成交处理 |
成本构成示例(一次完整买卖的成本拆解)
| 成本项 | 买入 | 卖出 | 合计 |
|---|---|---|---|
| 佣金(0.03%,双边) | 30元 | 30元 | 60元 |
| 印花税(0.1%,仅卖出) | — | 100元 | 100元 |
| 过户费(约0.001%) | 1元 | 1元 | 2元 |
| 滑点(0.1%,双边) | 100元 | 100元 | 200元 |
| 合计(10万元交易) | 131元 | 231元 | 362元 |
说明:单次往返成本约0.36%,若策略年换手20次,仅交易成本就接近7.2%,因此高换手策略必须把成本纳入优化目标。
# 示例策略:双均线交叉策略
class DualMovingAverageStrategy:
"""双均线策略"""
def __init__(self, short_window: int = 20, long_window: int = 60):
self.short_window = short_window
self.long_window = long_window
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
"""生成交易信号"""
signals = pd.Series(index=data.index, dtype=int)
# 计算均线
ma_short = data["close"].rolling(self.short_window).mean()
ma_long = data["close"].rolling(self.long_window).mean()
# 金叉买入,死叉卖出
position = 0
for i in range(self.long_window, len(data)):
if ma_short.iloc[i] > ma_long.iloc[i] and position == 0:
signals.iloc[i] = 1 # 买入
position = 1
elif ma_short.iloc[i] < ma_long.iloc[i] and position == 1:
signals.iloc[i] = -1 # 卖出
position = 0
return signals
# RSI均值回归策略
class RSIMeanReversionStrategy:
"""RSI均值回归策略"""
def __init__(self, period: int = 14,
oversold: float = 30,
overbought: float = 70):
self.period = period
self.oversold = oversold
self.overbought = overbought
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
"""RSI超卖买入,超买卖出"""
delta = data["close"].diff()
gain = (delta.where(delta > 0, 0)).rolling(self.period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(self.period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
signals = pd.Series(index=data.index, dtype=int)
position = 0
for i in range(self.period, len(data)):
if rsi.iloc[i] < self.oversold and position == 0:
signals.iloc[i] = 1 # 买入
position = 1
elif rsi.iloc[i] > self.overbought and position == 1:
signals.iloc[i] = -1 # 卖出
position = 0
return signals
# 布林带突破策略(含波动率过滤)
class BollingerBreakoutStrategy:
"""布林带突破策略"""
def __init__(self, window: int = 20, num_std: float = 2.0,
atr_window: int = 14, atr_stop: float = 2.5):
self.window = window
self.num_std = num_std
self.atr_window = atr_window
self.atr_stop = atr_stop
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
"""突破上轨买入,跌破中轨卖出,并以ATR作为止损参考"""
ma = data["close"].rolling(self.window).mean()
std = data["close"].rolling(self.window).std()
upper = ma + self.num_std * std
lower = ma - self.num_std * std
# ATR用于度量波动
high, low, close = data["high"], data["low"], data["close"]
tr = pd.concat([
high - low,
(high - close.shift()).abs(),
(low - close.shift()).abs()
], axis=1).max(axis=1)
atr = tr.rolling(self.atr_window).mean()
signals = pd.Series(index=data.index, dtype=int)
position = 0
entry_price = 0.0
for i in range(self.window, len(data)):
price = close.iloc[i]
stop_line = entry_price - self.atr_stop * atr.iloc[i]
if position == 0 and price > upper.iloc[i]:
signals.iloc[i] = 1
position = 1
entry_price = price
elif position == 1 and (price < ma.iloc[i] or price < stop_line):
signals.iloc[i] = -1
position = 0
entry_price = 0.0
return signals
三类策略的适用环境对比
| 策略 | 逻辑 | 适用市场 | 最大风险点 | 关键参数 |
|---|---|---|---|---|
| 双均线 | 趋势跟踪 | 单边趋势市 | 震荡市反复止损 | 短/长窗口 |
| RSI均值回归 | 超买超卖反转 | 震荡市 | 趋势市中逆势 | 周期、超买超卖阈值 |
| 布林带突破 | 波动扩张跟进 | 波动放大初期 | 假突破 | 窗口、标准差倍数、ATR止损 |
示例 1|参数寻优的正确做法
| 步骤 | 做法 | 目的 |
|---|---|---|
| 1 | 粗网格搜索(如MA参数 5-60,步长5) | 快速定位有效区域 |
| 2 | 观察参数邻域稳定性 | 避免落到孤立最优值 |
| 3 | 在样本外区间验证 | 检验是否过拟合 |
| 4 | 检查参数的经济含义 | 排除无逻辑的参数组合 |
| 5 | 固定参数做前向测试 | 确认可复现 |
示例 2|参数敏感性判断(用邻域看稳定性)
| 参数组合 | MA短 | MA长 | 年化收益 | 最大回撤 | 判断 |
|---|---|---|---|---|---|
| A | 20 | 60 | 16.8% | −27.6% | 中心值 |
| B | 18 | 60 | 15.9% | −26.8% | 邻域稳定 |
| C | 22 | 60 | 16.2% | −28.1% | 邻域稳定 |
| D | 5 | 20 | 34.5% | −41.2% | 孤立高收益,疑过拟合 |
| E | 40 | 120 | 11.2% | −22.4% | 反应迟缓,收益偏低 |
判断要点:中心值附近绩效平滑变化说明参数稳健;若出现"只有某个很窄的参数区间收益极高、邻域骤降",通常意味着曲线拟合(过拟合)而非真实的策略优势。
class MonteCarloSimulation:
"""蒙特卡洛模拟"""
def run_simulation(self, historical_returns: pd.Series,
n_simulations: int = 1000,
n_periods: int = 252,
initial_value: float = 1000000) -> dict:
"""
运行蒙特卡洛模拟
"""
mu = historical_returns.mean()
sigma = historical_returns.std()
simulations = np.zeros((n_simulations, n_periods))
simulations[:, 0] = initial_value
for t in range(1, n_periods):
random_returns = np.random.normal(mu, sigma, n_simulations)
simulations[:, t] = simulations[:, t-1] * (1 + random_returns)
# 统计结果
final_values = simulations[:, -1]
percentiles = {
"5th": np.percentile(final_values, 5),
"25th": np.percentile(final_values, 25),
"50th": np.percentile(final_values, 50),
"75th": np.percentile(final_values, 75),
"95th": np.percentile(final_values, 95)
}
# 概率分析
prob_loss = (final_values < initial_value).mean() * 100
return {
"percentiles": {k: round(v, 2) for k, v in percentiles.items()},
"probability_of_loss": round(prob_loss, 2),
"expected_return": round(final_values.mean() - initial_value, 2),
"var_95": round(initial_value - percentiles["5th"], 2),
"simulations": simulations
}
示例 1|蒙特卡洛结果解读(看分布,不看单点)
| 分位 | 期末价值(万元) | 相对初始(100万) | 解读 |
|---|---|---|---|
| 5th | 78.5 | −21.5% | 悲观情景,一年后亏损两成 |
| 25th | 94.2 | −5.8% | 四分之一概率不赚钱 |
| 50th | 112.6 | +12.6% | 中位情景 |
| 75th | 134.8 | +34.8% | 乐观情景 |
| 95th | 162.4 | +62.4% | 极端乐观情景 |
示例 2|蒙特卡洛的三种误用
| 误用 | 后果 | 正确做法 |
|---|---|---|
| 只用正态分布 | 低估极端损失 | 用历史自助抽样或t分布对比 |
| 参数来自短期样本 | 分布估计不稳 | 用足够长的样本并做分段检验 |
| 把模拟结果当作预测 | 误导决策 | 明确"模拟为假设推演,非收益预测" |
前向分析设计表
| 项目 | 设置 | 说明 |
|---|---|---|
| 训练窗口 | 36个月 | 用于参数拟合 |
| 测试窗口 | 6个月 | 用于样本外验证 |
| 滚动步长 | 6个月 | 窗口向前滚动 |
| 参数冻结 | 测试期内不变 | 防止"边测边调" |
| 输出 | 各测试段的收益、回撤 | 关注一致性而非平均 |
示例|样本内 vs 样本外对比(判断是否过拟合)
| 指标 | 样本内 | 样本外 | 衰减幅度 | 判断 |
|---|---|---|---|---|
| 年化收益 | 24.5% | 11.2% | −54% | 衰减明显,需谨慎 |
| 夏普比率 | 1.12 | 0.48 | −57% | 风险调整后收益下降 |
| 最大回撤 | −18.6% | −26.3% | 加深 7.7pct | 样本外更差 |
| 胜率 | 56% | 49% | −7pct | 优势减弱 |
经验口径:样本外年化收益不低于样本内的60%、夏普比率不低于样本内的50%,方可认为策略具备一定稳健性;衰减超过上述幅度时应重新审视参数与逻辑。
绩效归因分解表
| 归因维度 | 贡献 | 说明 |
|---|---|---|
| 择时(仓位) | +3.2% | 高仓位期恰逢上涨 |
| 选股(标的) | +9.6% | 主要收益来源 |
| 行业配置 | +2.1% | 超配强势行业 |
| 交易成本 | −4.3% | 换手偏高拖累 |
| 残差/其他 | +0.6% | 无法解释部分 |
| 合计 | +11.2% | 与样本外年化一致 |
使用要点:归因结果要与换手率、成本假设交叉验证;若"选股贡献"高度集中于个别标的,应在报告中标注集中度风险。
回测双均线策略:
回测双均线策略(MA20/MA60):
- 初始资金:100万
- 回测期:2020-01-01至2025-12-31
- 关注指标:收益率、夏普比率、最大回撤
蒙特卡洛模拟:
对当前持仓做蒙特卡洛模拟:
- 模拟次数:10000次
- 模拟期限:1年
- 置信区间:95%
前向分析:
对[策略名称]做前向分析:训练窗口36个月、测试窗口6个月、
滚动步长6个月,输出各测试段的收益与回撤,并对比样本内外的衰减幅度。
容量与成本评估:
评估[策略名称]的容量上限与成本影响:
- 输入:策略年换手、持仓标的日均成交额
- 输出:容量上限估算、滑点扩大情景下的净收益衰减
This skill provides backtesting tools for educational and research purposes. Backtesting results do not guarantee future performance. Past performance is not indicative of future results. Algorithmic trading involves substantial risk of loss.