量化投资(Quantitative Investing)是指利用数学模型、统计方法和计算机程序来制定和执行系统化投资策略的投资方式。与传统的基本面分析或技术分析不同,量化投资强调数据驱动、规则明确、可重复验证,旨在消除人为情绪干扰,实现稳健的超额收益。
根据 BarclayHedge 的数据,截至 2024 年,全球量化对冲基金管理资产规模超过 3.5 万亿美元,占所有对冲基金资产的 30% 以上。量化策略的年化换手率通常为 400–1200%,远高于传统主动管理基金的 20–80%。
量化投资体系的构建依赖四大支柱:
| 支柱 | 说明 | 典型工具/技术 |
|---|---|---|
| 数据基础设施 | 海量、高质量、低延时的数据源 | Tick 级行情、另类数据(卫星图像、信用卡交易)、自然语言处理 |
| 因子与模型 | 从数据中发现可预测的模式,构建定价和预测模型 | Fama-French 多因子模型、机器学习模型、统计套利模型 |
| 回测与验证 | 在历史数据上验证策略的统计显著性和稳健性 | 蒙特卡洛模拟、Walk-Forward 分析、交叉验证 |
| 执行系统 | 将策略信号转化为实际交易,最小化交易成本 | 算法交易(VWAP/TWAP)、做市算法、订单拆分 |
数据采集 → 因子挖掘 → 模型构建 → 回测验证 → 组合优化 → 实盘执行 → 绩效归因 → 迭代优化
↑ |
└──────────────────────────── 反馈循环 ────────────────────────────────────────┘
量化策略的成败很大程度上取决于数据基础设施的质量。一个典型的生产级数据流水线包含以下层级:
┌─────────────────────────────────────────────────┐
│ 数据源层 │
│ Level1行情 Level2行情 财务数据 另类数据 舆情数据 │
└─────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 采集层 │
│ TCP/UDP组播接收 WebSocket订阅 定时爬虫 API轮询 │
└─────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 存储层 │
│ 时序数据库(InfluxDB) 对象存储(MinIO/S3) 关系库(PostgreSQL) │
│ Tick数据(压缩存储) 日频/分钟数据 财务/参考数据 │
└─────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 计算层 │
│ 统一因子计算框架(PAI/Spark/Flink) │
│ 因子工厂:价量因子 → 基本面因子 → 另类因子 │
│ 行情预处理:复权处理、异常值过滤、缺失值插值 │
└─────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────┐
│ 服务层 │
│ 因子值查询API 信号生成服务 组合优化引擎 订单路由 │
└─────────────────────────────────────────────────┘
数值存储规模估算:
处理全 A 股 5000+ 只股票的高频数据所需存储:
| 数据粒度 | 单日数据量 | 年数据量 | 压缩后 |
|---|---|---|---|
| Tick 级(逐笔成交) | ~50 GB | ~12 TB | ~3 TB |
| 分钟级 OHLCV | ~500 MB | ~125 GB | ~30 GB |
| 日线 OHLCV | ~20 MB | ~5 GB | ~1 GB |
| 财务报表(季度) | — | ~2 GB | ~0.5 GB |
| 另类数据(情感/新闻) | ~1 GB | ~250 GB | ~60 GB |
一个全量高频数据库每年需要约 3–4 TB 的存储空间,加上备份和冗余,推荐至少配置 10 TB 以上的存储集群。
因子投资是量化投资最成熟、研究最深入的领域。其核心理念是:股票收益率可以分解为对若干个系统性风险因子的暴露。
Fama 和 French 在 1993 年提出的三因子模型是因子投资的奠基性工作:
其中:
2015 年,Fama-French 扩展为五因子模型:
新增两个因子:
下面用具体数值演示如何构造 HML(价值)因子。假设选取 2024 年 A 股数据:
步骤 1:按市值排序,分为大(Big)和小(Small)两组
| 股票 | 市值(亿元) | 分组 |
|---|---|---|
| 工商银行 | 18,000 | B |
| 贵州茅台 | 15,000 | B |
| 招商银行 | 8,000 | B |
| 宁德时代 | 6,500 | B |
| 格力电器 | 2,000 | S |
| 江淮汽车 | 500 | S |
| 三只松鼠 | 120 | S |
| 某某微型 | 30 | S |
分界点:市值中位数 ≈ 4,500 亿元
步骤 2:按账面市值比(B/P)排序,分为高(H)、中(M)、低(L)三组
| 股票 | B/P | 分组 |
|---|---|---|
| 工商银行 | 0.85 | H |
| 招商银行 | 0.72 | H |
| 格力电器 | 0.65 | H |
| 贵州茅台 | 0.30 | M |
| 江淮汽车 | 0.28 | M |
| 宁德时代 | 0.12 | L |
| 三只松鼠 | 0.10 | L |
| 某某微型 | 0.08 | L |
步骤 3:构建 6 个组合(S/H、S/M、S/L、B/H、B/M、B/L),计算每月等权收益率
步骤 4:HML = (S/H + B/H)/2 − (S/L + B/L)/2
假设某月的收益率数据(%):
| 组合 | 月收益率 |
|---|---|
| S/H | +3.2% |
| B/H | +2.1% |
| S/L | -1.5% |
| B/L | -0.8% |
即该月价值因子贡献 +3.80% 的收益。
以下是一段使用 Python 实现 Fama-French 三因子截面回归的代码:
import pandas as pd
import numpy as np
import statsmodels.api as sm
def ff3_cross_sectional_regression(returns_df, factor_df):
"""
对每个时间截面做 Fama-MacBeth 回归
Parameters
----------
returns_df : pd.DataFrame
个股收益率,index=日期, columns=股票代码
factor_df : pd.DataFrame
因子暴露,index=日期, columns=['MKT', 'SMB', 'HML', ...]
Returns
-------
pd.DataFrame: 每期因子溢价估计
"""
results = []
for date in returns_df.index:
y = returns_df.loc[date].dropna()
X = factor_df.loc[date].reindex(y.index).dropna()
common_idx = y.index.intersection(X.index)
if len(common_idx) < 30:
continue
y = y[common_idx]
X = sm.add_constant(X[common_idx])
model = sm.OLS(y, X).fit()
results.append({
'date': date,
'alpha': model.params.get('const', np.nan),
'rm_rf': model.params.get('MKT', np.nan),
'smb': model.params.get('SMB', np.nan),
'hml': model.params.get('HML', np.nan),
'rsquared': model.rsquared
})
return pd.DataFrame(results).set_index('date')
# 使用示例(伪数据)
# factor_premiums = ff3_cross_sectional_regression(stock_rets, stock_factors)
# print(factor_premiums[['alpha', 'rm_rf', 'smb', 'hml']].mean())
# >>> rm_rf: 0.82% (月均)
# >>> smb: 0.31% (月均)
# >>> hml: 0.42% (月均)
| 因子类别 | 因子名称 | 定义 | 逻辑 | 美国市场历史年化超额 |
|---|---|---|---|---|
| 价值 | 账面市值比 | B/P 高 | 市场过度反应 | +3.5% |
| 价值 | 盈利收益率 | E/P 高 | 被低估的盈利 | +4.0% |
| 规模 | 市值 | 小盘股 | 小公司风险溢价 | +2.0% |
| 动量 | 过去 12 个月收益 | 过去 12 个月(除最近 1 个月)涨幅高 | 趋势延续 | +8.0% |
| 质量 | ROE | 高净资产收益率 | 优秀公司持续优秀 | +3.0% |
| 质量 | 低负债 | 负债/资产低 | 财务安全溢价 | +2.5% |
| 低波动 | Beta | 低 Beta | 杠杆限制下的非对称需求 | +3.5% |
| 成长 | 营收增长 | 高营收增速 | 高增长预期 | +1.5% |
数据来源:AQR Capital Management 和 Kenneth French Data Library 的 1963–2023 年美股历史分析。实际表现会因时段和市场不同而变化。
将多个单因子有效组合是多因子策略的核心挑战。以下是三种主流方法及其 Python 实现:
def equal_weighted_factor_zscore(df_factors: pd.DataFrame) -> pd.Series:
"""
将多个因子标准化为 Z-Score 后等权相加
输入:df_factors 的每列是一个因子原始值
输出:综合得分(Z-Score 等权和)
数值示例:
┌─────────┬──────────┬─────────┬──────────┐
│ 股票 │ EP(Z) │ ROE(Z) │ MOM(Z) │ 综合得分 │
├─────────┼──────────┼─────────┼──────────┤
│ 股票A │ +1.5 │ +0.8 │ +1.2 │ ═ 3.5 │
│ 股票B │ -0.5 │ +1.5 │ +0.3 │ ═ 1.3 │
│ 股票C │ +0.2 │ -0.8 │ -1.0 │ ═ -1.6 │
│ 股票D │ -1.2 │ -1.5 │ -0.5 │ ═ -3.2 │
└─────────┴──────────┴─────────┴──────────┘
选股:做多综合得分 Top 20%,做空 Bottom 20%
"""
# 去极值:用中位数 ± 5 倍 MAD 截断
mad = np.abs(df_factors - df_factors.median()).median()
capped = df_factors.clip(
df_factors.median() - 5 * mad,
df_factors.median() + 5 * mad,
axis=1
)
# Z-Score 标准化
zscores = (capped - capped.mean()) / capped.std()
return zscores.mean(axis=1) # 等权求和
根据每个因子的历史 IC(信息系数)动态调整权重:
def ic_weighted_composite(factor_dict: dict, lookback=60):
"""
以过去 lookback 个月的 IC 为权重组合因子
factor_dict: {'EP': ep_series, 'ROE': roe_series, 'MOM': mom_series}
返回:股票 -> 综合得分
"""
weights = {}
total_abs = 0
for name, (factor_values, forward_returns) in factor_dict.items():
ic = factor_values.rank().corr(forward_returns.rank(), method='spearman')
# 滚动 IC 均值作为权重
ic_rolling = ic.rolling(window=lookback).mean().iloc[-1]
weights[name] = ic_rolling
total_abs += abs(ic_rolling)
composite = pd.Series(0.0, index=next(iter(factor_dict.values()))[0].index)
for name, (factor_values, _) in factor_dict.items():
weight = weights[name] / total_abs if total_abs > 0 else 1/len(factor_dict)
# 因子值本身已标准化
composite += weight * factor_values
return composite
使用 XGBoost 自动学习因子交互和非线性组合:
| 组合方法 | IC(月均) | 多空夏普 | 优点 | 缺点 |
|---|---|---|---|---|
| 等权 Z-Score | 0.04 | 0.85 | 稳定、可解释 | 忽略因子间相关性 |
| IC 动态加权 | 0.05 | 1.05 | 自适应市场 | 估计误差大 |
| XGBoost 组合 | 0.08 | 1.35 | 捕捉非线性 | 过拟合风险高 |
| 等权组合(简单平均) | 0.06 | 1.10 | 鲁棒性极佳 | 上限略低于 ML |
关键发现:等权组合(Simple Equal-weight Ensemble)在多因子组合中往往表现最优或接近最优,被称为"第 1.5 因子"——简单的组合本身就是一个强大的 Alpha 来源。
因子并非始终有效。例如,价值因子在 2007–2020 年间持续跑输成长因子,引发"价值因子已死"的讨论:
| 时间段 | 价值因子年化收益 | 成长因子年化收益 | 差异 |
|---|---|---|---|
| 1970–1989 | +5.2% | +2.1% | +3.1% |
| 1990–2006 | +3.8% | +4.5% | -0.7% |
| 2007–2020 | -1.5% | +6.2% | -7.7% |
| 2021–2024 | +4.8% | +0.5% | +4.3% |
因子择时(Factor Timing)试图预测因子表现并动态配置权重。常用方法包括:
因子拥挤的监测方法:
因子拥挤度可以用以下指标量化:
具体量化方法:
def calculate_factor_crowding(holdings_df, factor_exposure, window=60):
"""
计算因子拥挤度指标
holdings_df: 每只股票在因子组合中的权重
factor_exposure: 每只股票的因子暴露
"""
# 1. 持仓集中度:Herfindahl-Hirschman Index
hhi = (holdings_df ** 2).sum(axis=1)
# 2. 因子换手率(月度组合周转)
turnover = np.abs(holdings_df.diff()).sum(axis=1)
# 3. 因子收益预测波动
factor_vol = factor_exposure.rolling(window).std()
# 4. 拥挤度综合指标
crowding = (hhi * turnover) / (factor_vol + 1e-8)
# 5. 标准化(z-score)
return (crowding - crowding.mean()) / crowding.std()
# 示例输出:crowding > 2 表示极度拥挤,是因子崩溃的前兆
# >>> 2020-01: crowding = -0.5 (正常)
# >>> 2021-06: crowding = +2.3 (极度拥挤)
# >>> 2021-07: 因子崩溃发生 (-8.5% 单月损失)
统计套利(Statistical Arbitrage)利用资产价格之间的统计关系而非确定性关系来寻找交易机会。
配对交易是最经典的统计套利策略。以下演示一组完整的数值计算。
步骤 1:选择配对标的
选取两只同行业股票 A 和 B,过去 60 个交易日的相关系数为 0.85。
步骤 2:计算价差
| 日期 | A 价格 | B 价格 | 价差 (A − B) | Z-Score |
|---|---|---|---|---|
| T-3 | 100 | 95 | 5 | 0.5 |
| T-2 | 102 | 94 | 8 | 1.8 |
| T-1 | 99 | 97 | 2 | -1.2 |
| T | 97 | 98 | -1 | -2.1 |
价差的均值为 4.2,标准差为 2.5。
Z-Score 计算公式:
其中 为当前价差, 为历史均值, 为标准差。
T 日:
步骤 3:触发交易信号
| Z 值范围 | 信号 | 动作 |
|---|---|---|
| Z > +2.0 | 价差过大(正向偏离) | 做空 A,做多 B |
| Z < -2.0 | 价差过大(负向偏离) | 做多 A,做空 B |
| Z 回归至 0 附近 | 平仓 | 双向平仓 |
T 日 Z = -2.1,触发做多 A、做空 B 的信号。
步骤 4:建仓与平仓计算
步骤 5:平仓
假设 5 个交易日后,价差回归至均值:
| 日期 | A 价格 | B 价格 | 价差 | Z-Score | P&L(做多 A) | P&L(做空 B) | 总 P&L |
|---|---|---|---|---|---|---|---|
| T | 97 | 98 | -1 | -2.1 | 0 | 0 | 0 |
| T+1 | 98 | 98 | 0 | -1.6 | +1,000 | 0 | +1,000 |
| T+2 | 99 | 98 | 1 | -1.2 | +2,000 | 0 | +2,000 |
| T+3 | 100 | 97 | 3 | -0.4 | +3,000 | +1,000 | +4,000 |
| T+4 | 101 | 97 | 4 | 0 | +4,000 | +1,000 | +5,000 |
T+4 日价差回归至均值(4.0),平仓:
配对交易的前提是两只股票的价格序列存在协整关系(Cointegration),即它们有共同的随机趋势。常用 Engle-Granger 两步法检验:
第一步:对价格序列 和 做 OLS 回归:
第二步:对残差 进行单位根检验(ADF 检验)。若 是平稳序列,则 和 协整。
数值案例:
假设对 A 和 B 做回归得到 ,残差序列的 ADF 统计量为 -4.23:
| 指标 | 值 |
|---|---|
| ADF 统计量 | -4.23 |
| 1% 临界值 | -3.43 |
| 5% 临界值 | -2.86 |
| P 值 | 0.0006 |
ADF 统计量 -4.23 < 1% 临界值 -3.43,因此拒绝"残差非平稳"的原假设,确认两只股票协整。
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller, coint
def find_cointegrated_pairs(stock_prices: pd.DataFrame,
p_threshold: float = 0.05):
"""
在全市场股票中寻找协整配对
Parameters
----------
stock_prices: pd.DataFrame, columns=股票代码, index=日期
p_threshold: ADF 检验 p 值阈值
Returns
-------
List[tuple]: (stock_i, stock_j, p_value, half_life)
"""
n = stock_prices.shape[1]
pairs = []
for i in range(n):
for j in range(i + 1, n):
si, sj = stock_prices.columns[i], stock_prices.columns[j]
# 相关性筛选(快速)
corr = stock_prices[si].corr(stock_prices[sj])
if corr < 0.7:
continue
# Janus-Test 协整检验
score, pvalue, _ = coint(stock_prices[si], stock_prices[sj])
if pvalue < p_threshold:
# 计算半衰期
spread = stock_prices[si] - stock_prices[sj]
spread_lag = spread.shift(1)
delta_y = spread - spread_lag
spread_lag = spread_lag.dropna()
delta_y = delta_y.dropna()
reg = np.polyfit(spread_lag.values.ravel(),
delta_y.values.ravel(), 1)
half_life = -np.log(2) / reg[0] if reg[0] < 0 else np.inf
pairs.append((
si, sj, pvalue, half_life,
corr
))
return sorted(pairs, key=lambda x: x[2]) # 按 p 值排序
# 示例:在全 A 股中筛选
# pairs = find_cointegrated_pairs(a_share_close_prices)
# print(f"Found {len(pairs)} cointegrated pairs")
# print(pairs[:5])
# >>> Found 127 cointegrated pairs
# >>> [('600519.SH', '000858.SZ', 0.001, 12.3, 0.92), ...]
配对交易的扩展——多标的统计套利包括:
动量策略是全球量化投资中应用最广泛的策略之一。Jegadeesh 和 Titman(1993)首次系统验证了动量效应(Momentum Effect)。
| 维度 | 时间序列动量(Trend Following) | 截面动量(Cross-Sectional) |
|---|---|---|
| 定义 | 做多近期上涨资产,做空近期下跌资产(基于绝对值) | 做多相对强势的资产,做空相对弱势的资产(基于相对排序) |
| 信号 | 过去 N 个月收益 > 0 → 做多;< 0 → 做空 | 按过去 N 个月收益排序,做多 Top K%,做空 Bottom K% |
| 典型参数 | 12 个月回顾期,1 个月持有 | 12 个月回顾期(跳过最近 1 个月),1 个月持有 |
| 容量 | 较大(可应用于指数、期货、商品) | 中等(需要足够多的截面标的) |
| 代表研究 | Moskowitz, Ooi & Pedersen (2012) | Jegadeesh & Titman (1993, 2001) |
假设在 A 股市场测试截面动量策略(2023 年数据):
构建方法:
分组表现对比(2023 年度,年化收益率):
| 分组 | 年化收益率 | 年化波动率 | 夏普比率 | 最大回撤 |
|---|---|---|---|---|
| Top 10%(赢家组合) | +18.5% | 22.3% | 0.83 | -16.5% |
| 第 2 十分位 | +12.2% | 20.1% | 0.61 | -14.2% |
| 第 3–8 十分位(中间) | +5.8% | 18.5% | 0.31 | -18.0% |
| 第 9 十分位 | -3.5% | 24.0% | -0.15 | -25.3% |
| Bottom 10%(输家组合) | -8.2% | 28.5% | -0.29 | -32.1% |
| 多空组合(Top − Bottom) | +26.7% | 18.0% | 1.48 | -12.0% |
多空组合年化收益率 26.7% 中,约 18% 来自多头端、8.7% 来自空头端。空头端的收益主要来源于输家组合的负收益,而非做空收益本身。
def cross_sectional_momentum(returns: pd.DataFrame,
lookback: int = 252,
skip: int = 21,
top_pct: float = 0.1,
rebalance_freq: int = 21):
"""
截面动量策略回测
Parameters
----------
returns: 日收益率 DataFrame, index=日期, columns=股票
lookback: 回顾期(交易日数)
skip: 跳过最近 N 个交易日(避开短期反转)
top_pct: 做多/做空比例
Returns
-------
pd.Series: 策略每日收益率
"""
dates = returns.index
strat_returns = []
for i in range(lookback + skip, len(dates), rebalance_freq):
current_date = dates[i]
# 计算过去 lookback 个交易日的累计收益(跳过后面的 skip 天)
past_ret = returns.iloc[i - lookback - skip: i - skip].apply(
lambda x: (1 + x).prod() - 1, axis=0
)
# 排序
sorted_stocks = past_ret.sort_values()
n = len(sorted_stocks)
n_long = int(n * top_pct)
n_short = int(n * top_pct)
long_stocks = sorted_stocks.tail(n_long).index
short_stocks = sorted_stocks.head(n_short).index
# 持有期收益率
hold_end = min(i + rebalance_freq, len(dates))
hold_returns = returns.iloc[i:hold_end]
daily_ret = (hold_returns[long_stocks].mean(axis=1)
- hold_returns[short_stocks].mean(axis=1))
strat_returns.append(daily_ret)
return pd.concat(strat_returns)
# 使用示例
# strat = cross_sectional_momentum(daily_returns)
# sharpe = np.sqrt(252) * strat.mean() / strat.std()
# print(f"夏普比率: {sharpe:.2f}") # 预期 ~0.8-1.5
动量策略存在一个显著风险——动量崩盘(Momentum Crash)。当市场在下跌后急剧反转时,动量策略会遭受巨大损失:
| 事件 | 时间 | 动量策略损失 | 说明 |
|---|---|---|---|
| 2009 年 3 月 | 金融危机后反转 | -45%(1 个月) | 市场急速反弹,空头头寸暴涨 |
| 2020 年 4 月 | 新冠后反弹 | -38%(2 个月) | 科技股反弹力度远超传统行业 |
缓解方法:
| 方法 | 适用场景 | 优点 | 缺点 | 典型性能(A 股因子预测) |
|---|---|---|---|---|
| 线性回归 | 简单因子组合 | 可解释性强 | 无法捕捉非线性关系 | 1–3% |
| 逻辑回归 | 涨跌方向预测 | 概率输出、简单 | 线性决策边界 | 55–58% 准确率 |
| 随机森林 | 复杂因子组合 | 抗过拟合、特征重要性 | 可能欠拟合浅层信号 | 3–6% |
| XGBoost/LightGBM | 大多数因子预测 | 效率高、效果好 | 需要调参 | 5–12% |
| 神经网络(MLP) | 大规模数据 | 拟合能力强 | 容易过拟合、黑箱 | 4–10% |
| LSTM/Transformer | 时间序列预测 | 捕捉时序模式 | 数据需求大、训练慢 | 6–15%(长周期) |
| 强化学习 | 执行优化、组合管理 | 端到端优化 | 样本效率低、不稳定 | 视任务而定 |
因子预测 数据来源:中国 A 股市场 2010–2023 年实证,不同因子和时段有显著差异。A 股市场 通常低于美股。
以 A 股选股为例,常见特征维度:
基础因子(20+ 个):
衍生因子(100+ 个):
以下是一个完整的 XGBoost 选股流程(含过拟合防护):
import xgboost as xgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_squared_error
def train_xgboost_factor_model(features: pd.DataFrame,
forward_returns: pd.Series,
n_splits: int = 5):
"""
用时间序列交叉验证训练 XGBoost 因子模型
输入:
- features: 特征矩阵,每列是一个因子
- forward_returns: 未来 N 日收益率(标签)
输出:
- 特征重要性 DataFrame
- 样本外预测值
"""
# 时间序列交叉验证(不随机打乱)
tscv = TimeSeriesSplit(n_splits=n_splits)
oof_predictions = pd.Series(index=forward_returns.index, dtype=float)
feature_importance = []
params = {
'max_depth': 4, # 限制深度,防过拟合
'subsample': 0.8, # 行采样
'colsample_bytree': 0.6, # 列采样
'learning_rate': 0.05,
'n_estimators': 200,
'reg_lambda': 1.0, # L2 正则化
'reg_alpha': 0.1, # L1 正则化
'gamma': 0.1, # 分裂所需最小损失减少
'random_state': 42
}
for fold, (train_idx, val_idx) in enumerate(tscv.split(features)):
X_train = features.iloc[train_idx]
y_train = forward_returns.iloc[train_idx]
X_val = features.iloc[val_idx]
y_val = forward_returns.iloc[val_idx]
dtrain = xgb.DMatrix(X_train, label=y_train)
dval = xgb.DMatrix(X_val, label=y_val)
model = xgb.train(
params, dtrain,
num_boost_round=200,
evals=[(dtrain, 'train'), (dval, 'val')],
early_stopping_rounds=20,
verbose_eval=False
)
# 样本外预测
pred = model.predict(dval)
oof_predictions.iloc[val_idx] = pred
# 特征重要性
importance = pd.Series(
model.get_score(importance_type='gain'),
name=f'fold_{fold}'
)
feature_importance.append(importance)
# 汇总特征重要性
importance_df = pd.DataFrame(feature_importance).fillna(0)
importance_df['mean'] = importance_df.mean(axis=1)
importance_df = importance_df.sort_values('mean', ascending=False)
# 评估
ic = oof_predictions.rank().corr(forward_returns.rank(), method='spearman')
rank_ic = oof_predictions.corr(forward_returns, method='spearman')
print(f"Rank IC (因子预测值 vs 实际收益): {rank_ic:.4f}")
print(f"Top 5 重要特征: {importance_df.head(5).index.tolist()}")
return importance_df, oof_predictions
# 使用示例
# importance, pred = train_xgboost_factor_model(factor_df, forward_ret)
# >>> Rank IC (因子预测值 vs 实际收益): 0.0723
# >>> Top 5 重要特征: ['EP_TTM', 'ROE_Q', 'REV_MOM_6M', 'TURNOVER_20D', 'ANALYST_UP']
传统 LSTM 在长序列建模中存在梯度消失问题。Transformer 的自注意力机制能够捕捉更远距离的依赖关系:
输入: (Batch, Seq_len=60, Features=20)
│
▼
┌────────────────────────────┐
│ 多头自注意力 (Multi-Head) │
│ Q=K=V: 线性投影到 64 维 │
│ head=8, 每个 head 关注不同 │
│ 时间尺度(短期/中期/长期) │
└────────────┬───────────────┘
│
▼
┌────────────────────────────┐
│ 前馈网络 (Feed-Forward) │
│ 256 → 128 → 1 │
└────────────┬───────────────┘
│
▼
输出: (Batch,) — 个股未来收益率预测值
Transformer 对比 LSTM 在 A 股的实证结果(2018–2023):
| 模型 | 月均 Rank IC | 多空年化收益 | 训练时间/周期 | 参数数量 |
|---|---|---|---|---|
| 线性回归 | 0.031 | +6.2% | 5 秒 | 20 |
| LSTM(2层,128单元) | 0.062 | +11.5% | 15 分钟 | 68K |
| Transformer(4层,8head) | 0.081 | +15.8% | 45 分钟 | 185K |
| Transformer + 预训练 | 0.095 | +18.2% | 2 小时 | 185K (finetune) |
| XGBoost(参考) | 0.075 | +13.5% | 2 分钟 | — |
Transformer 的性能提升主要在两个方面:一是通过多头注意力捕捉不同周期的模式,二是自注意力的并行计算比 LSTM 更适合 GPU 加速。
由于深度学习模型是黑箱,量化投资中必须对其预测进行解释。SHAP(SHapley Additive exPlanations)是目前最常用的解释方法:
import shap
def interpret_ml_model(model, X_sample, feature_names):
"""
使用 SHAP 解释模型预测
X_sample: 某个时间点的因子截面数据
"""
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_sample)
# 单只股票解释
stock_idx = 5 # 第 6 只股票
print(f"股票 {feature_names[stock_idx]} 的预测分解:")
# SHAP 瀑布图的关键数值(示例输出)
stock_shap = shap_values[stock_idx]
prediction = model.predict(X_sample.iloc[[stock_idx]])[0]
base_value = explainer.expected_value
print(f" 基准值 (base value): {base_value:.4f}")
print(f" 预测值: {prediction:.4f}")
print(f" 关键因子贡献:")
# 按 SHAP 绝对值排序
for idx in np.argsort(-np.abs(stock_shap))[:5]:
factor_val = X_sample.iloc[stock_idx, idx]
print(f" {feature_names[idx]}: {stock_shap[idx]:+.4f} "
f"(因子值: {factor_val:.3f})")
return shap_values
# 输出示例:
# >>> 股票 '贵州茅台' 的预测分解:
# >>> 基准值 (base value): 0.0182
# >>> 预测值: 0.0355
# >>> 关键因子贡献:
# >>> ROE_Q: +0.0085 (因子值: 8.92%)
# >>> EP_TTM: -0.0032 (因子值: 1.85%)
# >>> MOM_12M: +0.0061 (因子值: 23.5%)
# >>> ANALYST_UP: +0.0048 (因子值: 3个上调)
# >>> TURNOVER_20D: -0.0021 (因子值: 0.85%)
机器学习在量化投资中的最大风险是过拟合。以下是一个经典案例:
| 策略 | 回测年化收益 | 实盘年化收益 | 差异 |
|---|---|---|---|
| 简单价值策略(EP > 80% 分位) | +12.5% | +11.8% | -0.7% |
| 500 个特征的 XGBoost 策略(无正则化) | +58.3% | -12.5% | -70.8% |
| 500 个特征的 XGBoost(交叉验证 + 正则化) | +18.2% | +13.5% | -4.7% |
| 100 个精选因子的神经网络(Dropout + Early Stopping) | +22.1% | +16.8% | -5.3% |
过拟合防护清单:
其中 为独立尝试的策略数。尝试 1000 个策略后,观察到未经调整夏普比率 3.0,调整后:
这意味着该"优秀"策略很可能只是数据挖掘的产物。
强化学习(Reinforcement Learning)在量化投资中的应用正在快速增长,尤其在执行优化和组合管理方面。
传统 VWAP/TWAP 无法根据实时市场状态动态调整。深度 RL 通过学习最优执行策略,动态平衡市场冲击和时间风险:
状态空间 (State):
├─ 当前持仓量 Q_t
├─ 剩余时间 T - t
├─ 当前市场状态:波动率 σ_t、买卖价差 S_t、订单簿深度
├─ 日内 VWAP 曲线进度
└─ 日内累计成交量 VWAP_deviation
动作空间 (Action): 本次应交易的股数 a_t ∈ [0, Q_t]
奖励函数 (Reward):
R_t = −[ α × 市场冲击(S_t, a_t) + β × 时间风险(σ_t, Q_t − a_t) ]
+ 实现价格的相对优势
PPO(Proximal Policy Optimization)在 A 股执行中的表现:
| 执行算法 | 冲击成本(bps) | vs VWAP 偏差 | 完成率 | 适用场景 |
|---|---|---|---|---|
| VWAP(基准) | 5.2 | 0.0 | 99.5% | 常规市场 |
| TWAP | 6.8 | 0.5 | 99.8% | 低波动市场 |
| PPO(训练 6 个月) | 3.1 | 0.8 | 99.2% | 高波动市场 |
| DQN | 3.8 | 1.2 | 98.5% | 中等波动市场 |
| 自适应 VWAP | 4.5 | 0.3 | 99.6% | 大单交易 |
PPO 在高波动市场中的表现最优,冲击成本比 VWAP 降低 40%。但在牛市中冲击成本已很低时,优势不明显。
使用深度 RL 直接进行动态资产配置:
import numpy as np
class DRLPortfolioManager:
"""
深度强化学习组合管理(概念框架)
状态: 各资产近期收益率、波动率、相关性、宏观经济指标
动作: 各资产配置权重(约束:和为1,均≥0)
奖励: 夏普比率(滚动30日)
"""
def __init__(self, n_assets=10):
self.n_assets = n_assets
self.state_dim = n_assets * 4 # 收益率+波动率+动量+相关性
self.action_dim = n_assets
def softmax_weights(self, raw_actions):
"""将原始动作转换为满足约束的权重(和为1,≥0)"""
exp_a = np.exp(raw_actions - np.max(raw_actions))
return exp_a / exp_a.sum()
def compute_reward(self, portfolio_returns):
"""滚动夏普比率"""
if len(portfolio_returns) < 30:
return 0.0
ret = np.mean(portfolio_returns[-30:]) * 252
vol = np.std(portfolio_returns[-30:]) * np.sqrt(252)
return ret / (vol + 1e-8)
# 数值示例:RL 组合 vs 传统方法的 5 年回测
# ┌─────────────────┬─────────┬─────────┬─────────┐
# │ 方法 │ 年化收益 │ 夏普 │ 最大回撤 │
# ├─────────────────┼─────────┼─────────┼─────────┤
# │ 等权组合 │ 8.5% │ 0.72 │ -18.5% │
# │ 风险平价 │ 7.2% │ 0.85 │ -12.3% │
# │ Mean-Variance │ 10.1% │ 0.91 │ -22.0% │
# │ DRL (PPO) 组合 │ 12.8% │ 1.12 │ -15.5% │
# │ DRL + 状态切换 │ 14.5% │ 1.28 │ -13.2% │
# └─────────────────┴─────────┴─────────┴─────────┘
市场微观结构研究交易过程和价格形成机制,是高频量化交易的理论基础。
限价订单簿(Limit Order Book)是市场的最底层数据:
──────────────────────────────────────────────────
卖单队列 价格 数量
──────────────────────────────────────────────────
100.50 5,000 ← 卖5
100.40 3,200 ← 卖4
100.30 8,000 ← 卖3
100.20 2,100 ← 卖2
100.10 1,500 ← 卖1 (Best Ask)
──────────────────────────────────────────────────
100.05 2,000 ← 买1 (Best Bid)
99.95 3,500 ← 买2
99.85 6,000 ← 买3
99.75 4,000 ← 买4
99.65 8,000 ← 买5
──────────────────────────────────────────────────
买卖价差 (Spread) = 100.10 − 100.05 = 0.05 元
中间价 (Mid-price) = (100.10 + 100.05) / 2 = 100.075 元
市场深度 (Depth) = 买1数量 + 卖1数量 = 3,500 股
订单不平衡(Order Imbalance)是重要的高频信号:
实际数据案例(某 A 股个股日内 5 分钟切片):
| 时间段 | 买方主动成交量 | 卖方主动成交量 | OI | 下 5 分钟收益 |
|---|---|---|---|---|
| 9:35 | 120,000 | 85,000 | +0.17 | +0.32% |
| 9:40 | 95,000 | 105,000 | -0.05 | -0.08% |
| 9:45 | 150,000 | 60,000 | +0.43 | +0.65% |
| 9:50 | 80,000 | 140,000 | -0.27 | -0.41% |
| 9:55 | 110,000 | 110,000 | 0.00 | -0.02% |
OI > 0.15 时,下 5 分钟正向收益的概率为 68%(基于该股历史数据)。
价格变化可以分解为:
其中 为买卖价差, 为信息冲击。
Rollen 有效价差模型:
该模型不需要订单簿数据,仅通过成交价格的序列相关估算隐含价差。
回测是量化策略开发的核心环节,也是最容易产生误导的环节。
Dashed 无偏回测五要素:
若换手率 600%/年,交易成本 0.2%/次,则年化成本 = 600% × 0.2% = 12%
一个生产级的回测引擎应采用事件驱动架构:
┌──────────────────┐ ┌────────────────────┐
│ 数据管理器 │────▶│ 事件引擎 │
│ - 行情数据提供 │ │ - 市场事件(Market) │
│ - 财务数据提供 │ │ - 订单事件(Order) │
│ - 参考数据 │ │ - 信号事件(Signal) │
└──────────────────┘ └────────┬───────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 策略模块 │ │ 风控模块 │ │ 绩效模块 │
│ - 信号生成 │ │ - 敞口限制 │ │ - 因子归因 │
│ - 头寸管理 │ │ - 止损检查 │ │ - Brinson │
│ - 订单发送 │ │ - VaR 监控 │ │ - 归因分析 │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
▼ ▼
┌────────────────────────────┐
│ 成交与滑点模拟 │
│ - 固定滑点 │
│ - 成交量比例冲击模型 │
│ - L2 订单簿回放模拟 │
└────────────────────────────┘
| 指标 | 公式 | 解读 | 优秀阈值 |
|---|---|---|---|
| 年化收益率 | 复利年化收益 | > 15% | |
| 年化波动率 | 风险度量 | < 20% | |
| 夏普比率 | 风险调整后收益 | > 1.0 | |
| 索提诺比率 | 仅考虑下行风险 | > 1.5 | |
| 卡玛比率 | 回撤调整收益 | > 2.0 | |
| 最大回撤 | 最坏情况损失 | < 15% | |
| 胜率 | 盈利交易次数 / 总交易次数 | 信号质量 | > 50% |
| 盈亏比 | 平均盈利 / 平均亏损 | 收益不对称性 | > 2.0 |
| 信息比率 | 相对于基准的超额 | > 0.5 |
回测中夏普比率与实盘夏普比率的关系:
根据 Harvey, Liu & Zhu (2016) 的研究,期刊发表的量化策略回测夏普比率的平均值为 2.5,但调整数据挖掘偏差后实盘夏普比率的中位数约为 0.5–0.8。
class EventDrivenBacktest:
"""
简化版事件驱动回测引擎
核心流程:
1. 数据加载 → 2. 事件循环 → 3. 信号生成 →
4. 风控检查 → 5. 模拟成交 → 6. 绩效记录
"""
def __init__(self, data, initial_capital=1_000_000):
self.data = data # OHLCV DataFrame
self.capital = initial_capital
self.positions = {} # {stock: shares}
self.trades = [] # 交易记录
self.equity_curve = [initial_capital]
def run(self, strategy_fn):
"""运行回测"""
for date, market_data in self.data.iterrows():
# 1. 检查是否需要调仓
signals = strategy_fn(date, market_data)
# 2. 生成目标仓位
for stock, signal in signals.items():
if signal == 'buy':
self.place_order(stock, 'buy',
self.capital * 0.1 / market_data[stock])
elif signal == 'sell':
self.place_order(stock, 'sell',
self.positions.get(stock, 0))
# 3. 记录净值
self.equity_curve.append(self.capital)
def place_order(self, stock, side, quantity):
"""模拟成交(含滑点)"""
price = self.data.loc[self.current_idx, stock]
slippage = price * 0.001 # 0.1% 滑点
fill_price = price + slippage if side == 'buy' else price - slippage
cost = fill_price * quantity * (1 + 0.0005) # 0.05% 佣金
if side == 'buy' and cost <= self.capital:
self.capital -= cost
self.positions[stock] = self.positions.get(stock, 0) + quantity
elif side == 'sell':
self.capital += fill_price * quantity * (1 - 0.001) # 0.1% 印花税
self.positions[stock] -= quantity
if self.positions[stock] == 0:
del self.positions[stock]
def get_performance(self):
"""计算绩效指标"""
returns = pd.Series(self.equity_curve).pct_change().dropna()
print(f"总收益率: {(self.equity_curve[-1] / self.equity_curve[0] - 1) * 100:.1f}%")
print(f"年化波动率: {returns.std() * np.sqrt(252) * 100:.1f}%")
print(f"夏普比率: {returns.mean() * 252 / (returns.std() * np.sqrt(252) + 1e-8):.2f}")
print(f"最大回撤: {self.max_drawdown() * 100:.1f}%")
def max_drawdown(self):
series = pd.Series(self.equity_curve)
return (series / series.cummax() - 1).min()
Walk-Forward Analysis(WFA)是最可靠的验证方法之一:
训练期1 测试期1
[████████████][░░░░░] → 记录绩效
训练期2 测试期2
[████████████][░░░░░] → 记录绩效
训练期3 测试期3
[████████████][░░░░░] → 记录绩效
训练期4 测试期4
[████████████][░░░░░] → 记录绩效
一个 WFA 的数值示例:
| WFA 轮次 | 训练期 | 测试期 | 训练期夏普 | 测试期夏普 | 衰减比例 |
|---|---|---|---|---|---|
| 1 | 2018-01–2020-12 | 2021-01–2021-06 | 1.82 | 0.95 | 52% |
| 2 | 2018-07–2021-06 | 2021-07–2021-12 | 1.65 | 1.12 | 68% |
| 3 | 2019-01–2021-12 | 2022-01–2022-06 | 1.48 | 0.78 | 53% |
| 4 | 2019-07–2022-06 | 2022-07–2022-12 | 1.55 | 0.85 | 55% |
| 5 | 2020-01–2022-12 | 2023-01–2023-06 | 1.70 | 0.92 | 54% |
| 平均 | — | — | 1.64 | 0.92 | 56% |
风险平价(Risk Parity)是量化组合管理的重要方法。其核心理念是:分配风险而非分配资本。
假设两个资产:股票(S)和债券(B),各自波动率 ,,相关性 。
传统 60/40 组合的风险贡献:
60/40 组合中,股票的边际风险贡献(MR)为:
计算结果:
| 指标 | 传统 60/40 | 风险平价 |
|---|---|---|
| 股票权重 | 60% | 21% |
| 债券权重 | 40% | 79% |
| 组合波动率 | 9.3% | 5.5% |
| 股票风险贡献 | 89% | 50% |
| 债券风险贡献 | 11% | 50% |
| 夏普比率 | 0.45 | 0.62 |
| 最大回撤(2008 年) | -32% | -15% |
风险平价组合的夏普比率显著优于传统 60/40 组合。
对于 个资产的投资组合,最小化组合风险函数:
最小方差组合(Global Minimum Variance Portfolio, GMVP)的解析解:
其中 为全 1 向量, 为协方差矩阵。
数值示例(3 资产):
| 资产 | 年化波动率 | 与资产 1 相关 | 与资产 2 相关 |
|---|---|---|---|
| 资产 1(股票) | 18% | 1.0 | 0.5 |
| 资产 2(债券) | 6% | 0.5 | 1.0 |
| 资产 3(黄金) | 14% | 0.1 | 0.2 |
协方差矩阵:
GMVP 权重:,,
组合波动率:
import numpy as np
from scipy.optimize import minimize
def risk_parity_portfolio(cov_matrix: np.ndarray) -> np.ndarray:
"""
使用 SQP 算法求解风险平价组合权重
Parameters
----------
cov_matrix: n×n 协方差矩阵
Returns
-------
np.ndarray: 风险平价权重
"""
n = cov_matrix.shape[0]
def risk_contribution(w):
"""计算每个资产的风险贡献"""
portfolio_var = w @ cov_matrix @ w
marginal_contrib = cov_matrix @ w
risk_contrib = w * marginal_contrib / np.sqrt(portfolio_var)
return risk_contrib
def objective(w):
"""风险贡献均等化的目标函数"""
rc = risk_contribution(w)
target = np.mean(rc)
return np.sum((rc - target) ** 2)
constraints = [
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0}
]
bounds = [(0.0, 1.0)] * n
w0 = np.ones(n) / n
result = minimize(objective, w0, method='SLSQP',
bounds=bounds, constraints=constraints)
return result.x
# 使用示例(3 资产)
# cov = np.array([[0.0324, 0.0054, 0.0025],
# [0.0054, 0.0036, 0.0017],
# [0.0025, 0.0017, 0.0196]])
# w = risk_parity_portfolio(cov)
# print(f"风险平价权重: {w}")
# >>> 风险平价权重: [0.211, 0.623, 0.166]
**在险价值(VaR)**衡量在给定置信水平下的最大可能损失:
**条件在险价值(CVaR)**衡量超出 VaR 的尾部损失期望。
数值示例:
| 组合 | 年化收益 | 年化波动 | VaR 95%(周) | CVaR 95%(周) |
|---|---|---|---|---|
| 纯股票 | +10% | 18% | -3.7% | -5.2% |
| 60/40 | +8% | 9.3% | -1.9% | -2.8% |
| 风险平价 | +6.5% | 5.5% | -1.1% | -1.7% |
实盘执行是量化策略从纸面到实际收益的关键环节。常见执行算法:
| 算法 | 策略 | 适用场景 | 市场冲击 | 时间风险 | 典型节省 |
|---|---|---|---|---|---|
| VWAP | 成交量加权平均价 | 大单分拆,日内完成 | 低 | 中 | 2–5 bps |
| TWAP | 时间加权平均价 | 无成交量预测时 | 低 | 中 | 1–3 bps |
| Implementation Shortfall | 最小化市场冲击 + 机会成本 | 急迫交易 | 中 | 低 | 5–15 bps |
| Adaptive | 基于实时流动性动态调整 | 高波动市场 | 可变 | 可变 | 3–10 bps |
| Dark Pool | 暗池成交 | 超大盘交易 | 极低 | 不完全成交 | 3–8 bps |
Almgren-Chriss 模型是最广泛使用的市场冲击模型:
其中 为交易量占日均成交量比例, 为交易时长, 为风险厌恶系数, 为冲击参数。
数值案例:
交易 100 万股某股票(日均成交量 1000 万股,占比 10%):
| 交易时长 | 市场冲击成本 | 时间风险 | 总成本 | 相当于 |
|---|---|---|---|---|
| 30 分钟 | 18 bps | 1 bps | 19 bps | 冲击 ≈ 0.02% |
| 2 小时 | 12 bps | 3 bps | 15 bps | 冲击 ≈ 0.015% |
| 4 小时 | 8 bps | 5 bps | 13 bps | 冲击 ≈ 0.01% |
| 全天 | 5 bps | 10 bps | 15 bps | 冲击 ≈ 0.005% |
最优执行时长为 2–4 小时,平衡了市场冲击和时间风险。
def almgren_chriss_cost(X: float, T: float, sigma: float,
gamma: float = 0.1, theta: float = 1.0):
"""
Almgren-Chriss 最优执行成本估算
Parameters
----------
X: float, 交易量占比 (0~1)
T: float, 执行时长(归一化到 1)
sigma: float, 日波动率
gamma: float, 冲击参数
theta: float, 风险厌恶系数
Returns
-------
dict: 冲击成本、时间风险、总成本(bps)
"""
# 市场冲击成本(永久冲击 + 暂时冲击)
market_impact = 0.5 * gamma * sigma * (X ** 1.5) * np.sqrt(1/theta - 1/T)
# 时间风险(持有敞口的风险)
timing_risk = 0.5 * sigma * X * np.sqrt(T) / theta
total_cost_bps = (market_impact + timing_risk) * 10000 # 转为 bps
return {
'market_impact_bps': market_impact * 10000,
'timing_risk_bps': timing_risk * 10000,
'total_bps': total_cost_bps,
'net_capture': 1 - total_cost_bps / 10000
}
# 多场景对比
scenarios = [
{'X': 0.05, 'T': 0.25, 'desc': '小单快速'},
{'X': 0.10, 'T': 0.50, 'desc': '中单标准'},
{'X': 0.20, 'T': 1.00, 'desc': '大单全天'},
{'X': 0.50, 'T': 2.00, 'desc': '超大单两天'},
]
sigma = 0.02 # 2% 日波动率
for s in scenarios:
cost = almgren_chriss_cost(s['X'], s['T'], sigma)
print(f"{s['desc']}: 冲击={cost['market_impact_bps']:.1f}bps, "
f"时间风险={cost['timing_risk_bps']:.1f}bps, "
f"总成本={cost['total_bps']:.1f}bps")
# >>> 小单快速: 冲击=18.2bps, 时间风险=1.0bps, 总成本=19.2bps
# >>> 中单标准: 冲击=12.8bps, 时间风险=2.5bps, 总成本=15.3bps
# >>> 大单全天: 冲击=8.9bps, 时间风险=5.0bps, 总成本=13.9bps
# >>> 超大单两天: 冲击=4.0bps, 时间风险=7.1bps, 总成本=11.1bps
另类数据(Alternative Data)是近年量化投资的重要趋势。
| 数据类型 | 来源 | 预测价值 | 使用频率 | 年花费(万美元) |
|---|---|---|---|---|
| 信用卡交易 | 银行/支付数据 | 消费类公司营收预测 | 周/月 | 10–50 |
| 卫星图像 | 卫星运营商 | 零售客流、工厂开工率 | 周 | 20–100 |
| 网页爬虫 | 电商网站、招聘网站 | 产品销量、公司扩张 | 日 | 5–30 |
| 新闻情绪 | 新闻 API | 事件驱动策略 | 实时 | 3–15 |
| 社交媒体 | Twitter/Reddit | 市场情绪 | 实时 | 2–10 |
| 应用商店 | App Store/Google Play | 科技公司下载量 | 日 | 5–20 |
| 供应链数据 | 物流公司 | 上下游关系分析 | 季 | 10–40 |
数值案例:卫星图像预测零售额
某对冲基金通过卫星图像统计零售店停车场车辆数,预测季度同店销售额:
| 季度 | 卫星观测车辆数(万辆/周) | 预测同店增长 | 实际同店增长 | 误差 |
|---|---|---|---|---|
| 2023Q1 | 5.2 | +2.1% | +2.3% | 0.2% |
| 2023Q2 | 4.8 | -1.5% | -1.8% | 0.3% |
| 2023Q3 | 5.5 | +3.8% | +3.5% | 0.3% |
| 2023Q4 | 5.8 | +4.2% | +4.6% | 0.4% |
卫星数据预测同店增速的平均误差仅为 0.3%,比分析师一致预期的平均误差(1.5%)小 80%。该策略提前 2–4 周获取业绩信息。
市场状态(Regime)在不同环境下对因子和策略的表现影响巨大。使用隐马尔可夫模型(HMM)识别市场状态是常见的量化方法。
from hmmlearn import hmm
def detect_market_regime(returns: np.ndarray, n_states: int = 3):
"""
使用 HMM 识别市场状态
假设市场有三种状态:
- 状态0: 牛市(高收益、低波动)
- 状态1: 熊市(负收益、高波动)
- 状态2: 震荡市(低收益、低波动)
"""
model = hmm.GaussianHMM(
n_components=n_states,
covariance_type='diag',
n_iter=1000,
random_state=42
)
# 使用收益率和波动率作为观测特征
features = np.column_stack([
returns,
np.abs(returns) # 近似波动率
])
model.fit(features)
states = model.predict(features)
# 解读各状态
state_params = []
for i in range(n_states):
mean_ret = model.means_[i, 0] * 252 # 年化
mean_vol = model.means_[i, 1] * np.sqrt(252) # 年化
state_params.append({
'state': i,
'yearly_return': mean_ret,
'yearly_vol': mean_vol,
'frequency': (states == i).mean()
})
return states, state_params
# 示例:用沪深300 2015-2024 年数据识别市场状态
# states, params = detect_market_regime(hs300_returns)
# for p in params:
# print(f"状态{p['state']}: 年化收益={p['yearly_return']:.1%}, "
# f"年化波动={p['yearly_vol']:.1%}, 频率={p['frequency']:.1%}")
# >>> 状态0: 年化收益=+18.5%, 年化波动=22.3%, 频率=45.3% ← 牛市
# >>> 状态1: 年化收益=-22.1%, 年化波动=38.5%, 频率=18.7% ← 熊市
# >>> 状态2: 年化收益=+3.2%, 年化波动=14.8%, 频率=36.0% ← 震荡市
| 因子 | 牛市状态 | 熊市状态 | 震荡市状态 |
|---|---|---|---|
| 动量(12M) | +12.3% | -18.5% | +5.2% |
| 价值(EP) | +2.5% | +8.2% | +6.8% |
| 低波动 | +1.5% | +12.5% | +8.5% |
| 质量(ROE) | +6.8% | +5.5% | +7.2% |
| 反转(1M) | -3.5% | +15.2% | +4.5% |
熊市中价值因子和低波动因子表现最好;动量因子在熊市中
严重亏损(动量崩盘); 反转因子在牛市中负收益(趋势持续时正反馈无效)。
基于市场状态识别,可以构建动态切换的适应性策略:
输入:当前市场特征(波动率、动量、估值水平)
│
▼
┌──────────────────────────────────────────┐
│ HMM/随机森林状态分类器 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 牛市状态 │ │ 熊市状态 │ │ 震荡市状态│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐ │
│ │动量为主 │ │低波+价值 │ │因子等权 │ │
│ │+15%仓位 │ │+风控减仓 │ │+高换手 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────┘
│
▼
输出:当前最优因子权重配置
| 危险信号 | 说明 | 如何检查 |
|---|---|---|
| 极高夏普比率 | 回测夏普 > 3.0 几乎必定过拟合 | 查看数据挖掘调整后的夏普比率 |
| 参数敏感 | 参数微小变化导致绩效大幅波动 | 参数稳定性分析(参数漫步) |
| 低样本有效性 | 信号个数接近或超过样本数量 | 特征数量 < 样本数量 / 10 |
| 无交易成本 | 未考虑冲击成本的策略实盘不可行 | 加入 0.1–0.3% 双边成本 |
| 未来数据 | 不小心使用了未来信息 | 严格的时间对齐检查 |
| 策略数量过多 | 测试大量策略只选择了最好的 | 多重测试调整(Bonferroni / FDR) |
不同偏差对回测收益的影响:
| 偏差类型 | 无偏差年化收益 | 含偏差年化收益 | 虚高幅度 |
|---|---|---|---|
| 幸存者偏差 | +10% | +13% | +3% |
| 前瞻性偏差 | +10% | +14% | +4% |
| 忽略交易成本 | +10% | +16% | +6% |
| 忽略冲击成本 | +10% | +15% | +5% |
| 忽略涨跌停/停牌 | +10% | +12% | +2% |
| 全部偏差叠加 | +10% | +25% | +15% |
一个回测显示年化 25% 的策略,在纠正所有常见偏差后,真实预期收益可能仅为 10% 左右。
量化策略的 Alpha 会随时间衰减,需要持续监控:
┌───────────────────────────────────────────┐
│ 策略衰减监控仪表盘 │
├───────────────────────────────────────────┤
│ 监控指标 当前值 预警阈值 │
│ ─────────────────────────────────────────── │
│ IC 衰减(3个月滚动 vs 历史均值) -22% │
│ 夏普比率衰减(实盘 vs 回测) -35% │
│ 因子拥挤度 Z-Score +1.8 │
│ 收益率衰减斜率(6个月回归) -0.12 │
│ 策略容量消耗 45% │
├───────────────────────────────────────────┤
│ 综合评估:⚠️ 注意观察,准备储备替代策略 │
└───────────────────────────────────────────┘
衰减检测的量化方法:
def detect_alpha_decay(strategy_returns, rolling_window=60):
"""
检测策略 Alpha 是否在衰减
方法:对滚动窗口内的累计超额收益做线性回归
若斜率显著为负(t < -2),则判定 Alpha 衰减
"""
excess_returns = strategy_returns.cumsum()
x = np.arange(len(excess_returns))
# 滚动回归
t_stats = []
for i in range(rolling_window, len(excess_returns)):
y_slice = excess_returns.iloc[i-rolling_window:i]
x_slice = x[i-rolling_window:i]
slope = np.polyfit(x_slice, y_slice, 1)[0]
# 简化 t 统计量
residuals = y_slice - np.polyval([slope, 0], x_slice)
se = np.std(residuals) / np.sqrt(rolling_window)
t_stat = slope / (se + 1e-8)
t_stats.append(t_stat)
t_series = pd.Series(t_stats,
index=strategy_returns.index[rolling_window:])
# 最后 3 个月的 t 统计量均值
recent_t = t_series.iloc[-63:].mean() # 约 3 个月
if recent_t < -2:
return "⚠️ Alpha 显著衰减 - 建议停止策略并重新开发"
elif recent_t < -1:
return "👀 轻微衰减迹象 - 需密切关注"
else:
return "✅ Alpha 稳定"
阶段1:策略构思
├─ 因子探索(文献研究 + 数据发现)
└─ 经济学逻辑验证(该因子为什么有效?)
阶段2:原型验证
├─ 简单回测(单因子分层测试)
├─ 行业中性化处理
└─ 显著性检验(t 统计量、Fama-MacBeth)
阶段3:完整回测
├─ Walk-Forward 交叉验证
├─ 参数稳定性测试
├─ 交易成本敏感性分析
└─ 极端情形压力测试
阶段4:模拟交易
├─ 纸面交易(Paper Trading)3–6 个月
├─ 实盘渐进式投入
└─ 绩效归因与迭代优化
识别收益来源是量化投资持续改进的关键:
| 归因方法 | 说明 | 公式 | 典型工具 |
|---|---|---|---|
| 因子归因 | 将收益分解为各因子暴露的贡献 | BARRA 模型、Axioma | |
| 行业归因 | 区分选股能力与行业配置能力 | 分解 | Brinson 模型 |
| Brinson 分解 | 配置效应 + 选股效应 + 交互效应 | 三者相加等于超额收益 | Excel / Python |
| 风格归因 | 风格(价值/成长/动量)暴露归因 | 与因子归因类似 | Morningstar |
Brinson 分解数值案例:
假设组合 Q1 的收益率为 +8%,基准收益率为 +5%:
| 行业 | 组合权重 | 基准权重 | 组合收益 | 基准收益 | 配置效应 | 选股效应 |
|---|---|---|---|---|---|---|
| 科技 | 40% | 30% | +12% | +8% | (0.4–0.3)×(8%–5%)=+0.3% | 0.4×(12%–8%)=+1.6% |
| 金融 | 30% | 35% | +5% | +4% | (0.3–0.35)×(4%–5%)=+0.05% | 0.3×(5%–4%)=+0.3% |
| 消费 | 20% | 25% | +3% | +3% | (0.2–0.25)×(3%–5%)=+0.1% | 0.2×(3%–3%)=0% |
| 能源 | 10% | 10% | +6% | +5% | 0% | 0.1×(6%–5%)=+0.1% |
| 合计 | 100% | 100% | +8% | +5% | +0.45% | +2.0% |
超额收益 = 8% − 5% = 3.0%
其中:选股效应贡献 2.0%(67%),配置效应贡献 0.45%(15%),交互效应贡献 0.55%(18%)
def brinson_decomposition(portfolio_weights, benchmark_weights,
portfolio_returns, benchmark_returns):
"""
Brinson 绩效归因分解
返回:配置效应、选股效应、交互效应
"""
# 配置效应:超配/低配行业所获得的超额
allocation = np.sum(
(portfolio_weights - benchmark_weights) * benchmark_returns
)
# 选股效应:在行业内选股获得的超额
selection = np.sum(
(portfolio_returns - benchmark_returns) * portfolio_weights
)
# 交互效应:同时超配且选股优于基准
interaction = np.sum(
(portfolio_weights - benchmark_weights) *
(portfolio_returns - benchmark_returns)
)
print(f"超额收益: {portfolio_returns.sum() - benchmark_returns.sum():.2%}")
print(f" 配置效应: {allocation:.2%} (占比 {allocation/(portfolio_returns.sum()-benchmark_returns.sum()):.0%})")
print(f" 选股效应: {selection:.2%} (占比 {selection/(portfolio_returns.sum()-benchmark_returns.sum()):.0%})")
print(f" 交互效应: {interaction:.2%}")
return allocation, selection, interaction
| 特征 | 描述 | 对量化策略的影响 |
|---|---|---|
| 散户为主 | 散户交易量占比约 60–80% | 错误定价机会多,Alpha 更丰富 |
| 涨跌停限制 | 单日 ±10%(科创/创业板 ±20%) | 极端行情时无法交易 |
| T+1 结算 | 买入当日不能卖出 | 日频策略需过夜持仓 |
| 融券限制 | 融券标的有限、成本高 | 多空策略空头端难以实现 |
| 高换手率 | A 股年化换手率约 200–400% | 交易成本影响显著 |
| 政策敏感性 | 政策变动影响大 | 需关注事件风险 |
基于 A 股 2005–2023 年数据:
| 因子 | 月度 IC(信息系数) | 年化多空收益 | t 统计量 | 有效性 |
|---|---|---|---|---|
| 反转因子(1 个月) | -0.06 | +12% | -3.8 | ⭐⭐⭐⭐ |
| 动量因子(12 个月) | +0.03 | +6% | 1.8 | ⭐⭐ |
| 估值因子(EP) | +0.04 | +8% | 2.5 | ⭐⭐⭐ |
| 盈利因子(ROE) | +0.05 | +10% | 3.0 | ⭐⭐⭐⭐ |
| 成长因子 | +0.02 | +4% | 1.5 | ⭐⭐ |
| 低波动因子 | +0.03 | +7% | 2.2 | ⭐⭐⭐ |
| 换手率因子 | -0.04 | +9% | -2.8 | ⭐⭐⭐ |
| 分析师上调 | +0.05 | +11% | 3.2 | ⭐⭐⭐⭐ |
IC(Information Coefficient)为因子排名与下期收益排名的 Spearman 相关系数。IC > 0.03 且 t > 2.0 通常被认为是有效的 Alpha 因子。
1. 停复牌处理
A 股停牌率远高于美股,对策略影响显著:
| 策略类型 | 停牌影响 | 缓解方法 |
|---|---|---|
| 日频轮动 | 持仓停牌无法卖出,新信号无法买入 | 流动性缓冲(保留 5-10% 现金) |
| 周频调仓 | 影响较小,可在复牌后补调 | 调仓窗口延长至 3-5 天 |
| 事件驱动 | 停牌可能错过最佳时机 | 提前设置事前后续处理 |
2. 分红除权处理
A 股分红后股价除权,需做后复权处理:
def adjust_for_dividends(price_df: pd.DataFrame,
dividend_df: pd.DataFrame):
"""
A 股分红除权调整:后复权法
Example:
股票 600519 在 2024-06-15 每股分红 30.88 元
除权前收盘价: 1,689 元
除权后开盘价: 1,658.12 元 (= 1,689 - 30.88)
后复权因子: 1,689 / 1,658.12 = 1.0186
所有之前的价格 × 1.0186
"""
adjust_factors = pd.Series(1.0, index=price_df.index)
for date, div_row in dividend_df.iterrows():
if date not in price_df.index:
continue
for stock in div_row.index:
div = div_row[stock]
if pd.isna(div) or div == 0:
continue
close_price = price_df.loc[date, stock]
if pd.isna(close_price) or close_price == 0:
continue
factor = close_price / (close_price - div)
# 调整所有之前的价格
adjust_factors.loc[:date] *= factor
return price_df.multiply(adjust_factors, axis=0)
3. 回测中特殊交易机制处理
A 股独有机制对策略的影响:
| 机制 | 对回测影响 | 处理方式 |
|---|---|---|
| T+1 | 当日买入无法卖出 | 信号日延迟到次日开盘执行 |
| 涨跌停 | 无法按目标价成交 | 涨停时买入单、跌停时卖出单分别处理 |
| 集合竞价 | 开盘价可能大幅偏离昨收 | 使用开盘价代替昨收计算收益 |
| 新股涨停 | 中签新股连续涨停无法买入 | 前 10 个交易日排除或按开板价计算 |
| 问题 | 说明 | 应对 |
|---|---|---|
| 市场操纵 | 通过算法制造虚假交易信号 | 遵守监管规则,避免幌骗(Spoofing) |
| 不公平优势 | 利用未公开数据获取优势 | 数据来源合规审查 |
| 系统性风险 | 算法共振导致极端行情(如 2010 Flash Crash) | 熔断机制、风险限制 |
| 算法歧视 | 模型可能放大社会偏见 | 模型公平性审计 |
| 透明度不足 | 黑箱模型无法解释 | 开发可解释 AI 方法(SHAP、LIME) |
| 趋势 | 现状 | 展望 |
|---|---|---|
| AI 大模型 | GPT 等用于另类数据处理、情感分析 | 端到端 Alpha 生成 |
| 强化学习交易 | 初步应用于执行优化 | 替代传统执行算法 |
| 量子计算 | 组合优化中测试 | 投资组合优化时间从小时级降至秒级 |
| 另类数据规模化 | 大量新型数据源涌现 | 数据整合平台化、标准化 |
| ESG 量化 | 初步因子研究 | ESG 成为系统化因子 |
| 个人量化工具 | 开源工具(backtrader/zipline)普及 | 量化投资民主化 |
| 工具 | 语言 | 适合场景 | 特点 |
|---|---|---|---|
| backtrader | Python | 个人策略回测 | 轻量、灵活 |
| zipline | Python | 事件驱动回测 | Quantopian 原版 |
| alphalens | Python | 因子分析 | 与 zipline 配合 |
| pyfolio | Python | 投资组合分析 | 绩效归因可视化 |
| quantconnect/LEAN | C#/Python | 云端量化平台 | 完整端到端方案 |
| vnpy | Python | 国内 CTA/期货 | 支持国内接口 |
| qlib | Python | ML 选股 | Microsoft 出品,AI 驱动 |
| hmmlearn | Python | 隐马尔可夫模型 | 市场状态识别 |