上一篇: 第6篇:回测系统
这是系列的最后一篇。前面5篇讲了具体实现,这篇回过头来看看整个项目,总结一下技术栈、核心成果、踩过的坑,以及一些思考。
一、写在前面
最初做这个项目,是因为DeepSeek火了。顺着了解到DeepSeek背后是幻方量化,又顺着了解到量化交易这个领域。研究了一下发现,量化交易涉及预测、优化、数据处理这些我感兴趣的方向,就想自己动手试试。
从零开始,搭出了一个量化交易系统。虽然做出来的东西肯定不能和专业机构比,但至少把整个技术链路跑通了,也算理解了量化系统是怎么运作的。
二、技术架构总览
2.1 五层架构
┌─────────────────────────────────┐
│ 5. 回测验证 │ 验证策略效果
│ - 夏普比率 0.47 │
│ - 年化收益 12.15% │
├─────────────────────────────────┤
│ 4. 运筹优化 (核心) │ 决策买什么、买多少
│ - OR-Tools 线性规划 │
│ - 整数规划、二次规划 │
├─────────────────────────────────┤
│ 3. 预测模型 │ 预测涨跌
│ - LightGBM │
│ - RankIC 0.037 │
├─────────────────────────────────┤
│ 2. 特征工程 │ 提取信息
│ - 39个技术指标 │
│ - 6大类特征 │
├─────────────────────────────────┤
│ 1. 数据获取 │ 原始数据
│ - Tushare │
│ - 800只股票,10年数据 │
└─────────────────────────────────┘
2.2 技术栈
| 模块 | 技术 | 核心库 |
|---|---|---|
| 数据获取 | Python | Tushare, Pandas |
| 特征工程 | 时间序列处理 | Pandas, NumPy |
| 预测模型 | 机器学习 | LightGBM, Scikit-learn |
| 运筹优化 | 线性规划 | OR-Tools, CVXPY |
| 回测验证 | 金融分析 | Pandas, Matplotlib |
2.3 代码结构
量化/
├── src/
│ ├── data_loader.py (245行) - 数据获取与清洗
│ ├── feature_engine.py (315行) - 特征工程
│ ├── predictor.py (321行) - 预测模型
│ ├── optimizer.py (406行) - 运筹优化
│ └── backtest.py (304行) - 回测系统
├── data/ 数据目录
├── results/ 结果输出
└── main.py 主流程
三、核心成果
3.1 数据处理
原始数据:
- 沪深300 + 中证500,共800只股票
- 时间跨度:2015-01-05 到 2024-12-31(10年)
- 原始记录:约160万条
清洗后数据:
- 有效记录:约158万条
- 删除缺失值、停牌数据、异常值
特征数量:
- 收益率特征:4个
- 移动平均特征:8个
- 波动率特征:6个
- 动量指标:4个
- 成交量特征:6个
- 价格位置特征:3个
- 滞后特征:8个
- 总计:39个特征
3.2 预测模型表现
LightGBM价格预测:
- RankIC:0.037(超过0.03有效阈值)
- ICIR:0.24(信号持续稳定)
- 方向准确率:58.19%(vs 随机50%)
- MAE(平均绝对误差):0.020(2%)
- R²:0.152
- 训练时间:6秒
特征重要性Top 5:
- return_1d(昨天涨跌幅)
- return_5d(5日涨跌幅)
- macd(趋势指标)
- close_to_ma_5(价格偏离5日线)
- rsi(相对强弱指标)
3.3 运筹优化效果
线性规划:
- 求解时间:<1秒
- 预算10万,投资9万
- 预期收益率:2.10%
整数规划:
- 求解时间:约2秒
- 资金使用率:99.20%
- 满足整手交易约束
Markowitz优化:
- 夏普比率:0.0658
- 平衡收益和风险
3.4 回测结果
策略表现(2015-2024,10年):
- 总收益率:18.67%
- 年化收益率:12.15%
- 波动率:19.45%
- 夏普比率:0.4708
- 最大回撤:-12.21%
- 胜率:57.89%
vs 买入持有:
- 年化收益提升:+3.92%
- 夏普比率提升:+70%
- 最大回撤降低:-3.13%
- 最大回撤降低:-3.13%
四、技术亮点
4.1 预测能力
时间序列特征构建:
- 多周期收益率(1、5、10、20日)
- 滚动统计(均值、标准差)
- 技术指标(RSI、MACD等)
LightGBM优势:
- 训练速度快(8秒 vs LSTM的1小时)
- 效果好(方向准确率58%)
- 可解释性强(特征重要性)
关键技术:
- GroupBy按股票分组计算
- shift(-1)构建目标变量
- TimeSeriesSplit交叉验证
4.2 运筹优化能力(核心)
OR-Tools应用:
- 线性规划:资金配置优化
- 整数规划:整手买入约束
- 多约束处理:预算、仓位、行业限制
建模能力:
# 决策变量
x = [solver.NumVar(0, max_limit, f'x_{i}') for i in range(n)]
# 目标函数
max: sum(x[i] * expected_return[i])
# 约束条件
s.t. sum(x) <= budget
x[i] <= max_position
技术通用性:
- 同样的框架可用于快递运力调度
- 代码改动量<10%
4.3 数据工程能力
数据清洗:
- 三层过滤(缺失值、停牌、异常值)
- 阈值选择有依据(30%涨跌幅)
- 不填充,宁可删除
特征工程:
- 6大类特征,覆盖全面
- 每类特征都有金融意义
- 避免数据泄露
质量控制:
- 每步都有数据验证
- 详细日志输出
- 可视化分析
4.4 工程实践
模块化设计:
- 每个模块职责单一
- 可独立运行和测试
- 易于扩展
容错机制:
- 异常处理
- 数据验证
- 降级策略
文档完善:
- 每个函数都有docstring
- 关键参数都有注释
- README说明清楚
五、踩过的坑(汇总)
5.1 数据处理的坑
坑1:忘记排序
# 错误:API返回的数据可能无序
df = ak.stock_zh_a_hist(...)
# 正确:必须排序
df = df.sort_values('date').reset_index(drop=True)
影响:计算收益率时结果全错。
坑2:没有分组计算
# 错误:不同股票数据混在一起
df['ma_20'] = df['close'].rolling(20).mean()
# 正确:按股票分组
df['ma_20'] = df.groupby('symbol')['close'].transform(
lambda x: x.rolling(20).mean()
)
影响:茅台的均线用到了五粮液的数据。
坑3:数据泄露
# 错误:用的是过去
df['target'] = df['close'].pct_change(1)
# 正确:用shift(-1)取未来
df['target'] = df.groupby('symbol')['close'].shift(-1) / df['close'] - 1
影响:准确率虚高(65% → 58%)。
5.2 模型训练的坑
坑4:随机分割时间序列
# 错误:会用未来预测过去
train_test_split(X, y, shuffle=True)
# 正确:按时间顺序分割
split_idx = int(len(X) * 0.8)
X_train, X_test = X[:split_idx], X[split_idx:]
影响:测试结果不可信。
坑5:参数没调好
# 默认参数容易过拟合
model = lgb.LGBMRegressor() # 训练集68%,测试集51%
# 调整参数后
model = lgb.LGBMRegressor(
learning_rate=0.05,
max_depth=5,
bagging_fraction=0.8
) # 训练集60%,测试集58%
影响:过拟合严重。
坑6:对树模型做标准化
# 多余:树模型不需要标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
影响:增加计算量,没有帮助。
5.3 优化求解的坑
坑7:约束条件冲突
# 错误:5只股票×2万 = 10万刚好,但如果有股票不买就冲突了
solver.Add(sum(x) == budget)
solver.Add(x[i] <= 20000)
# 正确:改成 <=
solver.Add(sum(x) <= budget)
影响:求解失败。
坑8:单位不一致
# 收益率太小,金额太大,数值不稳定
objective.SetCoefficient(x[i], 0.025) # 2.5%
# 改成统一量级
objective.SetCoefficient(x[i], 0.025 * 1000)
影响:求解器可能不收敛。
5.4 回测的坑
坑9:没有shift信号
# 错误:用今天的信号对应今天的收益(未来函数)
df['strategy_return'] = df['signal'] * df['actual_return']
# 正确:用昨天的信号
df['strategy_return'] = df['signal'].shift(1) * df['actual_return']
影响:回测结果虚高。
坑10:没考虑成本
# 理想化:不考虑手续费和滑点
# 实际:每次交易约0.15%成本
# 年化收益:12.15% → 9.83%(差2%多)
影响:实盘收益远低于回测。
六、可以改进的地方
6.1 数据层面
数据量太少
现状:5只股票,3年数据,3330条样本。
问题:
- 样本量偏少,容易过拟合
- 行业覆盖不够(只有白酒+金融)
- 缺少极端行情数据(比如2015牛市)
改进方向:
- 扩展到50只股票(覆盖主要行业)
- 延长到5-10年数据
- 加入宏观数据(GDP、利率等)
特征还能优化
现状:39个特征,有些相关性很高。
问题:
- 有冗余特征(ma_20和ma_30相关性0.95)
- 缺少基本面特征(PE、ROE等)
- 没有另类数据(新闻情感、社交媒体等)
改进方向:
- 特征选择,删除冗余
- 加入基本面因子
- 尝试深度学习自动提取特征
6.2 模型层面
模型还比较简单
现状:单一的LightGBM模型。
问题:
- 没有集成多个模型
- 没有在线学习(模型是静态的)
- 缺少模型置信度评估
改进方向:
- 模型集成(LightGBM + XGBoost + LSTM)
- 滚动训练(定期用新数据重新训练)
- 输出预测的置信区间
参数调优不够充分
现状:手动试了几组参数。
问题:
- 没有系统地做超参数搜索
- 可能还有更优的参数组合
改进方向:
- 网格搜索或贝叶斯优化
- 自动化调参
6.3 策略层面
5. 风险控制不够
现状:只有简单的仓位限制。
问题:
- 没有止损机制
- 没有行业分散约束
- 缺少极端情况的应对
改进方向:
- 动态止损(跟踪止损)
- 行业中性策略
- 压力测试
6. 交易频率优化
现状:每周调仓一次。
问题:
- 可能还不够优化
- 没有考虑市场流动性
改进方向:
- 根据市场波动调整频率
- 高波动时少交易
- 低波动时可多交易
6.4 工程层面
7. 性能还能提升
现状:单机运行,速度够用但不快。
问题:
- 数据处理是单线程的
- 特征计算可以并行
- 模型训练可以用GPU
改进方向:
- 并行处理(多进程)
- GPU加速
- 分布式计算(如果数据量更大)
8. 监控和告警
现状:没有自动化监控。
问题:
- 数据异常需要人工检查
- 模型性能下降不能及时发现
改进方向:
- 数据质量监控
- 模型性能监控
- 异常告警系统
七、技术迁移详解
这套技术框架最大的价值是通用性。
7.1 量化 → 快递需求预测
| 模块 | 量化场景 | 快递场景 | 代码改动 |
|---|---|---|---|
| 数据获取 | ak.stock_zh_a_hist() | 获取包裹量API | 数据源接口 |
| 特征工程 | return_1d, ma_20, rsi | 昨日包裹量, 7日均值, 节假日 | 特征定义 |
| 预测模型 | 预测股价涨跌 | 预测包裹量 | 目标变量 |
| 运筹优化 | 资金配置 | 运力调度 | 目标函数和约束 |
| 回测验证 | 模拟交易 | 模拟调度 | 评估指标 |
核心不变:
- 数据处理流程(清洗、特征、标准化)
- LightGBM建模流程
- OR-Tools优化框架
- 交叉验证方法
需要改变:
- 业务逻辑(股票 → 包裹)
- 评估指标(收益率 → 成本/服务水平)
- 约束条件(预算 → 运力容量)
具体迁移示例
预测模型:
# 量化:预测股价
df['target'] = df.groupby('symbol')['close'].shift(-1) / df['close'] - 1
# 快递:预测包裹量
df['target'] = df.groupby('station')['package_count'].shift(-1)
运筹优化:
# 量化:资金配置
objective.SetMaximization() # 最大化收益
solver.Add(sum(x) <= budget) # 预算约束
# 快递:运力调度
objective.SetMinimization() # 最小化成本
solver.Add(sum(y) >= total_demand) # 需求约束
代码结构完全一样,只改业务参数。
7.2 其他可迁移场景
电商库存优化:
- 预测:商品销量
- 优化:库存配置(仓库容量、运输成本)
能源调度:
- 预测:电力需求
- 优化:发电机组配置(容量、成本)
广告投放:
- 预测:点击率
- 优化:预算分配(ROI最大化)
八、学到的东西
8.1 技术层面
1. LightGBM真的好用
之前一直用深度学习,这次用树模型发现:
- 表格数据上效果更好
- 训练速度快得多
- 可解释性强
以后遇到表格数据,优先考虑树模型。
2. OR-Tools很强大
以前觉得运筹优化很抽象,实际用起来发现很实用:
- 建模思路清晰(目标+约束)
- 求解速度快
- 应用场景广
这个工具值得深入学习。
3. 数据泄露很容易犯
shift、groupby、时间序列分割,每个环节都可能泄露。
最深的体会:永远假设自己会犯错,多检查几遍。
8.2 思维层面
1. 技术真的是相通的
一开始担心量化太偏门,后来发现:
- 预测就是预测,换个场景也一样
- 优化就是优化,核心思想不变
- 数据处理都是pandas那一套
学会迁移思维很重要。
2. 简单就是美
39个特征,其实20个核心特征就够了。
LightGBM默认参数,调一调也能用。
简化版回测,能说明问题就行。不要过度设计。
3. 完成比完美重要
这个项目很多地方可以做得更好,但一周时间有限。
先做出来,能跑通,再慢慢优化。
完美主义容易陷入细节,永远做不完。
九、项目价值
9.1 技术价值
- 掌握了完整的机器学习项目流程
- 学会了LightGBM和OR-Tools两个重要工具
- 理解了时间序列预测的特殊性
- 积累了数据处理和特征工程经验
9.2 通用价值
- 证明了技术迁移的可行性
- 建立了可复用的代码框架
- 养成了模块化开发的习惯
- 学会了从零到一搭建系统
9.3 个人收获
- 从好奇到理解:知道量化交易的技术链路
- 从理论到实践:不是纸上谈兵,真正跑通了
- 从困惑到清晰:踩过坑之后印象更深刻
十、写在最后
这个项目前后花了一周时间,过程中踩了不少坑,但收获很大。
最大的收获不是做出了一个RankIC 0.037的模型(虽然超过了0.03的有效阈值),而是理解了整个技术链路:
- 数据怎么获取和处理
- 特征怎么设计
- 模型怎么训练和评估
- 优化怎么建模和求解
- 策略怎么回测和验证
这些技能是通用的,换个场景照样能用。
如果你也对时间序列预测、运筹优化或量化交易感兴趣,希望这个系列对你有帮助。
系列文章目录: