commit 702586dcfd48f489c5f1bd7f54abeb374af71630 Author: Shuo Cao <42436068+caoshuo594@users.noreply.github.com> Date: Mon Oct 27 19:45:33 2025 +0800 Add files via upload diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..829f455 --- /dev/null +++ b/CHANGELOG.md @@ -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 +**维护状态**: 活跃开发中 diff --git a/LSTM_EA.ex5 b/LSTM_EA.ex5 new file mode 100644 index 0000000..7fb6eed Binary files /dev/null and b/LSTM_EA.ex5 differ diff --git a/LSTM_EA.mq5 b/LSTM_EA.mq5 new file mode 100644 index 0000000..218889e --- /dev/null +++ b/LSTM_EA.mq5 @@ -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 + +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); + } + } +} +//+------------------------------------------------------------------+ diff --git a/README.md b/README.md new file mode 100644 index 0000000..946c12f --- /dev/null +++ b/README.md @@ -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开发者 diff --git a/lstm_model.onnx b/lstm_model.onnx new file mode 100644 index 0000000..5e2a58b Binary files /dev/null and b/lstm_model.onnx differ diff --git a/lstm_training.ipynb b/lstm_training.ipynb new file mode 100644 index 0000000..aac14a9 --- /dev/null +++ b/lstm_training.ipynb @@ -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 +} diff --git a/prompt_professional.txt b/prompt_professional.txt new file mode 100644 index 0000000..b7a5063 --- /dev/null +++ b/prompt_professional.txt @@ -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) + +--- + +**开始生成项目文件,使用中文输出,代码注释也用中文。** diff --git a/prompt_simple.txt b/prompt_simple.txt new file mode 100644 index 0000000..4624caf --- /dev/null +++ b/prompt_simple.txt @@ -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` + +所有输出使用中文,代码注释也用中文。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..699520d --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/scaler_params.npy b/scaler_params.npy new file mode 100644 index 0000000..f489709 Binary files /dev/null and b/scaler_params.npy differ diff --git a/train_lstm.py b/train_lstm.py new file mode 100644 index 0000000..5a9181c --- /dev/null +++ b/train_lstm.py @@ -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进行回测") diff --git a/training_loss.png b/training_loss.png new file mode 100644 index 0000000..4ae7c58 Binary files /dev/null and b/training_loss.png differ