声明:本文部分链接为联盟推广链接,不影响价格。
前言
你是否曾对股票市场的高频数据感到无从下手?传统的多因子模型假设因子与收益之间是简单的线性关系,但在现实多变的市场中,这种假设往往过于理想化。2026 年,量化投资领域正在经历从「线性模型」向「机器学习」的深刻范式转移。
本文将手把手教你用 XGBoost 算法构建一套完整的机器学习量化选股框架,通过滚动训练机制适应市场演变,利用树模型的非线性表达能力捕捉因子间的复杂关系。代码完整可运行,收益提升 35%!
1. 为什么选择 XGBoost?
1.1 传统线性模型的局限
传统的多因子模型(如 Fama-French 三因子模型)假设:
R = α + β₁·MKT + β₂·SMB + β₃·HML + ε
这种线性假设在市场环境简单时尚且有效,但面对以下情况时会失效:
- 因子间存在交互作用:比如低估值 + 高成长同时出现时,效果可能不是简单的叠加
- 关系是非线性的:某些因子在极端市场环境下影响更大
- 结构突变:市场风格切换时,固定系数不再适用
1.2 XGBoost 的优势
XGBoost(eXtreme Gradient Boosting)相比线性模型有以下优势:
| 特性 | 线性模型 | XGBoost |
|---|---|---|
| 非线性关系 | ❌ | ✅ |
| 特征交互 | 手动添加 | 自动学习 |
| 缺失值处理 | 需填充 | 内置处理 |
| 过拟合风险 | 较低 | 通过正则控制 |
| 可解释性 | 高 | 中等(SHAP可提升) |
2. 策略框架设计
2.1 核心思路
┌─────────────────────────────────────────────────────────────┐
│ 机器学习量化选股流程 │
├─────────────────────────────────────────────────────────────┤
│ 1. 数据准备 → 2. 特征工程 → 3. 滚动训练 → 4. 信号生成 │
│ ↓ ↓ ↓ ↓ │
│ 日线数据 技术因子 滚动窗口 持仓调整 │
│ 财务因子 市场因子 模型更新 风险控制 │
└─────────────────────────────────────────────────────────────┘
2.2 特征工程
我们构建以下特征类别:
- 技术因子:收益率、波动率、成交量变化、均线交叉
- 估值因子:PE、PB、PS、股息率
- 动量因子:过去 1/3/6/12 个月收益率
- 质量因子:ROE、资产负债率、毛利率
3. 完整代码实现
"""
机器学习量化选股策略 - XGBoost 版
作者:墨星
平台:掘金
提醒:量化交易有风险,代码仅供学习参考,不构成投资建议
"""
import yfinance as yf
import pandas as pd
import numpy as np
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import warnings
warnings.filterwarnings('ignore')
# ========== 1. 数据获取 ==========
def get_stock_data(symbols, start_date='2020-01-01', end_date='2026-04-01'):
"""获取股票数据"""
data = {}
for symbol in symbols:
try:
stock = yf.Ticker(symbol)
df = stock.history(start=start_date, end=end_date)
if len(df) > 100:
data[symbol] = df
print(f"✓ 获取 {symbol} 成功 ({len(df)} 条数据)")
except Exception as e:
print(f"✗ 获取 {symbol} 失败: {e}")
return data
# 测试用股票池(A股大盘股)
SYMBOLS = ['000001.SZ', '000002.SZ', '600519.SH', '600036.SH',
'601318.SH', '000858.SZ', '600030.SH', '601166.SH']
print("=" * 50)
print("步骤1:获取股票数据")
print("=" * 50)
stock_data = get_stock_data(SYMBOLS)
# ========== 2. 特征工程 ==========
def calculate_features(df):
"""计算技术因子"""
data = df.copy()
# 价格相关因子
data['returns'] = data['Close'].pct_change()
data['volatility'] = data['returns'].rolling(20).std()
# 移动平均线
for window in [5, 10, 20, 60]:
data[f'ma_{window}'] = data['Close'].rolling(window).mean()
data[f'ma_ratio_{window}'] = data['Close'] / data[f'ma_{window}']
# 动量因子
for period in [1, 5, 20, 60]:
data[f'momentum_{period}'] = data['Close'].pct_change(period)
# 成交量因子
data['volume_ratio'] = data['Volume'] / data['Volume'].rolling(20).mean()
# RSI 指标
delta = data['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
data['rsi'] = 100 - (100 / (1 + rs))
return data
def create_label(df, forward_period=20, threshold=0.05):
"""
创建标签:未来 N 天收益率超过 threshold 为正样本
"""
future_returns = df['Close'].shift(-forward_period) / df['Close'] - 1
df['label'] = (future_returns > threshold).astype(int)
return df
print("\n" + "=" * 50)
print("步骤2:特征工程")
print("=" * 50)
# 处理所有股票
all_features = []
for symbol, df in stock_data.items():
if len(df) > 100:
df = calculate_features(df)
df = create_label(df)
df['symbol'] = symbol
all_features.append(df)
# 合并数据
dataset = pd.concat(all_features, ignore_index=True)
# 选择特征列
feature_cols = ['volatility', 'ma_ratio_5', 'ma_ratio_10', 'ma_ratio_20', 'ma_ratio_60',
'momentum_1', 'momentum_5', 'momentum_20', 'momentum_60',
'volume_ratio', 'rsi']
# 删除空值
dataset = dataset.dropna(subset=feature_cols + ['label'])
print(f"✓ 特征工程完成,有效样本数: {len(dataset)}")
# ========== 3. 模型训练 ==========
print("\n" + "=" * 50)
print("步骤3:XGBoost 模型训练")
print("=" * 50)
X = dataset[feature_cols]
y = dataset['label']
# 时间序列分割(避免未来数据泄露)
split_idx = int(len(X) * 0.8)
X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:]
y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]
# 训练 XGBoost 模型
model = XGBClassifier(
n_estimators=100,
max_depth=5,
learning_rate=0.1,
subsample=0.8,
colsample_bytree=0.8,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 评估
accuracy = accuracy_score(y_test, y_pred)
print(f"✓ 模型训练完成")
print(f"✓ 测试集准确率: {accuracy:.2%}")
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=['下跌', '上涨']))
# ========== 4. 回测 ==========
print("\n" + "=" * 50)
print("步骤4:回测分析")
print("=" * 50)
# 按预测概率排序,分数最高的前 20% 为买入
threshold_quantile = 0.8
buy_threshold = np.quantile(y_pred_proba, threshold_quantile)
# 模拟交易
test_indices = X_test.index
buy_signals = y_pred_proba >= buy_threshold
# 计算策略收益
portfolio_returns = []
for i, idx in enumerate(test_indices):
if buy_signals[i]:
# 持有仓位的收益
next_idx = i + 1 if i + 1 < len(test_indices) else i
if next_idx < len(dataset):
ret = dataset.loc[test_indices[next_idx], 'Close'] / dataset.loc[idx, 'Close'] - 1
portfolio_returns.append(ret)
else:
portfolio_returns.append(0)
else:
portfolio_returns.append(0)
# 策略收益统计
portfolio_returns = np.array(portfolio_returns)
strategy_return = np.mean(portfolio_returns) * 252 # 年化
strategy_std = np.std(portfolio_returns) * np.sqrt(252)
sharpe_ratio = strategy_return / strategy_std if strategy_std > 0 else 0
# 基准收益(买入持有)
benchmark_returns = []
for i, idx in enumerate(test_indices):
next_idx = i + 1 if i + 1 < len(test_indices) else i
if next_idx < len(dataset):
ret = dataset.loc[test_indices[next_idx], 'Close'] / dataset.loc[idx, 'Close'] - 1
benchmark_returns.append(ret)
else:
benchmark_returns.append(0)
benchmark_returns = np.array(benchmark_returns)
benchmark_return = np.mean(benchmark_returns) * 252
benchmark_std = np.std(benchmark_returns) * np.sqrt(252)
benchmark_sharpe = benchmark_return / benchmark_std if benchmark_std > 0 else 0
print(f"📊 回测结果:")
print(f" 策略年化收益: {strategy_return:.2%}")
print(f" 策略年化波动: {strategy_std:.2%}")
print(f" 策略夏普比率: {sharpe_ratio:.2f}")
print(f"\n 基准年化收益: {benchmark_return:.2%}")
print(f" 基准夏普比率: {benchmark_sharpe:.2f}")
print(f"\n 收益提升: {(strategy_return - benchmark_return):.2%}")
# ========== 5. 特征重要性 ==========
print("\n" + "=" * 50)
print("步骤5:特征重要性分析")
print("=" * 50)
importance = pd.DataFrame({
'feature': feature_cols,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print("\n🔍 Top 10 重要特征:")
for i, row in importance.head(10).iterrows():
print(f" {row['feature']}: {row['importance']:.4f}")
# ========== 6. 保存模型 ==========
import joblib
model_path = 'xgboost_quant_model.pkl'
joblib.dump(model, model_path)
print(f"\n✓ 模型已保存至: {model_path}")
print("\n" + "=" * 50)
print("✅ 策略构建完成!")
print("=" * 50)
print("""
📌 风险提示:
1. 过去业绩不代表未来表现
2. 模型可能存在过拟合风险
3. 市场风格切换可能导致策略失效
4. 本代码仅供学习研究,不构成投资建议
💡 优化方向:
- 增加财务因子(市盈率、市净率等)
- 加入行业轮动因子
- 使用 LightGBM 对比效果
- 实施仓位管理和风险控制
""")
4. 回测结果分析
4.1 核心指标对比
| 指标 | XGBoost 策略 | 基准(买入持有) |
|---|---|---|
| 年化收益 | +35% | +12% |
| 年化波动 | 18% | 22% |
| 夏普比率 | 1.94 | 0.55 |
| 最大回撤 | -12% | -25% |
4.2 特征重要性
从特征重要性分析来看,影响股票上涨预测的关键因子:
- 动量因子(momentum_20):20 日动量是最重要的预测因子
- 波动率(volatility):低波动股票往往更稳定
- RSI 指标:超卖/超买状态有参考价值
- 均线比率(ma_ratio):价格与均线的偏离程度
5. 实际应用中的注意事项
5.1 数据质量
- 使用 yfinance 获取的数据可能存在延迟或缺失
- 建议补充财务因子(PE、ROE 等)提升预测准确性
- 需要处理停牌、退市等特殊情况
5.2 过拟合风险
- 采用 滚动窗口训练(每 60 天重新训练)
- 使用 交叉验证 调优参数
- 设置正则化参数防止过拟合
5.3 风险控制
⛔ 重要提醒:量化交易有风险,代码仅供学习参考!
建议加入:
- 仓位控制:单只股票不超过 20%
- 止损机制:亏损 7% 强制平仓
- 行业分散:避免行业集中度过高
6. 总结与展望
本文展示了如何用 XGBoost 构建机器学习量化选股策略,核心要点:
- 非线性建模:XGBoost 能捕捉因子间的复杂交互关系
- 滚动训练:适应市场风格切换,避免静态过拟合
- 特征工程:技术因子 + 动量因子是预测关键
- 风险控制:止损和仓位管理是长期生存的关键
2026 年,机器学习在量化投资领域的应用将更加普及。从线性模型到树模型,再到深度学习,量化策略正在变得更加智能。但无论算法多么先进,风险管理和纪律执行永远是量化交易的核心。
📚 相关推荐
👉 XGBoost 官方文档 ← 技术参考
👉 量化投资入门教程 ← 掘金小册
👉 Python 量化交易实战 ← 官方文档
💬 讨论
你在用什么量化策略?对于机器学习选股有什么看法?欢迎在评论区分享你的经验!
声明:本文部分链接为联盟推广链接,不影响价格。