Add files via upload

This commit is contained in:
Shuo Cao
2025-10-27 19:45:33 +08:00
committed by GitHub
commit 702586dcfd
12 changed files with 2100 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
# 更新日志 (Changelog)
## [2.0.0] - 2025-10-27
### ✨ 新增功能
#### 训练改进
- **训练集/验证集划分**: 自动划分80%训练集和20%验证集
- **双Loss监控**: 每个epoch同时显示训练Loss和验证Loss
- **早停机制(Early Stopping)**: 验证Loss连续20轮不降则自动停止训练
- **最佳模型保存**: 自动保存验证Loss最低的模型,而非最后一轮
- **智能训练**: 最多训练500轮,但通常会在150-200轮触发早停
#### 可视化功能
- **训练曲线图**: 自动生成 `training_loss.png`
- 双曲线对比(训练Loss vs 验证Loss
- 标记最佳模型的epoch位置
- 提供完整曲线和后期收敛细节两个子图
- 支持中文字体显示
#### 诊断功能
- **训练总结报告**: 训练结束后自动输出
- 总训练轮数
- 最佳模型epoch
- 最佳验证Loss
- 最终训练Loss和验证Loss
- 训练/验证Loss差异分析
- **智能诊断**: 自动判断模型状态
- ✓ 良好:没有明显过拟合
- ⚠ 尚可:有轻微过拟合倾向
- ✗ 警告:可能存在过拟合
### 📝 文档更新
#### README.md
- 更新训练流程说明,详细说明新增功能
- 新增"如何判断模型训练效果好坏"FAQ
- 新增"验证Loss远高于训练Loss"问题解答
- 更新训练参数表格,包含早停和验证集信息
- 更新性能基准数据
- 标注版本号为2.0.0并列出更新内容
#### 提示词文件
- **prompt_simple.txt**: 更新训练脚本要求,包含验证集和早停
- **prompt_professional.txt**: 详细补充所有新功能的实现要求
- 验证集监控
- 早停机制
- 最佳模型保存
- 训练可视化
- 智能诊断
- 更新成功标准和重要提示
### 🔧 代码优化
#### train_lstm.py
- 代码从150行扩展到~330行(增加了大量功能但保持可读性)
- 所有新增代码均有详细中文注释
- 改进的训练循环:
```python
for epoch in range(500): # 最多500轮
# 训练阶段
model.train()
# ... 训练代码 ...
# 验证阶段
model.eval()
with torch.no_grad():
# ... 验证代码 ...
# 早停检查
if val_loss < best_val_loss:
best_model_state = model.state_dict()
else:
patience_counter += 1
if patience_counter >= patience:
break # 触发早停
```
### 📊 训练效果对比
#### v1.0.0(旧版本)
- 固定训练100轮
- 只有训练Loss
- 无法判断过拟合
- 可能保存次优模型
- 训练时间:~15秒
#### v2.0.0(新版本)
- 智能训练150-200轮(早停)
- 训练Loss + 验证Loss
- 自动检测过拟合
- 保证保存最佳模型
- 训练时间:~30秒
- 新增训练曲线图
- 新增诊断报告
### 🎯 教学价值提升
1. **理解过拟合**: 通过双Loss对比直观理解
2. **学习早停**: 了解工业界常用的训练技巧
3. **模型评估**: 学会判断模型质量的方法
4. **可视化思维**: 培养通过图表分析问题的能力
5. **最佳实践**: 掌握训练集/验证集划分的重要性
### 📦 新增文件
- `training_loss.png` - 训练曲线图(自动生成)
- `CHANGELOG.md` - 本更新日志
### ⚙️ 依赖变化
需要安装matplotlib用于绘图(可选):
```bash
pip install matplotlib
```
如果未安装matplotlib,程序会跳过绘图但不影响训练。
### 🐛 问题修复
- 修复了可能保存次优模型的问题(现在保存验证Loss最低的模型)
- 优化了文件路径处理,确保所有输出文件保存到脚本目录
### 🔮 未来计划
- [ ] 添加TensorBoard支持
- [ ] 支持学习率动态调整
- [ ] 添加多模型对比功能
- [ ] 支持分布式训练
- [ ] 增加更多技术指标特征
---
## [1.0.0] - 2025-10-27(初始版本)
### 基础功能
- LSTM模型训练
- CUDA GPU加速
- MT5数据获取
- ONNX模型导出
- MQL5 EA交易
- 基础文档
---
**版本说明**:
- 主版本号:重大功能变更或架构调整
- 次版本号:新增功能或重要改进
- 修订号:Bug修复或小优化
**当前版本**: 2.0.0
**维护状态**: 活跃开发中
BIN
View File
Binary file not shown.
+119
View File
@@ -0,0 +1,119 @@
//+------------------------------------------------------------------+
//| LSTM_EA.mq5 |
//| LSTM量化交易教学示例 |
//+------------------------------------------------------------------+
#property copyright "LSTM Trading Example"
#property version "1.00"
#resource "\\Files\\lstm_model.onnx" as uchar lstm_model[]
#include <Trade\Trade.mqh>
input double LotSize = 0.01; // 手数
input int StopLoss = 100; // 止损点数
input int TakeProfit = 150; // 止盈点数
long model_handle;
CTrade trade;
// 归一化参数(从训练脚本自动生成)
double data_min[5] = {0.95383, 0.95586, 0.95351, 0.95383, 9.0};
double data_max[5] = {1.23394, 1.23485, 1.23327, 1.23396, 24004.0};
//+------------------------------------------------------------------+
int OnInit()
{
model_handle = OnnxCreateFromBuffer(lstm_model, ArraySize(lstm_model));
if(model_handle == INVALID_HANDLE)
{
Print("ONNX模型加载失败");
return INIT_FAILED;
}
// 设置输入形状: [batch_size=1, seq_len=10, features=5]
ulong input_shape[] = {1, 10, 5};
if(!OnnxSetInputShape(model_handle, 0, input_shape))
{
Print("设置ONNX输入形状失败");
return INIT_FAILED;
}
// 设置输出形状: [batch_size=1, output_size=1]
ulong output_shape[] = {1, 1};
if(!OnnxSetOutputShape(model_handle, 0, output_shape))
{
Print("设置ONNX输出形状失败");
return INIT_FAILED;
}
Print("ONNX模型加载成功,准备交易EURUSD H1");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
OnnxRelease(model_handle);
}
//+------------------------------------------------------------------+
void OnTick()
{
// 仅在新K线时执行
static datetime last_bar = 0;
datetime current_bar = iTime(_Symbol, PERIOD_H1, 0);
if(current_bar == last_bar) return;
last_bar = current_bar;
// 准备输入数据: [1, 10, 5]
// 使用double避免在传参或计算时发生不必要的类型转换警告
double inputs[50];
for(int i = 0; i < 10; i++)
{
double bar_open = iOpen(_Symbol, PERIOD_H1, i);
double bar_high = iHigh(_Symbol, PERIOD_H1, i);
double bar_low = iLow(_Symbol, PERIOD_H1, i);
double bar_close = iClose(_Symbol, PERIOD_H1, i);
// 使用double存储成交量,避免精度或类型转换警告
double bar_volume = (double)iVolume(_Symbol, PERIOD_H1, i);
// 归一化(结果仍为double)
inputs[i*5 + 0] = (bar_open - data_min[0]) / (data_max[0] - data_min[0]);
inputs[i*5 + 1] = (bar_high - data_min[1]) / (data_max[1] - data_min[1]);
inputs[i*5 + 2] = (bar_low - data_min[2]) / (data_max[2] - data_min[2]);
inputs[i*5 + 3] = (bar_close - data_min[3]) / (data_max[3] - data_min[3]);
inputs[i*5 + 4] = (bar_volume - data_min[4]) / (data_max[4] - data_min[4]);
}
// 运行推理
// 输出也使用double,确保与输入数组类型一致
double output[1];
if(!OnnxRun(model_handle, ONNX_DEFAULT, inputs, output))
{
Print("ONNX推理失败");
return;
}
double current_price = iClose(_Symbol, PERIOD_H1, 0);
double predicted_price = output[0] * (data_max[3] - data_min[3]) + data_min[3];
// 交易逻辑: 预测价格上涨买入,下跌卖出
if(PositionsTotal() == 0)
{
if(predicted_price > current_price * 1.0001)
{
double sl = current_price - StopLoss * _Point;
double tp = current_price + TakeProfit * _Point;
trade.Buy(LotSize, _Symbol, 0, sl, tp);
Print("买入 | 当前:", current_price, " 预测:", predicted_price);
}
else if(predicted_price < current_price * 0.9999)
{
double sl = current_price + StopLoss * _Point;
double tp = current_price - TakeProfit * _Point;
trade.Sell(LotSize, _Symbol, 0, sl, tp);
Print("卖出 | 当前:", current_price, " 预测:", predicted_price);
}
}
}
//+------------------------------------------------------------------+
+477
View File
@@ -0,0 +1,477 @@
# LSTM量化交易教学项目 - EURUSD H1策略
## 项目简介
基于深度学习LSTM网络的外汇量化交易完整解决方案,使用真实EURUSD历史数据训练模型,通过ONNX格式在MT5平台实现实盘交易。本项目涵盖从数据获取、模型训练、导出到回测的完整工作流,是学习AI量化交易的理想教学案例。
## 技术栈
- **深度学习框架**: PyTorch 2.5+ (支持CUDA GPU加速)
- **模型架构**: LSTM (Long Short-Term Memory)
- **模型格式**: ONNX (跨平台部署)
- **交易平台**: MetaTrader 5
- **编程语言**: Python 3.11 + MQL5
- **数据源**: MT5实时行情数据
## LSTM在量化交易中的优势
**LSTM (长短期记忆网络)** 是循环神经网络(RNN)的变体,特别适合时间序列预测:
1. **长期依赖捕捉**: 通过门控机制(遗忘门、输入门、输出门)记住长期价格趋势
2. **自动特征提取**: 无需手动构造技术指标,直接从原始OHLCV数据学习
3. **非线性建模**: 可拟合复杂的市场动态,优于传统线性模型
4. **梯度稳定性**: 解决传统RNN的梯度消失问题,支持深层网络训练
## 项目文件结构
```
LSTM/
├── requirements.txt # Python依赖包清单
├── train_lstm.py # 训练脚本(支持GPU加速)
├── lstm_model.onnx # 训练生成的ONNX模型(~11KB)
├── scaler_params.npy # MinMax归一化参数
├── training_loss.png # 训练曲线图(自动生成)
├── LSTM_EA.mq5 # MQL5交易EA代码
├── prompt_simple.txt # 简单版提示词模板
├── prompt_professional.txt # 专业版提示词模板
└── README.md # 项目文档
```
## 快速开始
### 环境准备
**硬件要求**:
- CPU: 任意现代处理器
- GPU: NVIDIA显卡(可选,推荐RTX系列)
- 内存: 8GB+
- 硬盘: 1GB可用空间
**软件要求**:
- Python 3.11+
- MetaTrader 5 (已登录账户)
- CUDA 12.1+ (GPU训练需要)
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
**依赖包说明**:
- `torch`: PyTorch深度学习框架
- `onnx`: ONNX模型格式支持
- `MetaTrader5`: MT5 Python API
- `pandas`: 数据处理
- `scikit-learn`: 数据归一化
- `numpy`: 数值计算
**安装GPU版PyTorch** (如有NVIDIA显卡):
```bash
pip uninstall torch torchvision torchaudio -y
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
```
### 2. 训练模型
```bash
python train_lstm.py
```
**训练流程**:
1. 自动检测CUDA并使用GPU加速(如可用)
2. 连接MT5终端获取EURUSD历史数据
3. 使用MinMaxScaler归一化OHLCV数据
4. **数据集划分**: 80%训练集 + 20%验证集
5. **智能训练**: 最多500个epoch,支持早停机制
6. **验证监控**: 实时追踪训练Loss和验证Loss
7. **最佳模型保存**: 自动保存验证Loss最低的模型
8. **可视化**: 生成训练曲线图(training_loss.png)
9. 导出ONNX格式模型
10. 保存归一化参数供推理使用
**训练输出示例**:
```
使用设备: cuda
GPU型号: NVIDIA GeForce RTX 4090
获取到 31,053 条EURUSD H1数据
训练集形状: torch.Size([24833, 10, 5])
验证集形状: torch.Size([6209, 10, 5])
开始训练...
Epoch 10 | 训练Loss: 0.307397 | 验证Loss: 0.301387
Epoch 20 | 训练Loss: 0.198500 | 验证Loss: 0.189706
...
Epoch 164 | 训练Loss: 0.000424 | 验证Loss: 0.000152 ← 最佳模型
早停触发! 验证损失已经20个epoch没有改善
已恢复最佳模型 (Epoch 164)
训练总结:
总训练轮数: 184 epochs
最佳模型: Epoch 164
最佳验证Loss: 0.000152
✓ 模型状态: 良好,没有明显的过拟合
训练曲线已保存: training_loss.png
ONNX模型已保存: lstm_model.onnx (10.27 KB)
```
### 3. 部署到MT5
#### 步骤A: 复制ONNX模型
```
将 lstm_model.onnx 复制到:
MT5安装目录/MQL5/Files/lstm_model.onnx
```
#### 步骤B: 验证归一化参数
训练脚本会输出归一化参数,确保与 `LSTM_EA.mq5` 第20-21行一致:
```cpp
double data_min[5] = {0.95383, 0.95586, 0.95351, 0.95383, 9.00000};
double data_max[5] = {1.23394, 1.23485, 1.23327, 1.23396, 24004.00000};
```
#### 步骤C: 编译EA
1.`LSTM_EA.mq5` 复制到 `MT5/MQL5/Experts/`
2. 在MetaEditor中打开并编译(F7)
#### 步骤D: 策略回测
1. 打开策略测试器(Ctrl+R)
2. 配置参数:
- EA: LSTM_EA
- 品种: EURUSD
- 周期: H1
- 模式: 每笔成交
- 日期: 选择样本外数据(如最近3个月)
- 初始资金: 10,000 USD
3. 点击"开始"运行回测
### 4. EA参数说明
| 参数 | 默认值 | 说明 |
|------|--------|------|
| LotSize | 0.01 | 每笔交易手数 |
| StopLoss | 100 | 止损点数 |
| TakeProfit | 150 | 止盈点数 |
## 模型架构详解
### 网络结构
```
输入层: [batch_size, 10, 5]
├─ 10: 时间步长(10根历史K线)
└─ 5: 特征维度(OHLCV)
LSTM层: input_size=5, hidden_size=20, num_layers=1
├─ 遗忘门: 决定丢弃哪些历史信息
├─ 输入门: 决定存储哪些新信息
└─ 输出门: 决定输出哪些信息
全连接层: Linear(20 → 1)
输出层: [batch_size, 1]
└─ 预测下一根K线收盘价(归一化值)
```
**模型参数**:
- 总参数量: ~2,040个
- ONNX文件大小: 10.27 KB
- 推理速度: <1ms/次 (GPU)
### 数据预处理
**归一化公式**:
```python
normalized = (value - min) / (max - min) # 映射到[0,1]
```
**反归一化公式**:
```python
real_value = normalized * (max - min) + min
```
### 训练参数
| 参数 | 值 | 说明 |
|------|-----|------|
| 数据划分 | 80%/20% | 训练集/验证集 |
| 优化器 | Adam | 自适应学习率 |
| 学习率 | 0.001 | 初始学习率 |
| 损失函数 | MSE | 均方误差 |
| 批大小 | 全量 | 使用所有训练数据 |
| 最大轮数 | 500 | 最多训练500个epoch |
| 早停耐心 | 20 | 验证Loss 20轮不降则停止 |
| 最佳模型 | 自动保存 | 保存验证Loss最低的模型 |
## 交易逻辑
```python
每小时新K线形成时:
1. 获取最近10根H1 K线 [Open, High, Low, Close, Volume]
2. 使用训练时的参数归一化数据
3. 输入LSTM模型进行推理
4. 反归一化得到预测收盘价
5. 交易决策:
- 预测价 > 当前价 * 1.0001 买入
- 预测价 < 当前价 * 0.9999 卖出
- 其他情况 不交易
6. 设置止损止盈并提交订单
```
## 性能优化
### 提升模型效果
1. **增加模型容量**:
```python
self.lstm = nn.LSTM(input_size=5, hidden_size=50, num_layers=2)
```
2. **扩展时间窗口**:
```python
lookback = 20 # 使用20根K线预测
```
3. **调整训练参数**:
```python
patience = 30 # 增加早停耐心值
optimizer = torch.optim.Adam(model.parameters(), lr=0.0005) # 降低学习率
```
4. **添加技术指标**:
```python
features = ['open', 'high', 'low', 'close', 'volume', 'rsi', 'macd']
```
5. **数据增强**:
- 添加噪声
- 时间窗口滑动
- 多时间周期融合
### 防止过拟合
✅ **已实现的功能**:
1. **训练集/验证集分割**:
```python
train_size = int(len(X) * 0.8) # 80%训练,20%验证
X_train = X[:train_size]
X_val = X[train_size:]
```
2. **早停法(Early Stopping)**:
```python
patience = 20 # 验证Loss 20轮不降则停止
if val_loss < best_val_loss:
best_model_state = model.state_dict() # 保存最佳模型
```
3. **验证Loss监控**:
```python
# 每10轮显示训练和验证Loss对比
print(f"训练Loss: {train_loss:.6f} | 验证Loss: {val_loss:.6f}")
```
4. **训练曲线可视化**:
- 自动生成 `training_loss.png`
- 双曲线对比(训练vs验证)
- 标记最佳模型位置
⚙️ **可选的进阶方法**:
5. **添加Dropout**:
```python
self.dropout = nn.Dropout(0.2)
```
6. **L2正则化**:
```python
optimizer = Adam(model.parameters(), lr=0.001, weight_decay=1e-5)
```
7. **学习率衰减**:
```python
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5)
```
## 常见问题排查
### 训练相关
**Q: 训练时CUDA不可用?**
```bash
# 检查PyTorch版本
python -c "import torch; print(torch.__version__)"
# 应显示 2.5.1+cu121 (带+cu121后缀)
# 如果是 2.5.1+cpu,需重装GPU版本:
pip install torch --index-url https://download.pytorch.org/whl/cu121
```
**Q: MT5连接失败?**
```
解决方案:
1. 确保MT5已启动并登录
2. 检查工具→选项→EA交易→允许DLL导入
3. 尝试手动在MT5图表上加载EURUSD H1
```
**Q: 训练Loss不下降?**
```
可能原因:
1. 学习率过大 → 降低到0.0001
2. 数据未归一化 → 检查scaler
3. 数据质量差 → 检查MT5历史数据完整性
4. 模型过于简单 → 增加hidden_size或num_layers
```
**Q: 验证Loss远高于训练Loss?**
```
原因: 模型过拟合训练数据
解决方案:
1. 查看训练曲线图(training_loss.png)确认
2. 减少训练轮数或降低模型复杂度
3. 增加训练数据量
4. 添加Dropout层或L2正则化
5. 使用早停机制(已自动启用)
```
**Q: 如何判断模型训练效果好坏?**
```
关键指标:
1. ✅ 验证Loss越小越好 (如<0.001)
2. ✅ 训练Loss和验证Loss接近 (差异<10%)
3. ✅ Loss曲线平稳收敛不震荡
4. ✅ 早停在合理位置触发
查看方法:
- 打开 training_loss.png 查看曲线
- 阅读终端输出的"训练总结"
- 关注"模型状态"的诊断结果
```
### 回测相关
**Q: ONNX推理失败 "wrong dimension"?**
```cpp
// 确保在OnInit()中设置输入输出形状
ulong input_shape[] = {1, 10, 5};
OnnxSetInputShape(model_handle, 0, input_shape);
ulong output_shape[] = {1, 1};
OnnxSetOutputShape(model_handle, 0, output_shape);
```
**Q: 回测没有交易信号?**
```
检查清单:
1. 归一化参数是否一致
2. ONNX模型是否正确加载
3. 时间周期是否为H1
4. 查看Expert日志的预测值
```
**Q: 回测收益为负?**
```
这是正常的,原因:
1. LSTM难以预测随机市场
2. 模型过拟合训练数据
3. 交易成本(点差/滑点)未考虑
4. 需要优化止损止盈参数
建议:
- 仅用于学习,不要实盘
- 尝试不同市场和时间周期
- 结合其他技术指标
```
## 进阶扩展方向
### 1. 模型改进
- **双向LSTM**: 同时考虑过去和未来信息
- **GRU**: 更简单的门控单元,训练更快
- **Transformer**: 注意力机制,捕捉长期依赖
- **CNN-LSTM**: 卷积提取局部特征+LSTM捕捉时序
### 2. 特征工程
- **技术指标**: RSI, MACD, 布林带, KDJ
- **市场情绪**: VIX指数, 恐慌指数
- **基本面**: 经济数据, 新闻情绪分析
- **多周期**: M5/M15/H4数据融合
### 3. 策略优化
- **强化学习**: PPO/DQN优化交易决策
- **仓位管理**: Kelly公式动态调整手数
- **风控系统**: 最大回撤限制, 连续亏损止损
- **集成学习**: 多模型投票/加权平均
### 4. 工程化部署
- **实时推理服务**: FastAPI + Docker
- **模型监控**: MLflow跟踪模型性能
- **在线学习**: 定期用新数据微调模型
- **多品种交易**: 扩展到黄金/原油/股指
## 性能基准
### 训练性能 (RTX 4090)
| 指标 | 数值 |
|------|------|
| 数据量 | 31,053条 |
| 训练集 | 24,833条 (80%) |
| 验证集 | 6,209条 (20%) |
| 训练时间 | ~30秒 (含早停) |
| 实际轮数 | 164-184 (早停触发) |
| 最佳验证Loss | ~0.0001-0.0002 |
| 显存占用 | ~500MB |
### 推理性能 (MT5)
| 指标 | 数值 |
|------|------|
| 单次推理 | <1ms |
| H1周期延迟 | 可忽略 |
| CPU占用 | <1% |
## 免责声明
**重要提示**:
1. 本项目仅用于教学和研究目的,不构成任何投资建议
2. 金融市场存在不可预测性,过去表现不代表未来收益
3. LSTM模型可能过拟合历史数据,实盘表现可能与回测不符
4. 实盘交易前务必在模拟账户充分测试
5. 交易有风险,投资需谨慎,请勿使用无法承受损失的资金
## 参考资料
- [PyTorch官方文档](https://pytorch.org/docs/stable/index.html)
- [ONNX格式规范](https://onnx.ai/)
- [MQL5语言参考](https://www.mql5.com/zh/docs)
- [LSTM原理论文](https://www.bioinf.jku.at/publications/older/2604.pdf)
- [量化交易策略](https://www.quantstart.com/)
## 许可证
本项目采用 MIT 许可证开源,允许自由使用、修改和分发。
## 作者与贡献
本项目由AI辅助生成,用于教学演示。欢迎提出改进建议和Bug反馈。
---
**最后更新**: 2025-10-27
**版本**: 2.0.0
**更新内容**:
- ✅ 新增训练集/验证集划分 (80%/20%)
- ✅ 新增早停机制 (Early Stopping)
- ✅ 新增最佳模型自动保存
- ✅ 新增训练曲线可视化
- ✅ 新增训练状态智能诊断
- ✅ 优化训练流程和文档说明
**适用人群**: 量化交易初学者、深度学习实践者、MQL5开发者
BIN
View File
Binary file not shown.
+398
View File
@@ -0,0 +1,398 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "de76d792",
"metadata": {},
"source": [
"# LSTM外汇价格预测 - 精简版\n",
"\n",
"本Notebook包含LSTM模型训练的核心代码,用于预测EURUSD价格。\n",
"\n",
"## 主要步骤\n",
"1. 导入库和设置设备\n",
"2. 从MT5获取历史数据\n",
"3. 数据归一化\n",
"4. 构造训练/验证数据集\n",
"5. 定义LSTM模型\n",
"6. 训练模型(早停机制)\n",
"7. 可视化训练过程\n",
"8. 导出ONNX模型"
]
},
{
"cell_type": "markdown",
"id": "063db750",
"metadata": {},
"source": [
"## 1. 导入库和设置设备"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fee98c7a",
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torch.nn as nn\n",
"import numpy as np\n",
"import pandas as pd\n",
"import MetaTrader5 as mt5\n",
"from sklearn.preprocessing import MinMaxScaler\n",
"from datetime import datetime, timedelta\n",
"import matplotlib.pyplot as plt\n",
"import os\n",
"\n",
"# 设置中文字体\n",
"plt.rcParams['font.sans-serif'] = ['SimHei']\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"# 选择计算设备(GPU或CPU\n",
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
"print(f\"使用设备: {device}\")"
]
},
{
"cell_type": "markdown",
"id": "19c6b9d0",
"metadata": {},
"source": [
"## 2. 从MT5获取EURUSD历史数据"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eee75337",
"metadata": {},
"outputs": [],
"source": [
"# 连接MT5\n",
"mt5.initialize()\n",
"\n",
"# 设置时间范围(最近5年)\n",
"end_date = datetime.now()\n",
"start_date = end_date - timedelta(days=5*365)\n",
"\n",
"# 获取EURUSD H1数据\n",
"rates = mt5.copy_rates_range(\"EURUSD\", mt5.TIMEFRAME_H1, start_date, end_date)\n",
"mt5.shutdown()\n",
"\n",
"# 转换为DataFrame\n",
"df = pd.DataFrame(rates)\n",
"print(f\"获取到 {len(df):,} 条数据\")\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"id": "4fda9d49",
"metadata": {},
"source": [
"## 3. 数据归一化"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5dccb3f",
"metadata": {},
"outputs": [],
"source": [
"# 创建归一化器,将数据缩放到[0,1]范围\n",
"scaler = MinMaxScaler()\n",
"\n",
"# 对开、高、低、收、成交量进行归一化\n",
"df[['open', 'high', 'low', 'close', 'tick_volume']] = scaler.fit_transform(\n",
" df[['open', 'high', 'low', 'close', 'tick_volume']]\n",
")\n",
"\n",
"print(\"归一化完成\")\n",
"print(f\"最小值: {scaler.data_min_}\")\n",
"print(f\"最大值: {scaler.data_max_}\")"
]
},
{
"cell_type": "markdown",
"id": "15ddac0b",
"metadata": {},
"source": [
"## 4. 构造时间序列数据集"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4887abce",
"metadata": {},
"outputs": [],
"source": [
"def create_dataset(data, lookback=10):\n",
" \"\"\"将时间序列转换为监督学习数据集\"\"\"\n",
" X, y = [], []\n",
" for i in range(len(data) - lookback - 1):\n",
" X.append(data[i:i+lookback]) # 输入:前10根K线\n",
" y.append(data[i+lookback, 3]) # 输出:第11根K线的收盘价\n",
" return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32).reshape(-1, 1)\n",
"\n",
"# 提取特征\n",
"features = df[['open', 'high', 'low', 'close', 'tick_volume']].values\n",
"X, y = create_dataset(features)\n",
"\n",
"# 划分训练集(80%)和验证集(20%\n",
"train_size = int(len(X) * 0.8)\n",
"X_train = torch.from_numpy(X[:train_size]).to(device)\n",
"y_train = torch.from_numpy(y[:train_size]).to(device)\n",
"X_val = torch.from_numpy(X[train_size:]).to(device)\n",
"y_val = torch.from_numpy(y[train_size:]).to(device)\n",
"\n",
"print(f\"训练集: {X_train.shape}\")\n",
"print(f\"验证集: {X_val.shape}\")"
]
},
{
"cell_type": "markdown",
"id": "208c234b",
"metadata": {},
"source": [
"## 5. 定义LSTM模型"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2916d2d",
"metadata": {},
"outputs": [],
"source": [
"class SimpleLSTM(nn.Module):\n",
" \"\"\"简单的LSTM神经网络\"\"\"\n",
" def __init__(self):\n",
" super().__init__()\n",
" # LSTM层:输入5个特征,隐藏层20个神经元\n",
" self.lstm = nn.LSTM(input_size=5, hidden_size=20, num_layers=1, batch_first=True)\n",
" # 全连接层:将20维输出映射到1维(预测值)\n",
" self.fc = nn.Linear(20, 1)\n",
"\n",
" def forward(self, x):\n",
" lstm_out, _ = self.lstm(x)\n",
" return self.fc(lstm_out[:, -1, :]) # 取最后一个时间步的输出\n",
"\n",
"# 创建模型\n",
"model = SimpleLSTM().to(device)\n",
"optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
"criterion = nn.MSELoss()\n",
"\n",
"print(\"模型创建完成\")"
]
},
{
"cell_type": "markdown",
"id": "9e6f4eeb",
"metadata": {},
"source": [
"## 6. 训练模型(带早停机制)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "04481e99",
"metadata": {},
"outputs": [],
"source": [
"# 训练参数\n",
"max_epochs = 500\n",
"patience = 20 # 验证Loss不降20轮则停止\n",
"\n",
"# 记录训练过程\n",
"train_losses = []\n",
"val_losses = []\n",
"best_val_loss = float('inf')\n",
"best_epoch = 0\n",
"patience_counter = 0\n",
"best_model_state = None\n",
"\n",
"print(\"开始训练...\")\n",
"for epoch in range(max_epochs):\n",
" # 训练阶段\n",
" model.train()\n",
" optimizer.zero_grad()\n",
" train_output = model(X_train)\n",
" train_loss = criterion(train_output, y_train)\n",
" train_loss.backward()\n",
" optimizer.step()\n",
" \n",
" # 验证阶段\n",
" model.eval()\n",
" with torch.no_grad():\n",
" val_output = model(X_val)\n",
" val_loss = criterion(val_output, y_val)\n",
" \n",
" # 记录Loss\n",
" train_losses.append(train_loss.item())\n",
" val_losses.append(val_loss.item())\n",
" \n",
" # 每10轮打印一次\n",
" if (epoch + 1) % 10 == 0:\n",
" print(f\"Epoch {epoch+1:3d} | 训练Loss: {train_loss.item():.6f} | 验证Loss: {val_loss.item():.6f}\")\n",
" \n",
" # 早停检查\n",
" if val_loss.item() < best_val_loss:\n",
" best_val_loss = val_loss.item()\n",
" best_epoch = epoch + 1\n",
" patience_counter = 0\n",
" best_model_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}\n",
" else:\n",
" patience_counter += 1\n",
" \n",
" if patience_counter >= patience:\n",
" print(f\"\\n早停触发!最佳模型在Epoch {best_epoch}\")\n",
" break\n",
"\n",
"# 恢复最佳模型\n",
"if best_model_state:\n",
" model.load_state_dict(best_model_state)\n",
" model = model.to(device)\n",
" print(f\"已恢复最佳模型 (Epoch {best_epoch}, 验证Loss: {best_val_loss:.6f})\")"
]
},
{
"cell_type": "markdown",
"id": "1669bc75",
"metadata": {},
"source": [
"## 7. 可视化训练过程"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cd1011f3",
"metadata": {},
"outputs": [],
"source": [
"# 绘制训练和验证Loss曲线\n",
"plt.figure(figsize=(12, 5))\n",
"\n",
"# 完整曲线\n",
"plt.subplot(1, 2, 1)\n",
"plt.plot(train_losses, label='训练Loss', color='blue', linewidth=2)\n",
"plt.plot(val_losses, label='验证Loss', color='orange', linewidth=2)\n",
"plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型 (Epoch {best_epoch})')\n",
"plt.xlabel('Epoch')\n",
"plt.ylabel('Loss')\n",
"plt.title('训练和验证损失曲线')\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"\n",
"# 后80%放大曲线\n",
"plt.subplot(1, 2, 2)\n",
"start_idx = int(len(train_losses) * 0.2)\n",
"plt.plot(range(start_idx, len(train_losses)), train_losses[start_idx:], label='训练Loss', color='blue', linewidth=2)\n",
"plt.plot(range(start_idx, len(val_losses)), val_losses[start_idx:], label='验证Loss', color='orange', linewidth=2)\n",
"plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型')\n",
"plt.xlabel('Epoch')\n",
"plt.ylabel('Loss')\n",
"plt.title('后期收敛细节')\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig('training_loss.png', dpi=150)\n",
"plt.show()\n",
"\n",
"print(\"训练曲线已保存为 training_loss.png\")"
]
},
{
"cell_type": "markdown",
"id": "80b9c1c2",
"metadata": {},
"source": [
"## 8. 导出ONNX模型和归一化参数"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5b82ee2",
"metadata": {},
"outputs": [],
"source": [
"# 导出ONNX模型\n",
"model.eval()\n",
"model_cpu = model.cpu()\n",
"dummy_input = torch.randn(1, 10, 5)\n",
"\n",
"torch.onnx.export(\n",
" model_cpu,\n",
" dummy_input,\n",
" \"lstm_model.onnx\",\n",
" export_params=True,\n",
" opset_version=11,\n",
" input_names=['input'],\n",
" output_names=['output']\n",
")\n",
"\n",
"print(\"✓ ONNX模型已导出: lstm_model.onnx\")\n",
"\n",
"# 保存归一化参数\n",
"np.save('scaler_params.npy', {'min': scaler.data_min_, 'max': scaler.data_max_})\n",
"print(\"✓ 归一化参数已保存: scaler_params.npy\")\n",
"\n",
"# 输出MQL5格式的归一化参数\n",
"print(\"\\n=\" * 60)\n",
"print(\"归一化参数 (复制到LSTM_EA.mq5):\")\n",
"print(\"=\" * 60)\n",
"print(f\"double data_min[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_min_])}}};\")\n",
"print(f\"double data_max[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_max_])}}};\")\n",
"print(\"=\" * 60)"
]
},
{
"cell_type": "markdown",
"id": "cb21d2d4",
"metadata": {},
"source": [
"## 9. 训练总结"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6ac5bc9",
"metadata": {},
"outputs": [],
"source": [
"print(\"=\" * 60)\n",
"print(\"训练总结\")\n",
"print(\"=\" * 60)\n",
"print(f\"总训练轮数: {len(train_losses)} epochs\")\n",
"print(f\"最佳模型: Epoch {best_epoch}\")\n",
"print(f\"最佳验证Loss: {best_val_loss:.6f}\")\n",
"print(f\"最终训练Loss: {train_losses[-1]:.6f}\")\n",
"print(f\"最终验证Loss: {val_losses[-1]:.6f}\")\n",
"\n",
"loss_diff = abs(train_losses[-1] - val_losses[-1])\n",
"print(f\"\\nLoss差异: {loss_diff:.6f}\")\n",
"if loss_diff < 0.0001:\n",
" print(\"✓ 模型状态: 良好\")\n",
"elif loss_diff < 0.001:\n",
" print(\"⚠ 模型状态: 尚可,有轻微过拟合\")\n",
"else:\n",
" print(\"✗ 模型状态: 可能过拟合\")\n",
"print(\"=\" * 60)"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+383
View File
@@ -0,0 +1,383 @@
# AI量化交易LSTM+ONNX+MQL5完整项目生成提示词
## 项目背景
我需要一个用于教学的量化交易完整解决方案,使用深度学习LSTM模型预测价格并在MT5平台自动交易。
## 核心需求
### 1. 技术栈要求
- **深度学习框架**: PyTorch (支持CUDA GPU加速)
- **模型类型**: LSTM (Long Short-Term Memory 长短期记忆网络)
- **模型格式**: ONNX (用于跨平台部署到MT5)
- **交易平台**: MetaTrader 5 (MQL5语言)
- **数据源**: MT5实时历史数据 (使用MetaTrader5 Python库)
- **编程语言**: Python 3.11+ 和 MQL5
### 2. 硬件环境
- GPU: NVIDIA RTX 4090
- CUDA: 12.1+
- 已安装: Python, CUDA, cuDNN
- 需要: 自动检测CUDA并使用GPU训练
### 3. 项目目标
这是一个**教学项目**,需要满足:
- ✅ 代码简洁易懂 (能少一句绝不多一句)
- ✅ 文件数量最少 (Python训练脚本1个 + ONNX模型1个 + MQL5 EA 1个)
- ✅ 完整工作流 (数据获取→训练→导出→回测)
- ✅ 可直接运行 (提供requirements.txt)
- ✅ 中文注释和文档
## 详细要求
### 一、Python训练脚本 (train_lstm.py)
**功能清单**:
1. **CUDA检测与GPU训练**
- 自动检测CUDA是否可用
- 如果CUDA不可用,提示安装GPU版PyTorch的命令
- 显示GPU型号、CUDA版本、可用显存
- 优先使用GPU训练,CPU作为备选
2. **数据获取**
- 使用`MetaTrader5`库连接MT5终端
- 获取指定交易品种(如EURUSD)最近N年的H1数据
- 自动检查MT5连接状态并给出清晰的错误提示
- 显示获取的数据量和时间范围
3. **数据预处理**
- 使用`sklearn.MinMaxScaler`归一化OHLCV数据到[0,1]
- 构造时间序列训练集: 用前N根K线预测下一根收盘价
- **数据集划分**: 80%训练集 + 20%验证集 (时间序列顺序划分)
- 保存归一化参数(最小值/最大值)供MQL5使用
4. **LSTM模型定义**
- 简洁的PyTorch LSTM模型 (input→LSTM→FC→output)
- 输入: [batch_size, seq_len, features]
- 输出: [batch_size, 1] (预测收盘价)
- 模型参数可配置 (隐藏层大小、层数等)
5. **模型训练**
- 使用Adam优化器
- MSE损失函数
- **验证集监控**: 每个epoch计算训练Loss和验证Loss
- **早停机制(Early Stopping)**: 验证Loss连续N轮不降则停止
- **最佳模型保存**: 自动保存验证Loss最低的模型状态
- 训练过程显示进度和双Loss对比
- 支持GPU加速训练
6. **ONNX导出**
- 训练完成后导出为ONNX格式
- **使用最佳模型**: 导出验证Loss最低的模型,而非最后一轮
- 文件保存到**脚本所在目录** (使用`__file__`获取路径)
- 验证ONNX模型正确性
- 显示文件大小和完整保存路径
7. **训练可视化**
- 使用matplotlib绘制训练曲线图
- 双曲线对比: 训练Loss vs 验证Loss
- 标记最佳模型的epoch位置
- 生成两个子图: 完整曲线 + 后期收敛细节
- 保存为training_loss.png
8. **归一化参数输出**
- 保存为.npy文件
- 同时在终端输出C++数组格式,方便直接复制到MQL5代码
- 示例: `double data_min[5] = {0.95, 0.96, ...};`
9. **训练总结报告**
- 显示总训练轮数、最佳epoch
- 显示最佳验证Loss和最终Loss
- 计算训练/验证Loss差异
- **智能诊断**: 自动判断是否过拟合并给出建议
**代码风格**:
- 代码行数控制在250行以内 (含可视化和诊断功能)
- 每个关键步骤都有中文注释
- 函数尽量简洁,避免过度封装
- 输出信息美观清晰,便于教学演示
- 训练过程有清晰的阶段划分注释
---
### 二、MQL5交易EA (LSTM_EA.mq5)
**功能清单**:
1. **ONNX模型加载**
- 使用`#resource`方式嵌入ONNX文件
- 在`OnInit()`中加载模型
- 使用`OnnxSetInputShape`和`OnnxSetOutputShape`设置张量维度
- 模型加载失败时给出清晰提示
2. **数据归一化**
- 硬编码训练时的归一化参数(从Python脚本输出复制)
- 归一化公式: `(value - min) / (max - min)`
- 对OHLCV五个特征分别归一化
3. **推理预测**
- 每个新K线形成时触发(使用静态变量判断)
- 获取最近N根K线数据
- 归一化后输入ONNX模型
- 反归一化得到真实预测价格
4. **交易逻辑**
- 预测价格 > 当前价格 → 买入
- 预测价格 < 当前价格 → 卖出
- 设置止损止盈
- 每次只持有一个仓位
5. **可调参数**
- 手数 (LotSize)
- 止损点数 (StopLoss)
- 止盈点数 (TakeProfit)
**代码风格**:
- 代码行数控制在100行以内
- MQL5标准注释风格
- 核心逻辑清晰,适合教学
---
### 三、依赖文件 (requirements.txt)
必须包含:
```
torch>=2.0.0
numpy>=1.24.0
onnx>=1.14.0
onnxruntime>=1.15.0
pandas>=2.0.0
MetaTrader5>=5.0.0
scikit-learn>=1.3.0
```
并在README中说明如何安装GPU版PyTorch:
```bash
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
```
---
### 四、README说明文档
**必须包含的章节**:
1. **项目简介**
- 一句话概括项目功能
- 技术栈列表
2. **LSTM原理说明** (教学重点)
- LSTM是什么
- 为什么适合时间序列预测
- 门控机制简介 (遗忘门、输入门、输出门)
- 在量化交易中的优势
3. **文件结构**
- 每个文件的作用
- 文件之间的关系
- 包含训练生成的文件(ONNX、参数、曲线图)
4. **快速开始**
- 环境准备 (硬件、软件要求)
- 依赖安装
- 训练模型 (详细步骤)
- **查看训练效果**: 如何解读曲线图和诊断报告
- 部署到MT5 (详细步骤)
- 回测运行
5. **模型架构详解**
- 网络结构图 (文字ASCII图)
- 输入输出格式
- 参数量统计
6. **训练参数说明**
- 数据划分比例
- 优化器、学习率、损失函数
- **早停机制参数** (耐心值、触发条件)
- **最佳模型保存策略**
7. **使用示例**
- 训练脚本运行示例
- 预期输出示例 (含验证Loss和诊断信息)
- 训练曲线图解读
- 回测结果解读
8. **常见问题**
- CUDA不可用怎么办
- MT5连接失败
- ONNX推理错误
- 回测无信号
- 模型不收敛
- **验证Loss远高于训练Loss (过拟合)**
- **如何判断模型训练效果好坏**
9. **进阶方向** (启发学生思考)
- 如何改进模型 (增加层数、添加技术指标等)
- **如何防止过拟合** (重点说明已实现的功能)
- 如何优化交易策略
10. **免责声明**
- 教学用途,不构成投资建议
- 实盘风险提示
---
## 特殊要求
### 代码简洁性
- Python训练脚本: ≤250行 (含验证、早停、可视化)
- MQL5 EA: ≤100行
- 避免过度封装和抽象
- 每行代码都有存在的必要
### 教学友好性
- 中文注释覆盖率 >50%
- 关键步骤输出详细日志
- **训练过程可视化**: 生成易读的曲线图
- **智能诊断**: 自动评估模型状态
- 错误提示清晰具体
- README配图或ASCII图辅助理解
### 可移植性
- 文件路径使用`os.path`处理
- ONNX和参数文件保存到脚本同目录
- 不依赖绝对路径或特定文件夹结构
### 实用性
- 数据来源真实 (MT5历史数据)
- 模型可真实回测
- 参数可调节优化
- 支持GPU加速训练
---
## 交付清单
请生成以下文件:
1. ✅ `train_lstm.py` - Python训练脚本
2. ✅ `LSTM_EA.mq5` - MQL5交易EA
3. ✅ `requirements.txt` - Python依赖清单
4. ✅ `README.md` - 完整中文文档
5. ✅ `prompt.txt` - 本提示词文件(用于复现)
训练脚本运行后自动生成:
6. ✅ `lstm_model.onnx` - 训练好的ONNX模型
7. ✅ `scaler_params.npy` - 归一化参数文件
---
## 示例配置
**默认参数建议**:
- 交易品种: EURUSD
- 时间周期: H1
- 历史数据: 最近5年
- 数据划分: 80%训练 / 20%验证
- 时间窗口: 10根K线
- LSTM隐藏层: 20个单元
- 最大训练轮数: 500 epochs
- 早停耐心值: 20 epochs
- 学习率: 0.001
- 批大小: 全量(小数据集)
---
## 成功标准
生成的项目应该满足:
1. ✅ 新手可在10分钟内完成训练
2. ✅ 代码总行数 <400行 (Python≤250行 + MQL5≤100行)
3. ✅ GPU自动检测并加速
4. ✅ **训练集/验证集自动划分**
5. ✅ **早停机制自动运行**
6. ✅ **最佳模型自动保存**
7. ✅ **训练曲线自动生成**
8. ✅ **模型状态智能诊断**
9. ✅ 模型可成功导出为ONNX
10. ✅ EA可在MT5回测器运行
11. ✅ 所有步骤有详细中文说明
12. ✅ 文件保存路径正确 (脚本同目录)
---
## 输出格式
请按以下顺序生成:
1. 先生成`requirements.txt`
2. 再生成`train_lstm.py` (包含详细注释)
3. 再生成`LSTM_EA.mq5`
4. 最后生成`README.md` (完整文档)
每个文件生成后立即告知文件路径和关键信息。
---
## 风格示例
**Python代码风格**:
```python
# 检查CUDA
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
```
**MQL5代码风格**:
```cpp
// 归一化参数(从训练脚本复制)
double data_min[5] = {0.95, 0.96, 0.95, 0.95, 9.0};
double data_max[5] = {1.23, 1.24, 1.23, 1.23, 24000.0};
```
**文档风格**:
```markdown
## 快速开始
### 1. 安装依赖
\`\`\`bash
pip install -r requirements.txt
\`\`\`
### 2. 训练模型
\`\`\`bash
python train_lstm.py
\`\`\`
```
---
## 重要提示
1. **GPU训练必须自动检测**: 不要假设用户已安装GPU版PyTorch
2. **文件路径必须正确**: 使用`__file__`确保保存到脚本目录
3. **归一化参数必须输出**: 终端显示可直接复制的C++数组格式
4. **ONNX张量维度必须设置**: MQL5中必须调用`OnnxSetInputShape`和`OnnxSetOutputShape`
5. **代码必须极简**: 优先考虑教学清晰度,而非工程复杂度
6. **验证集必须包含**: 实现训练/验证划分和双Loss监控
7. **早停必须实现**: 防止过拟合和浪费训练时间
8. **最佳模型必须保存**: 不能只保存最后一轮的模型
9. **可视化必须自动生成**: 便于学生直观理解训练过程
10. **诊断信息必须输出**: 帮助学生判断模型质量
---
## 可选扩展 (如果有时间)
✅ **已实现的核心功能**:
- 训练/验证集分割
- 早停机制
- 最佳模型保存
- 训练曲线绘制
- 智能诊断报告
⚙️ **可选的进阶功能**:
- 学习率衰减调度器
- Dropout正则化
- L2权重衰减
- 支持多品种/多周期
- 添加更多技术指标作为特征
- 批量训练(DataLoader)
---
**开始生成项目文件,使用中文输出,代码注释也用中文。**
+98
View File
@@ -0,0 +1,98 @@
# LSTM量化交易项目生成 - 简单版提示词
## 我的需求
帮我生成一个**LSTM量化交易教学项目**,要求代码简洁,适合初学者。
## 项目要求
### 基本信息
- **模型**: LSTM神经网络
- **用途**: 预测EURUSD价格并自动交易
- **平台**: MetaTrader 5
- **语言**: Python + MQL5
- **硬件**: 支持NVIDIA GPU训练 (我有RTX 4090)
### 核心功能
1. 从MT5获取EURUSD最近5年H1数据
2. 用PyTorch训练LSTM模型
3. 导出为ONNX格式
4. 在MT5中自动交易
5. 支持策略回测
### 文件要求
只需要3个核心文件:
- `train_lstm.py` - Python训练脚本
- `LSTM_EA.mq5` - MQL5交易EA
- `requirements.txt` - 依赖包清单
另外需要:
- `README.md` - 使用说明文档
### 代码风格
- ✅ 极简代码 (能少一句绝不多一句)
- ✅ Python脚本 <150行
- ✅ MQL5代码 <100行
- ✅ 中文注释
- ✅ 新手可看懂
## 技术细节
### Python训练脚本要做什么?
1. 检测CUDA,自动用GPU训练
2. 连接MT5获取历史数据
3. 归一化OHLCV数据
4. **数据集划分**: 80%训练集 + 20%验证集
5. 训练LSTM模型 (10根K线→预测下一根收盘价)
6. **早停机制**: 验证Loss不降则自动停止
7. **最佳模型保存**: 自动保存验证Loss最低的模型
8. **可视化**: 生成训练曲线图(training_loss.png)
9. 导出ONNX模型到**脚本同目录**
10. 输出归一化参数 (供MQL5使用)
### MQL5 EA要做什么?
1. 加载ONNX模型
2. 每小时获取最近10根K线
3. 归一化后输入模型预测
4. 预测涨→买入,预测跌→卖出
5. 设置止损止盈
### README要写什么?
1. LSTM是什么 (简单解释)
2. 如何安装依赖
3. 如何训练模型
4. 如何查看训练曲线和效果评估
5. 如何在MT5回测
6. 常见问题
7. 训练优化建议 (如何判断模型好坏)
## 默认参数
- 交易品种: EURUSD
- 时间周期: H1
- 历史数据: 5年
- 数据划分: 80%训练 / 20%验证
- 时间窗口: 10根K线
- LSTM隐藏层: 20
- 最大训练轮数: 500
- 早停耐心值: 20
- 学习率: 0.001
## 重要提示
1. **GPU训练**: 自动检测CUDA,如果没有提示如何安装
2. **文件路径**: 使用`__file__`确保ONNX保存到脚本目录
3. **归一化参数**: 终端输出C++数组格式,可直接复制到MQL5
4. **ONNX设置**: MQL5必须调用`OnnxSetInputShape`和`OnnxSetOutputShape`
5. **代码简洁**: 教学优先,不要过度工程化
6. **智能训练**: 支持早停、验证集监控、最佳模型保存
7. **可视化**: 自动生成训练曲线图便于效果评估
## 开始生成
请按顺序生成:
1. `requirements.txt`
2. `train_lstm.py`
3. `LSTM_EA.mq5`
4. `README.md`
所有输出使用中文,代码注释也用中文。
+7
View File
@@ -0,0 +1,7 @@
torch>=2.0.0
numpy>=1.24.0
onnx>=1.14.0
onnxruntime>=1.15.0
pandas>=2.0.0
MetaTrader5>=5.0.0
scikit-learn>=1.3.0
BIN
View File
Binary file not shown.
+465
View File
@@ -0,0 +1,465 @@
# 导入PyTorch核心库,用于深度学习模型构建和训练
import torch
# 导入PyTorch神经网络模块,包含各种网络层的定义
import torch.nn as nn
# 导入NumPy,用于数值计算和数组操作
import numpy as np
# 导入Pandas,用于数据处理和分析
import pandas as pd
# 导入MetaTrader5接口,用于获取外汇交易数据
import MetaTrader5 as mt5
# 从scikit-learn导入数据归一化工具
# 从scikit-learn导入数据归一化工具
from sklearn.preprocessing import MinMaxScaler
# ==================== CUDA设备检查 ====================
# 强制检查CUDA是否可用,如果不可用则给出警告和安装指引
if not torch.cuda.is_available():
# 打印分隔线,使警告信息更醒目
print("=" * 60)
# 提示用户CUDA不可用
print("警告: CUDA不可用!")
# 显示当前安装的PyTorch版本号
print(f"当前PyTorch版本: {torch.__version__}")
# 说明安装的是CPU版本
print("您安装的是CPU版本的PyTorch")
# 提供换行,使信息更清晰
print("\n请卸载当前PyTorch并安装CUDA版本:")
# 提供卸载当前版本的命令
print("pip uninstall torch torchvision torchaudio")
# 提供安装CUDA版本的命令(针对CUDA 12.1
print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121")
# 打印分隔线
print("=" * 60)
# 询问用户是否继续使用CPU进行训练
response = input("\n是否继续使用CPU训练? (y/n): ")
# 如果用户不输入'y',则退出程序
if response.lower() != 'y':
quit()
# 根据CUDA是否可用来选择计算设备(GPU或CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 打印分隔线
print(f"\n{'='*60}")
# 显示当前使用的计算设备
print(f"使用设备: {device}")
# 如果CUDA可用,显示GPU详细信息
if torch.cuda.is_available():
# 显示GPU型号名称
print(f"GPU型号: {torch.cuda.get_device_name(0)}")
# 显示CUDA版本
print(f"CUDA版本: {torch.version.cuda}")
# 显示GPU可用显存大小(转换为GB单位)
print(f"可用显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB")
# 打印分隔线
# 打印分隔线
print(f"{'='*60}\n")
# ==================== 连接MT5并获取历史数据 ====================
# 连接MT5获取数据
print("正在连接MT5...")
# 初始化MT5连接,如果失败则退出程序
if not mt5.initialize():
# 提示用户MT5初始化失败的原因
print("MT5初始化失败,请确保MT5已启动并登录")
# 退出程序
quit()
# 导入datetime模块用于处理日期和时间
from datetime import datetime, timedelta
# 获取当前日期时间作为数据结束时间
end_date = datetime.now()
# 计算开始时间为当前时间往前推5年(5*365天)
start_date = end_date - timedelta(days=5*365)
# 提示用户正在获取数据,显示数据范围
print(f"正在获取EURUSD H1数据 ({start_date.date()}{end_date.date()})...")
# 从MT5获取EURUSD货币对的H1(1小时)时间框架的历史数据
rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)
# 关闭MT5连接,释放资源
mt5.shutdown()
# 检查是否成功获取数据,如果没有数据则退出
if rates is None or len(rates) == 0:
# 提示用户获取数据失败的可能原因
print("获取数据失败,请检查MT5中是否有EURUSD历史数据")
# 退出程序
quit()
# 将获取的数据转换为Pandas DataFrame格式,便于后续处理
df = pd.DataFrame(rates)
# 显示获取到的数据条数(使用千位分隔符格式化)
# 显示获取到的数据条数(使用千位分隔符格式化)
print(f"获取到 {len(df):,} 条EURUSD H1数据\n")
# ==================== 数据预处理 ====================
# 数据归一化 - 创建MinMaxScaler对象,用于将数据缩放到0-1范围
scaler = MinMaxScaler()
# 对开盘价、最高价、最低价、收盘价和成交量进行归一化处理
# fit_transform会先学习数据的最小值和最大值,然后进行归一化转换
df[['open', 'high', 'low', 'close', 'tick_volume']] = scaler.fit_transform(
df[['open', 'high', 'low', 'close', 'tick_volume']])
# ==================== 构造训练数据集 ====================
# 构造训练数据 - 定义函数将时间序列数据转换为监督学习格式
def create_dataset(data, lookback=10):
"""
将时间序列数据转换为监督学习数据集
参数:
data: 输入的时间序列数据
lookback: 回溯窗口大小,表示用过去多少个时间步预测下一个时间步
返回:
X: 特征数据(过去lookback个时间步的数据)
y: 标签数据(下一个时间步的收盘价)
"""
# 初始化特征列表和标签列表
X, y = [], []
# 遍历数据,构造训练样本
# 减去lookback+1是为了确保每个样本都有对应的标签
for i in range(len(data) - lookback - 1):
# 提取从i到i+lookback的数据作为特征(输入)
X.append(data[i:i+lookback])
# 提取i+lookback位置的收盘价(索引3)作为标签(输出)
y.append(data[i+lookback, 3]) # 3代表收盘价列
# 将列表转换为NumPy数组,并指定数据类型为float32(节省内存)
return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32).reshape(-1, 1)
# 从DataFrame中提取需要的特征列(开、高、低、收、成交量)
features = df[['open', 'high', 'low', 'close', 'tick_volume']].values
# 调用create_dataset函数创建训练数据集,默认使用10个时间步作为回溯窗口
X, y = create_dataset(features)
# ==================== 划分训练集和验证集 ====================
# 计算训练集大小(80%用于训练)
train_size = int(len(X) * 0.8)
# 划分训练集特征数据(前80%
X_train_np = X[:train_size]
# 划分训练集标签数据(前80%
y_train_np = y[:train_size]
# 划分验证集特征数据(后20%
X_val_np = X[train_size:]
# 划分验证集标签数据(后20%
y_val_np = y[train_size:]
# 将训练集NumPy数组转换为PyTorch张量,并移动到指定的计算设备(GPU或CPU)
X_train = torch.from_numpy(X_train_np).to(device)
# 将训练集标签数据也转换为PyTorch张量并移动到设备
y_train = torch.from_numpy(y_train_np).to(device)
# 将验证集特征数据转换为PyTorch张量并移动到设备
X_val = torch.from_numpy(X_val_np).to(device)
# 将验证集标签数据转换为PyTorch张量并移动到设备
y_val = torch.from_numpy(y_val_np).to(device)
# 打印训练集和验证集的形状,便于确认数据维度
print(f"训练集形状: {X_train.shape}") # 应该是 (训练样本数, 10, 5)
print(f"训练标签形状: {y_train.shape}") # 应该是 (训练样本数, 1)
print(f"验证集形状: {X_val.shape}") # 应该是 (验证样本数, 10, 5)
print(f"验证标签形状: {y_val.shape}\n") # 应该是 (验证样本数, 1)
# ==================== 定义LSTM神经网络模型 ====================
# LSTM模型 - 定义一个简单的LSTM神经网络类
class SimpleLSTM(nn.Module):
"""
简单的LSTM神经网络模型
用于预测外汇价格的下一个收盘价
"""
def __init__(self):
"""初始化模型结构"""
# 调用父类的初始化方法
super().__init__()
# 定义LSTM层:
# input_size=5: 输入特征数(开、高、低、收、成交量)
# hidden_size=20: LSTM隐藏层的神经元数量
# num_layers=1: LSTM层数
# batch_first=True: 输入数据的第一个维度是batch_size
self.lstm = nn.LSTM(input_size=5, hidden_size=20, num_layers=1, batch_first=True)
# 定义全连接层,将LSTM的输出(20维)映射到1维(预测的收盘价)
self.fc = nn.Linear(20, 1)
def forward(self, x):
"""
前向传播函数
参数:
x: 输入数据,形状为 (batch_size, sequence_length, input_size)
返回:
预测值,形状为 (batch_size, 1)
"""
# 将输入数据传入LSTM层,获取输出
# lstm_out形状: (batch_size, sequence_length, hidden_size)
# _表示隐藏状态和细胞状态,这里不需要使用
lstm_out, _ = self.lstm(x)
# 取LSTM输出的最后一个时间步([:, -1, :]),传入全连接层得到最终预测
return self.fc(lstm_out[:, -1, :])
# 创建模型实例并移动到指定设备(GPU或CPU)
model = SimpleLSTM().to(device)
# 定义优化器,使用Adam算法,学习率设为0.001
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 定义损失函数,使用均方误差(MSE)损失
# 定义损失函数,使用均方误差(MSE)损失
criterion = nn.MSELoss()
# ==================== 训练模型 ====================
# 训练
print("开始训练...")
# 打印分隔线,使训练过程更清晰
print(f"{'='*60}")
# 初始化记录训练过程的列表
train_losses = [] # 记录每个epoch的训练损失
val_losses = [] # 记录每个epoch的验证损失
best_val_loss = float('inf') # 初始化最佳验证损失为无穷大
best_epoch = 0 # 记录最佳模型对应的epoch
patience = 20 # 早停的耐心值(验证损失20个epoch不下降就停止)
patience_counter = 0 # 早停计数器
best_model_state = None # 保存最佳模型的状态
# 训练最多500个epoch(但可能会提前停止)
for epoch in range(500):
# ========== 训练阶段 ==========
model.train() # 设置模型为训练模式
# 清空之前的梯度,防止梯度累积
optimizer.zero_grad()
# 前向传播:将训练数据输入模型,得到预测输出
output = model(X_train)
# 计算训练损失:比较预测值和真实值之间的差异
train_loss = criterion(output, y_train)
# 反向传播:计算损失函数对模型参数的梯度
train_loss.backward()
# 更新参数:根据梯度和学习率更新模型参数
optimizer.step()
# ========== 验证阶段 ==========
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 验证时不计算梯度,节省内存
# 在验证集上进行前向传播
val_output = model(X_val)
# 计算验证损失
val_loss = criterion(val_output, y_val)
# 记录本epoch的损失值
train_losses.append(train_loss.item())
val_losses.append(val_loss.item())
# 每10个epoch打印一次训练进度
if (epoch + 1) % 10 == 0:
# 显示当前epoch、训练损失和验证损失
print(f"Epoch {epoch+1:3d} | 训练Loss: {train_loss.item():.6f} | 验证Loss: {val_loss.item():.6f}")
# ========== 早停机制 ==========
# 如果当前验证损失是最好的
if val_loss.item() < best_val_loss:
best_val_loss = val_loss.item() # 更新最佳验证损失
best_epoch = epoch + 1 # 记录最佳epoch
patience_counter = 0 # 重置耐心计数器
# 保存当前最佳模型的状态(深拷贝)
best_model_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}
else:
# 如果验证损失没有改善,增加计数器
patience_counter += 1
# 如果连续patience个epoch验证损失都没有改善,提前停止训练
if patience_counter >= patience:
print(f"\n早停触发! 验证损失已经{patience}个epoch没有改善")
print(f"最佳模型出现在Epoch {best_epoch},验证Loss: {best_val_loss:.6f}")
break
# 恢复最佳模型的参数
if best_model_state is not None:
model.load_state_dict(best_model_state)
model = model.to(device) # 确保模型在正确的设备上
print(f"\n已恢复最佳模型 (Epoch {best_epoch})")
# 打印分隔线,表示训练结束
# 打印分隔线,表示训练结束
print(f"{'='*60}\n")
# ==================== 绘制训练曲线 ====================
# 绘制Loss曲线,可视化训练过程
print("正在生成训练曲线图...")
try:
# 导入matplotlib绘图库
import matplotlib.pyplot as plt
# 导入os模块(如果之前没有导入)
import os
# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 创建图形和坐标轴
plt.figure(figsize=(12, 5))
# 第一个子图:训练损失和验证损失
plt.subplot(1, 2, 1)
# 绘制训练损失曲线(蓝色)
plt.plot(train_losses, label='训练Loss', color='blue', linewidth=2)
# 绘制验证损失曲线(橙色)
plt.plot(val_losses, label='验证Loss', color='orange', linewidth=2)
# 标记最佳模型的位置(红色虚线)
plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型 (Epoch {best_epoch})')
# 设置x轴标签
plt.xlabel('Epoch')
# 设置y轴标签
plt.ylabel('Loss')
# 设置图表标题
plt.title('训练和验证损失曲线')
# 显示图例
plt.legend()
# 显示网格
plt.grid(True, alpha=0.3)
# 第二个子图:放大后期的损失曲线(更清楚地看收敛情况)
plt.subplot(1, 2, 2)
# 只显示后80%的epoch,方便观察收敛细节
start_idx = int(len(train_losses) * 0.2)
plt.plot(range(start_idx, len(train_losses)), train_losses[start_idx:],
label='训练Loss', color='blue', linewidth=2)
plt.plot(range(start_idx, len(val_losses)), val_losses[start_idx:],
label='验证Loss', color='orange', linewidth=2)
plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型 (Epoch {best_epoch})')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练和验证损失曲线(后80%')
plt.legend()
plt.grid(True, alpha=0.3)
# 调整子图之间的间距
plt.tight_layout()
# 保存图表到文件(获取当前脚本所在目录)
current_dir = os.path.dirname(os.path.abspath(__file__))
loss_plot_path = os.path.join(current_dir, "training_loss.png")
plt.savefig(loss_plot_path, dpi=150, bbox_inches='tight')
print(f"训练曲线已保存: {loss_plot_path}")
# 显示图表
plt.show()
except ImportError:
# 如果没有安装matplotlib,跳过绘图
print("未安装matplotlib库,跳过绘图。可运行: pip install matplotlib")
except Exception as e:
# 捕获其他绘图错误
print(f"绘图时出错: {e}")
print(f"{'='*60}\n")
# ==================== 导出ONNX模型 ====================
# 导出ONNX (先移到CPU) - ONNX是一种跨平台的模型格式
print("正在导出ONNX模型...")
# 将模型设置为评估模式(关闭dropout等训练特有的层)
model.eval()
# 将模型从GPU移动到CPU,因为ONNX导出在CPU上更稳定
model_cpu = model.cpu()
# 创建一个虚拟输入张量,用于ONNX导出时追踪模型结构
# 形状为 (1, 10, 5)batch_size=1, sequence_length=10, input_size=5
dummy_input = torch.randn(1, 10, 5)
# 导入os模块用于文件路径操作
import os
# 获取脚本所在目录的绝对路径
script_dir = os.path.dirname(os.path.abspath(__file__))
# 构造ONNX模型的完整保存路径
onnx_path = os.path.join(script_dir, "lstm_model.onnx")
# 构造归一化参数的完整保存路径
scaler_path = os.path.join(script_dir, "scaler_params.npy")
# 显示脚本所在目录
print(f"脚本所在目录: {script_dir}")
# 显示模型将要保存的路径
print(f"模型将保存到: {onnx_path}\n")
# 使用try-except捕获可能的导出错误
try:
# 导出模型为ONNX格式
torch.onnx.export(
model_cpu, # 要导出的模型
dummy_input, # 模型的示例输入
onnx_path, # 保存路径
export_params=True, # 导出模型参数
opset_version=11, # ONNX算子集版本(11是较为通用的版本)
do_constant_folding=True, # 是否执行常量折叠优化
input_names=['input'], # 输入节点的名称
output_names=['output'], # 输出节点的名称
dynamic_axes={'input': {0: 'batch_size'}} # 动态维度设置(batch_size可变)
)
# 验证文件是否真的生成
if os.path.exists(onnx_path):
# 获取生成的ONNX文件大小(转换为KB)
file_size = os.path.getsize(onnx_path) / 1024
# 显示保存成功信息
print(f"ONNX模型已保存: {onnx_path}")
# 显示文件大小
print(f"文件大小: {file_size:.2f} KB")
# 验证ONNX模型的正确性
import onnx
# 加载刚才保存的ONNX模型
onnx_model = onnx.load(onnx_path)
# 检查模型格式是否正确
onnx.checker.check_model(onnx_model)
# 显示验证通过信息
print("ONNX模型验证通过!")
else:
# 如果文件未生成,显示错误信息
print(f"错误: ONNX文件未生成在 {onnx_path}")
except Exception as e:
# 捕获并显示导出过程中的任何异常
print(f"导出ONNX失败: {e}")
# 导入traceback模块
import traceback
# 导入traceback模块
import traceback
# 打印详细的错误堆栈信息,便于调试
traceback.print_exc()
# ==================== 保存归一化参数 ====================
# 保存归一化参数 - 将归一化时使用的最小值和最大值保存下来
# 保存为字典格式,包含'min'和'max'两个键
np.save(scaler_path, {'min': scaler.data_min_, 'max': scaler.data_max_})
# 显示归一化参数保存成功信息
print(f"归一化参数已保存: {scaler_path}")
# ==================== 输出归一化参数供MQL5使用 ====================
# 输出归一化参数供MQL5使用 - 生成可以直接复制到MQL5代码中的数组声明
# 打印分隔线
print(f"\n{'='*60}")
# 提示这些参数需要复制到EA代码中
print("归一化参数 (复制到LSTM_EA.mq5):")
# 打印分隔线
print(f"{'='*60}")
# 生成data_min数组的声明语句,包含5个特征的最小值(保留5位小数)
print(f"double data_min[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_min_])}}};")
# 生成data_max数组的声明语句,包含5个特征的最大值(保留5位小数)
print(f"double data_max[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_max_])}}};")
# 打印分隔线
print(f"{'='*60}\n")
# ==================== 输出训练总结 ====================
print(f"{'='*60}")
print("训练总结:")
print(f"{'='*60}")
print(f"总训练轮数: {len(train_losses)} epochs")
print(f"最佳模型: Epoch {best_epoch}")
print(f"最佳验证Loss: {best_val_loss:.6f}")
print(f"最终训练Loss: {train_losses[-1]:.6f}")
print(f"最终验证Loss: {val_losses[-1]:.6f}")
# 计算训练集和验证集的Loss差异,用于判断是否过拟合
loss_diff = abs(train_losses[-1] - val_losses[-1])
print(f"训练/验证Loss差异: {loss_diff:.6f}")
if loss_diff < 0.0001:
print("✓ 模型状态: 良好,没有明显的过拟合")
elif loss_diff < 0.001:
print("⚠ 模型状态: 尚可,有轻微的过拟合倾向")
else:
print("✗ 模型状态: 可能存在过拟合,建议调整模型复杂度或增加数据")
print(f"{'='*60}\n")
# ==================== 输出完成信息和后续步骤 ====================
# 显示训练完成信息
print("训练完成! 下一步:")
# 提示步骤1:将ONNX模型文件复制到MT5的指定目录
print("1. 将 lstm_model.onnx 复制到 MT5/MQL5/Files/")
# 提示步骤2:将归一化参数复制到EA代码中
print("2. 将上面的归一化参数复制到 LSTM_EA.mq5")
# 提示步骤3:编译EA并运行回测
print("3. 编译并运行EA进行回测")
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB