Add files via upload
This commit is contained in:
+153
@@ -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
Binary file not shown.
+119
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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开发者
|
||||
Binary file not shown.
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
**开始生成项目文件,使用中文输出,代码注释也用中文。**
|
||||
@@ -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`
|
||||
|
||||
所有输出使用中文,代码注释也用中文。
|
||||
@@ -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
|
||||
Binary file not shown.
+465
@@ -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进行回测")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Reference in New Issue
Block a user