first commit
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# MQL5代码审查报告 - 第一、二阶段
|
||||
|
||||
**审查对象**: TopBottomEA.mq5
|
||||
**审查日期**: 2026-06-03
|
||||
**代码总行数**: 2574 行
|
||||
|
||||
---
|
||||
|
||||
## 1. 程序意图推测
|
||||
|
||||
### 1.1 文件类型判断
|
||||
- **文件类型**: Expert Advisor(EA,自动交易程序)
|
||||
- **文件路径**: `C:\Users\Administrator\Desktop\mt5_backtest\TopBottomEA.mq5`
|
||||
- **依赖头文件**: 无(DFCC 转换库以 class 形式内联在文件 1210–2436 行)
|
||||
- **文件描述**: 自称"波动率自适应机制"、单笔持仓、平均每天约4单、持仓约12小时、用Dukascopy历史数据通过$20小额回测10年+
|
||||
- **文件来源判断**: 顶部 copyright 写 2022 MetaQuotes,version 1.31,但变量名大量使用 `dword_6B59710`/`qword_6B59550` 这样的**反编译器十六进制地址风格**,是 fxDreema MQL4→MQL5 转换器**自动生成的产物**(参见 1169 行注释 `//== fxDreema MQL4 to MQL5 Converter ==//`)。
|
||||
|
||||
### 1.2 输入参数分析
|
||||
|
||||
| 输入参数 | 类型 | 默认值 | 含义推测 |
|
||||
|---|---|---|---|
|
||||
| `__Lots__` | double | 0.1 | 固定手数 |
|
||||
| `__Compoundinterestswitch__` | bool | false | 是否启用复利(按账户净值计算手数) |
|
||||
| `__Risk__` | int | 40 | 复利计算分子,公式:`equity * Risk / 1,000,000` |
|
||||
| `__ParameterSwitching__` | bool | false | 是否按品种自动切换参数(true=用 CHFSGD/GBPSGD 特殊规则) |
|
||||
| `__Volatility__` | int | 110 | WPR(威廉指标)的回溯周期 |
|
||||
| `__StopLoss__` | int | 800 | 止损点数(与 `_Point` 相乘) |
|
||||
| `__Profit__` | int | 300 | 止盈点数(**仅 MQL4 4位报价下=30pips;5位下=3pips,明显偏小**) |
|
||||
| `__Pointdifferencelimit__` | double | 50.0 | 最大点差过滤(MODE_SPREAD ≤ 此值才允许开仓) |
|
||||
| `__Displayswitch__` | bool | false | 图表显示开关(代码全被注释,无实际效果) |
|
||||
| `__Magic__` | int | 888999 | 魔术号 |
|
||||
| `__CommentName__` | string | "TopBottomEA" | 订单注释 |
|
||||
|
||||
**隐含的设计假设**:
|
||||
- 报价位数固定为 4 位(MQL4 时代遗留假设),移植到 5 位经纪商时所有点数类参数(StopLoss/Profit 等)量纲会发生 10 倍变化。
|
||||
- `Volatility` 同时承担 WPR 周期和"参数切换开关的品种识别"双重角色。
|
||||
- 复利计算基数 `qword_6B59510 = 1,000,000` 是硬编码常量。
|
||||
|
||||
### 1.3 核心指标/品种识别
|
||||
|
||||
- **iBands**(布林带):`PERIOD_CURRENT, period=20, shift=0, deviation=2.0, PRICE_CLOSE`
|
||||
- 取两条线:`mode=1`(UPPER_BAND)、`mode=2`(LOWER_BAND)
|
||||
- **浪费**:`g_iBands_Upper` 和 `g_iBands_Lower` 是**两个独立的 iBands 句柄**(行 529–530),而一个 iBands 句柄本身就包含 2/3 个 buffer——单句柄即可
|
||||
- **iWPR**(威廉百分比):`period = Volatility`(默认 110),shift=0
|
||||
- **未使用 MA/RSI/MACD/ATR/K线形态**
|
||||
- **特殊数据**:
|
||||
- 大量全局变量承担"上一次成交状态/订单扫描缓存"功能
|
||||
- 2 个**新柱检测**变量 `qword_6B59548` / `qword_6B59558`(分别对应 Buy/Sell 通道)
|
||||
- DFCC 类在 RefreshRates 中维护 6 个全局价格数组 `Ask/Bid/Close/High/Low/Open/Time/Volume`(行 1180)
|
||||
|
||||
### 1.4 功能意图总结
|
||||
|
||||
- **核心策略**: **波动率自适应的"顶/底反转"EA**——以布林带宽度(Upper-Lower)作为波动率过滤器,仅在带宽处于指定区间时,结合 WPR 极值(< -95 做多,> -5 做空)触发单笔反向开仓;持仓由"反向 WPR 极值"或"达到 Profit 点数"主动平仓,由硬编码 StopLoss 兜底。
|
||||
- **策略类型**: **均值回归(Mean Reversion)+ 波动率过滤**,单仓方向性反转
|
||||
- **关键特征**:
|
||||
1. 单品种单笔同时持仓(`dword_6B595A0 + dword_6B59598 < 1` 强约束)
|
||||
2. 同一柱内不重复加仓(新柱检测)
|
||||
3. 区分"测试模式时间窗"与"实盘 GMT 时间窗"(行 553/690)
|
||||
4. 5 个品种硬编码参数表(GBPCAD/EURSGD/GBPCHF/CHFSGD/GBPSGD)
|
||||
5. 复利开关开启时按账户净值动态算手数
|
||||
6. 用 fxDreema 工具链从 MQL4 批量转换,非手写
|
||||
|
||||
---
|
||||
|
||||
## 2. 全局逻辑链审查
|
||||
|
||||
### 2.1 文件依赖关系
|
||||
|
||||
```
|
||||
TopBottomEA.mq5
|
||||
├── (内联) class DFCC ── 2000+ 行的 MQL4→MQL5 兼容层
|
||||
└── (内联) class DFCC_TRADES ── 错误码/填充策略工具
|
||||
```
|
||||
|
||||
- 无 `#include` 头文件
|
||||
- 无外部库依赖
|
||||
- DFCC 实质是**MQL4 API 的完整仿真层**(OrderSelect/OrderSend/OrderClose/MarketInfo 等),是 fxDreema 转换器自动生成;这种"以单文件 inline 巨型包装类"的做法严重牺牲可读性
|
||||
|
||||
### 2.2 事件处理完整性
|
||||
|
||||
| 事件函数 | 是否存在 | 实现状态 | 备注 |
|
||||
|---|---|---|---|
|
||||
| `OnInit()` | ✅ | 几乎空壳 | 99% 是被注释掉的图表对象创建代码;仅 220 行一行有效赋值被后续 OnTick 覆盖 |
|
||||
| `OnDeinit()` | ✅ | 完整 | 1197 行,仅释放三个指标句柄 |
|
||||
| `OnTick()` | ✅ | 完整 | 1202 行入口→调 RefreshRates→__OnTick__ |
|
||||
| `OnTimer()` | ❌ | — | 未使用 |
|
||||
| `OnTrade()` / `OnTradeTransaction()` | ❌ | — | 未使用,订单状态变化靠轮询 |
|
||||
|
||||
**关键事件链**:
|
||||
```
|
||||
MT5报价 → OnTick() → DFCC::RefreshRates()(更新全局价格数组)
|
||||
↓
|
||||
__OnTick__()
|
||||
↓
|
||||
[a] 品种识别 → 切换 dword_6B59710/14/18/1C/20
|
||||
[b] 点差过滤(行 447-455)
|
||||
[c] 复用/重建指标句柄(行 525-532)
|
||||
[d] 读 v68=Upper / v67=Lower / v66=WPR
|
||||
[e] sub_5546348() 扫描持仓,更新 18 个全局缓存变量
|
||||
[f] 复利/固定手数 → dbl_6B59728
|
||||
[g] 时间窗 + 带宽区间 + WPR 极值 → 开仓 OrderSend
|
||||
[h] 4 段平仓逻辑(WPR 反转 + 止盈点)
|
||||
```
|
||||
|
||||
### 2.3 状态转换分析
|
||||
|
||||
EA 运行时主要状态:
|
||||
|
||||
| 状态 | 进入条件 | 退出条件 |
|
||||
|---|---|---|
|
||||
| **空仓观望** | 扫描后 `dword_6B595A0 + dword_6B59598 == 0` | 满足开仓条件 |
|
||||
| **持有多单** | `OrderType==0` 成交 | 主动平仓(4 类触发) |
|
||||
| **持有空单** | `OrderType==1` 成交 | 主动平仓(4 类触发) |
|
||||
| **订单重试中** | `dword_6B59570<0` | 重试 <10 次或成功 |
|
||||
|
||||
**潜在死循环/异常路径**:
|
||||
- **新柱防抖与开仓互斥**: `qword_6B59548 != qword_6B59550` 条件在开仓成功后立即刷新;若 Tick 频率异常高(测试器每柱 tick=全部 ticks)但 iTime 取到的是 K 线开始时间,可能让 `qword_6B59550` 不更新导致**本柱永远不再开仓**——需关注。
|
||||
- **同柱平+开**: 行 824(多单 WPR 反转平仓)与行 570(卖 WPR< -95 开空)的 v66 共享变量,理论上下一个 tick 内可能"平多后立即开空";无显式冷却。
|
||||
- **断点续传**: 终端重启后 `qword_6B59548/558/548/558` 等防抖变量被重置为 0,与 `qword_6B59550/560 = iTime` 不等 → **会立即允许再开一笔**,存在重复建仓风险。
|
||||
- **句柄重建竞争**: 行 526-532 中 `CleanupIndicatorHandles()` 在 OnTick 中执行,但调用 `_GetIndicatorValue_` 前已重建(行 535-537)→ 安全;但若多次 `Volatility` 变化会**泄漏中间态句柄**(实际 cleanup 已正确调用,无泄漏)。
|
||||
|
||||
### 2.4 跨文件冲突
|
||||
|
||||
- 全部代码内联,无 `#include`,**无跨文件冲突**。
|
||||
- 但内部隐式约定:DFCC 内部所有 `static` 函数(OrderTicket/LoadedType/CheckForTradingError)共享静态变量,**与外部全局变量 dword_6B59710 等无命名冲突**。
|
||||
- 真正的"冲突"是**命名空间污染**:50+ 形如 `dword_6B59710` 的全局变量与 11 个 input 重复声明(`__Lots__` / `Lots`),维护时极易混淆。
|
||||
|
||||
### 2.5 数据流分析
|
||||
|
||||
```
|
||||
MT5 报价
|
||||
↓ SymbolInfoDouble(ASK/BID) → Ask/Bid 全局(行 2228-2229, 1204 RefreshRates)
|
||||
↓ SymbolInfoInteger → Bars_FXD, Digits_FXD, Point_FXD
|
||||
↓
|
||||
__OnTick__():
|
||||
↓ iBands(0,1) → v68=Upper ← g_iBands_Upper
|
||||
↓ iBands(0,2) → v67=Lower ← g_iBands_Lower
|
||||
↓ iWPR(0) → v66=WPR ← g_iWPR_Handle
|
||||
↓
|
||||
├─ 时间窗判断 (Hour() 或 TimeGMT)
|
||||
├─ 带宽判断: v68 - v67 ∈ [dword_6B59720*P, dword_6B5971C*P]
|
||||
├─ WPR 极值: v66 < (5-100)=-95 → 多
|
||||
│ v66 > -5 → 空
|
||||
├─ 新柱: qword_6B59550 vs qword_6B59548
|
||||
├─ 持仓: dword_6B595A0 + dword_6B59598 < 1
|
||||
└─ 点差: byte_6B59544 (MarketInfo spread ≤ Pointdifferencelimit)
|
||||
↓ OrderSend(symbol, 0/1, lot, Ask/Bid, slip, SL, 0, ...)
|
||||
↓
|
||||
sub_5546348() // 持仓扫描
|
||||
↓ 累加 dword_6B595A0 (空)/ dword_6B59598 (多)
|
||||
↓ 取最近仓位 OpenPrice → qword_6B595F8/59600
|
||||
↓ 取次新仓位 OpenPrice/Lots/Ticket → qword_6B59690/660 等
|
||||
↓
|
||||
平仓 4 段:
|
||||
├─ 行 824: 多 → WPR > -5(与空开条件对称)
|
||||
├─ 行 868: 空 → WPR < -95(与多开条件对称)
|
||||
├─ 行 911: 多 → Bid - OpenPrice > Profit*P
|
||||
└─ 行 957: 空 → OpenPrice - Ask > Profit*P
|
||||
```
|
||||
|
||||
**数据流问题**:
|
||||
- `qword_6B58FB0 = Point()`(行 42)—— **在全局初始化时只取一次**,符号切换或账户配置变化不会更新;但 `_Point` 是 compile-time 常量,**实际不会变**——可接受。
|
||||
- `qword_6B59590 = MarketInfo("", 13)`(行 220)—— 启动时取的点差,**立即被覆盖**(行 447 每 tick 重读);`qword_6B59590` 在本 EA 中**未被任何代码读取**,是死代码。
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键代码片段分析
|
||||
|
||||
### 3.1 初始化逻辑
|
||||
|
||||
```mql5
|
||||
// OnInit() 行 159-430:99% 注释,仅 220 行一行有效
|
||||
qword_6B59590 = DFCC::MarketInfo("", 13); // 返回最后报价时间整数
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 几乎无初始化工作,所有"实质性初始化"(指标句柄、品种参数、复利手数)都放在 OnTick 第一次执行时。
|
||||
- 这种"延迟初始化"模式在策略 EA 中可接受,但**指标句柄应在 OnInit 中建好**——目前依赖 OnTick 中的 `g_iBands_Upper == INVALID_HANDLE` 守卫条件,逻辑正确但不规范。
|
||||
- 220 行 `MarketInfo("", 13)`(MODE_SPREAD=13)将 spread 存入 `qword_6B59590`,但该变量**全文未被读取**——是反编译器残留的初始化赋值,**死代码**。
|
||||
|
||||
### 3.2 核心交易逻辑
|
||||
|
||||
```mql5
|
||||
// __OnTick__() 行 438-982 - 核心决策
|
||||
void __OnTick__()
|
||||
{
|
||||
// 1) 点差过滤
|
||||
if (DFCC::MarketInfo("", 13) <= Pointdifferencelimit) byte_6B59544 = 1;
|
||||
else byte_6B59544 = 0;
|
||||
|
||||
// 2) 品种参数切换 (55 行 if-else if 链)
|
||||
if (ParameterSwitching) { ... }
|
||||
else { /* 5 个 StringFind 分支 */ }
|
||||
|
||||
// 3) 指标句柄管理
|
||||
if (g_iBands_Upper == INVALID_HANDLE || g_Volatility != Volatility) { ... }
|
||||
v68 = DFCC::_GetIndicatorValue_(g_iBands_Upper, 1, 0);
|
||||
v67 = DFCC::_GetIndicatorValue_(g_iBands_Lower, 2, 0);
|
||||
v66 = DFCC::_GetIndicatorValue_(g_iWPR_Handle, 0, 0);
|
||||
|
||||
// 4) 时间窗 + 带宽 + WPR 极值 + 单仓约束 → 4 段几乎对称的开仓块
|
||||
// 5) 4 段平仓块(多/空 × WPR反转/止盈)
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 整个 `__OnTick__` 函数 540+ 行,**未抽取任何子函数**——典型的"反编译产物"。
|
||||
- 4 段开仓逻辑 + 4 段平仓逻辑**结构高度对称**(测试模式×实盘模式 × 多×空),共 8 段近重复 if 块,应封装为 `TryOpenBuy()/TryOpenSell()/TryCloseBuy()/TryCloseSell()`。
|
||||
- "测试模式"(行 553-684)与"实盘模式"(行 686-815)只有时间窗不同;条件体几乎逐行复制。
|
||||
- `v66`(WPR)变量在同一次 tick 中**既用于开仓判断又用于平仓判断**,平仓与开仓可能同 tick 触发。
|
||||
|
||||
### 3.3 事件处理逻辑
|
||||
|
||||
- **不存在** `OnTrade()` / `OnTradeTransaction()`。
|
||||
- 订单成交确认依赖**轮询式**扫描(`sub_5546348()` 在每个 tick 重算 `dword_6B595A0/dword_6B59598`),**有最多 1 个 tick 的状态滞后**。
|
||||
- `OrderSend` 的重试机制为 do-while 循环,最多 10 次(行 600-624, 655-678, 729-751, 785-807)—— 4 段开仓**各自实现**了相同的重试逻辑。
|
||||
|
||||
### 3.4 资源释放逻辑
|
||||
|
||||
```mql5
|
||||
// OnDeinit() 行 1197-1200 - 资源释放
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
CleanupIndicatorHandles();
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 正确释放 3 个指标句柄。
|
||||
- **未释放**: 任何动态对象、文件句柄、定时器、图形对象——本 EA 几乎没有这些资源,可接受。
|
||||
- **未保存任何状态**:终端重启后所有"防重入变量"清零,存在重启后立即同向加仓风险。
|
||||
|
||||
---
|
||||
|
||||
## 4. 整体结构判断总结
|
||||
|
||||
| 维度 | 评价 |
|
||||
|---|---|
|
||||
| **策略清晰度** | 核心思想(布林带带宽 + WPR 极值 + 单仓反转)可识别,但被 540 行重复代码淹没 |
|
||||
| **代码风格** | 反编译器十六进制变量名主导,**几乎无法人工阅读** |
|
||||
| **状态管理** | 依赖 50+ 全局变量互相同步,缺乏显式状态机 |
|
||||
| **错误恢复** | 仅有 OrderSend 重试,无 OnTrade 事件、无断点续传保护 |
|
||||
| **资源管理** | 句柄已用 cleanup 模式管理,但 iBands 双句柄浪费 |
|
||||
| **多品种** | 5 个品种硬编码参数表,**Symbol() 字符串匹配**而非 enum |
|
||||
@@ -0,0 +1,286 @@
|
||||
# MQL5代码审查报告 - 第三阶段(可视化图表)
|
||||
|
||||
---
|
||||
|
||||
## 1. 程序运行状态机图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> OnInit
|
||||
OnInit --> WaitingForTick : 完成(实际为空壳)
|
||||
WaitingForTick --> SpreadCheck : OnTick触发
|
||||
SpreadCheck --> SymbolParamSelect : spread正常
|
||||
SpreadCheck --> WaitingForTick : spread过大
|
||||
SymbolParamSelect --> HandleInit : 选好品种参数
|
||||
HandleInit --> IndicatorRead : 句柄ready
|
||||
HandleInit --> WaitingForTick : 句柄创建失败
|
||||
IndicatorRead --> PositionScan : 读v66/v67/v68
|
||||
PositionScan --> TimeWindow : sub_5546348完成
|
||||
TimeWindow --> BandWidthCheck : 时间窗内
|
||||
TimeWindow --> PositionClose : 时间窗外
|
||||
BandWidthCheck --> WPRExtreme : 带宽dword_6B59720P<v68-v67<dword_6B5971CP
|
||||
BandWidthCheck --> PositionClose : 带宽越界
|
||||
WPRExtreme --> NoPosition : WPR<-95 (BUY)或>-5 (SELL)
|
||||
NoPosition --> NewBarCheck : 无持仓+dword_6B59544
|
||||
NewBarCheck --> OpenBuy : WPR<-95
|
||||
NewBarCheck --> OpenSell : WPR>-5
|
||||
NewBarCheck --> PositionClose : 已有持仓或本柱已开
|
||||
OpenBuy --> OrderSendLoop : 设置SL=Ask-dword_6B59714*P
|
||||
OpenSell --> OrderSendLoop : 设置SL=Bid+dword_6B59714*P
|
||||
OrderSendLoop --> OpenBuy : 重试<10次失败
|
||||
OrderSendLoop --> WaitingForTick : 下单成功
|
||||
PositionClose --> CloseBuyWPR : 多仓+WPR反转(v66>-5)
|
||||
PositionClose --> CloseBuyProfit : 多仓+Bid-Open>Profit*P
|
||||
PositionClose --> CloseSellWPR : 空仓+WPR反转(v66<-95)
|
||||
PositionClose --> CloseSellProfit : 空仓+Open-Ask>Profit*P
|
||||
PositionClose --> WaitingForTick : 平仓完成
|
||||
WaitingForTick --> [*] : OnDeinit
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- **正常路径**: 等待tick → 8 个串联条件(spread→品种→句柄→指标→时间→带宽→WPR→新柱)→ 开仓/平仓 → 回到等待
|
||||
- **异常/旁路**:
|
||||
- `OpenBuy → OrderSendLoop → OpenBuy` 自循环(重试<10次)
|
||||
- 4 段平仓逻辑**互不互斥**(WPR反转与止盈可同tick触发)
|
||||
- **死状态**: 句柄创建失败后回到 `WaitingForTick`,但**没有计数器**,理论可能无限重试创建——但 fxDreema 转换的 iBands/iWPR 不太可能创建失败,实操风险低。
|
||||
|
||||
---
|
||||
|
||||
## 2. 策略逻辑流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([OnTick触发]) --> Refresh[DFCC::RefreshRates<br/>更新Ask/Bid/Bars]
|
||||
Refresh --> Spread{spread ≤ Pointdifferencelimit?}
|
||||
Spread -->|否| End1([返回])
|
||||
Spread -->|是| SpreadOK[byte_6B59544=1]
|
||||
SpreadOK --> SymbolSwitch{Symbol参数切换}
|
||||
SymbolSwitch -->|GBPCAD| P1[Vol=110 SL=800 TP=300]
|
||||
SymbolSwitch -->|EURSGD| P2[Vol=140 SL=700 TP=160]
|
||||
SymbolSwitch -->|GBPCHF| P3[Vol=110 SL=600 TP=200]
|
||||
SymbolSwitch -->|CHFSGD| P4[Vol=60 SL=700 TP=160]
|
||||
SymbolSwitch -->|GBPSGD| P5[Vol=35 SL=700 TP=160]
|
||||
SymbolSwitch -->|其他| P0[用input默认]
|
||||
P1 & P2 & P3 & P4 & P5 & P0 --> HandleCheck{句柄ready?}
|
||||
HandleCheck -->|否| BuildH[创建iBands+iWPR]
|
||||
BuildH --> Calc
|
||||
HandleCheck -->|是| Calc[读v68=Upper v67=Lower v66=WPR]
|
||||
Calc --> Scan[sub_5546348<br/>扫描持仓计数]
|
||||
Scan --> Lot{Compoundinterest?}
|
||||
Lot -->|是| LotComp[equity*Risk/1e6 round 2位]
|
||||
Lot -->|否| LotFix[Lots=0.1]
|
||||
LotComp & LotFix --> TimeGate{IsTesting?}
|
||||
TimeGate -->|是| TimeTest[Hour∈20-24或0-3]
|
||||
TimeGate -->|否| TimeLive[TimeGMT∈18-1]
|
||||
TimeTest & TimeLive --> Band[带宽检查<br/>v68-v67 ∈ dword_6B59720P, dword_6B5971CP]
|
||||
Band -->|带宽越界| CloseCheck
|
||||
Band -->|带宽ok| Pos{多+空总数<1?}
|
||||
Pos -->|否| CloseCheck
|
||||
Pos -->|是| SpreadAct[byte_6B59544==1?]
|
||||
SpreadAct -->|否| CloseCheck
|
||||
SpreadAct -->|是| WPRExtreme{v66<-95?}
|
||||
WPRExtreme -->|是| NewBar1{qword_6B59548≠iTime?}
|
||||
NewBar1 -->|是| Buy[OrderSend BUY<br/>SL=Ask-dword_6B59714*P]
|
||||
NewBar1 -->|否| CloseCheck
|
||||
WPRExtreme -->|否| WPRExtreme2{v66>-5?}
|
||||
WPRExtreme2 -->|是| NewBar2{qword_6B59558≠iTime?}
|
||||
NewBar2 -->|是| Sell[OrderSend SELL<br/>SL=Bid+dword_6B59714*P]
|
||||
NewBar2 -->|否| CloseCheck
|
||||
WPRExtreme2 -->|否| CloseCheck
|
||||
Buy & Sell --> CloseCheck
|
||||
CloseCheck{持有多?}
|
||||
CloseCheck -->|是| WPRClose1{v66>-5?}
|
||||
WPRClose1 -->|是| DoCloseBuy1[OrderClose多]
|
||||
CloseCheck -->|否| CloseCheck2
|
||||
WPRClose1 -->|否| ProfitClose1{Bid-Open>ProfitP?}
|
||||
ProfitClose1 -->|是| DoCloseBuy2[OrderClose多]
|
||||
WPRClose1 & ProfitClose1 -->|否| CloseCheck2
|
||||
CloseCheck2{持有空?}
|
||||
CloseCheck2 -->|是| WPRClose2{v66<-95?}
|
||||
WPRClose2 -->|是| DoCloseSell1[OrderClose空]
|
||||
CloseCheck2 -->|否| End2
|
||||
WPRClose2 -->|否| ProfitClose2{Open-Ask>ProfitP?}
|
||||
ProfitClose2 -->|是| DoCloseSell2[OrderClose空]
|
||||
WPRClose2 & ProfitClose2 -->|否| End2
|
||||
DoCloseBuy1 & DoCloseBuy2 & DoCloseSell1 & DoCloseSell2 --> End2([返回])
|
||||
End1 & End2 --> WaitNext[等下一tick]
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- **核心决策点**: 8 个串联(spread / 品种 / 句柄 / 指标 / 时间 / 带宽 / 持仓 / WPR),任一不满足即跳过开仓
|
||||
- **平仓双重触发**: WPR反转(v66>-5/-95)作为反转信号、Profit*P 作为目标止盈;**两个条件可同tick都满足**,但 OrderClose 内部会因仓位已平而失败
|
||||
- **新柱防重入**: `qword_6B59548 != qword_6B59550`(多通道)/ `qword_6B59558 != qword_6B59560`(空通道),通过 iTime 比较
|
||||
- **资金管理**: 复利开关开启时 → `equity*40/1e6`;关闭时固定 0.1 手
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心类图
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class TopBottomEA {
|
||||
+input double __Lots__ = 0.1
|
||||
+input bool __Compoundinterestswitch__
|
||||
+input int __Risk__ = 40
|
||||
+input int __Volatility__ = 110
|
||||
+input int __StopLoss__ = 800
|
||||
+input int __Profit__ = 300
|
||||
+input double __Pointdifferencelimit__ = 50
|
||||
+input int __Magic__ = 888999
|
||||
+__OnTick__() void
|
||||
+OnInit() void
|
||||
+OnDeinit(reason) void
|
||||
+sub_5546348() int
|
||||
}
|
||||
class DFCC {
|
||||
-static int _LastError
|
||||
+AccountEquity() double
|
||||
+AccountFreeMarginCheck() double
|
||||
+Hour() int
|
||||
+IsTesting() bool
|
||||
+MarketInfo() double
|
||||
+OrderClose() bool
|
||||
+OrderClosePrice() double
|
||||
+OrderCommission() double
|
||||
+OrderLots() double
|
||||
+OrderMagicNumber() long
|
||||
+OrderOpenPrice() double
|
||||
+OrderProfit() double
|
||||
+OrderSelect() bool
|
||||
+OrderSend() int
|
||||
+OrderStopLoss() double
|
||||
+OrderSwap() double
|
||||
+OrderSymbol() string
|
||||
+OrderTakeProfit() double
|
||||
+OrderTicket() int
|
||||
+OrderType() int
|
||||
+OrdersTotal() int
|
||||
+RefreshRates() bool
|
||||
+TimeHour() int
|
||||
+iBands() double
|
||||
+iTime() datetime
|
||||
+iWPR() double
|
||||
-_GetIndicatorValue_() double
|
||||
-_ConvertAppliedPrice_() ENUM_APPLIED_PRICE
|
||||
-_ConvertTimeframe_() ENUM_TIMEFRAMES
|
||||
}
|
||||
class DFCC_TRADES {
|
||||
+CheckForTradingError() int
|
||||
+IsExpirationTypeAllowed() bool
|
||||
+IsFillingTypeAllowed() bool
|
||||
+LoadPendingOrder() bool
|
||||
+LoadedType() int
|
||||
}
|
||||
class IndicatorCache {
|
||||
+int g_iBands_Upper
|
||||
+int g_iBands_Lower
|
||||
+int g_iWPR_Handle
|
||||
+int g_Volatility
|
||||
+CleanupIndicatorHandles() void
|
||||
}
|
||||
class GlobalState {
|
||||
+byte_6B59544 bool // spread flag
|
||||
+dword_6B595A0 int // sell count
|
||||
+dword_6B59598 int // buy count
|
||||
+qword_6B595F8 double // last buy open
|
||||
+qword_6B59600 double // last sell open
|
||||
+qword_6B59548 datetime // last buy bar
|
||||
+qword_6B59558 datetime // last sell bar
|
||||
+qword_6B59550 datetime // current bar
|
||||
+dword_6B59710 int // vol override
|
||||
+dword_6B59714 int // SL override
|
||||
+dword_6B59718 int // TP override
|
||||
}
|
||||
TopBottomEA --> DFCC : 大量调用
|
||||
TopBottomEA --> DFCC_TRADES : 间接(经DFCC)
|
||||
TopBottomEA --> IndicatorCache : 创建/释放
|
||||
TopBottomEA --> GlobalState : 读写50+全局
|
||||
DFCC --> DFCC_TRADES : 错误处理
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `DFCC` 类是 MQL4 API 的**完全仿真层**(fxDreema 工具生成),把 MQL5 原生 API 重新包装成 MQL4 风格的静态方法
|
||||
- `GlobalState` 在 UML 中以"上帝对象"形式存在——50+ `dword_/qword_/byte_6B5xxxx` 全局变量承担所有状态管理
|
||||
- 没有任何"策略类"抽象(无 `CSignalGenerator / CRiskManager / CPositionManager`),所有逻辑平铺在 `__OnTick__` 的 540 行中
|
||||
- `IndicatorCache` 是个**隐式类**(用全局变量+全局函数实现),应在 `OnInit` 中创建、`OnDeinit` 中释放——目前放在 `OnTick` 中懒创建
|
||||
|
||||
---
|
||||
|
||||
## 4. 交互时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as MetaTrader 5
|
||||
participant EA as TopBottomEA
|
||||
participant C as DFCC::RefreshRates
|
||||
participant I as IndicatorCache
|
||||
participant S as GlobalState
|
||||
participant SC as sub_5546348
|
||||
participant O as DFCC::OrderSend
|
||||
participant OC as DFCC::OrderClose
|
||||
|
||||
T->>EA: OnTick(new tick)
|
||||
EA->>C: RefreshRates()
|
||||
C->>T: SymbolInfoDouble/SymbolInfoInteger
|
||||
T-->>C: Ask, Bid, Bars
|
||||
C-->>EA: true (data updated)
|
||||
|
||||
EA->>EA: spread check<br/>(byte_6B59544)
|
||||
EA->>EA: symbol param switch<br/>(dword_6B59710/14/18)
|
||||
|
||||
EA->>I: handle ready?
|
||||
I-->>EA: yes / 创建句柄
|
||||
|
||||
EA->>T: iBands(upper) iBands(lower) iWPR
|
||||
T-->>EA: v68, v67, v66
|
||||
|
||||
EA->>SC: 扫描持仓
|
||||
SC->>T: OrdersTotal + OrderSelect循环
|
||||
T-->>SC: 各订单数据
|
||||
SC->>S: 写入dword_6B595A0/598/qword_6B595F8/600等
|
||||
SC-->>EA: 完成
|
||||
|
||||
EA->>S: 读qword_6B595A0+598<1?
|
||||
S-->>EA: 是
|
||||
EA->>EA: 带宽检查 v68-v67
|
||||
EA->>EA: 时间窗检查 Hour/TimeGMT
|
||||
EA->>EA: WPR极值 v66
|
||||
|
||||
alt 开多条件满足
|
||||
EA->>S: 更新qword_6B59548=qword_6B59550
|
||||
EA->>O: OrderSend(BUY, lot, Ask, 30slip, SL=Ask-dword_6B59714*P)
|
||||
loop 重试<10
|
||||
O->>T: OrderSend(DEAL)
|
||||
T-->>O: retcode
|
||||
end
|
||||
O-->>EA: ticket
|
||||
else 开空条件满足
|
||||
EA->>S: 更新qword_6B59558=qword_6B59560
|
||||
EA->>O: OrderSend(SELL, lot, Bid, 30slip, SL=Bid+dword_6B59714*P)
|
||||
loop 重试<10
|
||||
O->>T: OrderSend(DEAL)
|
||||
T-->>O: retcode
|
||||
end
|
||||
O-->>EA: ticket
|
||||
end
|
||||
|
||||
alt 持有多+反向WPR或Profit目标
|
||||
EA->>OC: OrderClose(ticket, lots, closeprice, 300slip)
|
||||
OC->>T: OrderSend(DEAL reverse)
|
||||
T-->>OC: retcode
|
||||
OC-->>EA: bool
|
||||
else 持有空+反向WPR或Profit目标
|
||||
EA->>OC: OrderClose(ticket, lots, closeprice, 300slip)
|
||||
OC->>T: OrderSend(DEAL reverse)
|
||||
T-->>OC: retcode
|
||||
OC-->>EA: bool
|
||||
end
|
||||
|
||||
EA-->>T: 返回
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- **典型周期**: 1 个 tick 触发 1 次 OnTick → 4~5 次同步 MT5 API 调用 → 1 次持仓扫描 → 0~1 次开仓 + 0~1 次平仓
|
||||
- **异步点**: DFCC::OrderSend/OrderClose 内部有 `Sleep(10ms)` 轮询等待 MT5 状态同步(行 1974/2015),一次订单最多阻塞 ~200ms(10ms × 20次重试)
|
||||
- **无 OnTrade 回调**: 成交状态完全靠 sub_5546348 的轮询式扫描
|
||||
- **关键数据流**: v66(WPR) 同时驱动开仓和平仓判断,**无冷却时间**——理论上 tick 1 触发平多,tick 2 同一价格触发开空
|
||||
@@ -0,0 +1,955 @@
|
||||
# MQL5代码审查报告 - 第四阶段(最佳实践分析)
|
||||
|
||||
---
|
||||
|
||||
## 4.1 代码规范与可读性
|
||||
|
||||
### 4.1.1 命名规范
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 41–143 行 - 全局变量声明
|
||||
double qword_6B59510 = 1000000;
|
||||
int dword_6B59520 = 1000;
|
||||
int dword_6B59524 = 150;
|
||||
int dword_6B59528 = 5;
|
||||
...
|
||||
double qword_6B595F8 = 0;
|
||||
double qword_6B59600 = 0;
|
||||
...
|
||||
bool byte_6B59544 = 1;
|
||||
bool byte_6B596D8 = 1;
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 19, 21, 23... 行 - input 重复声明
|
||||
input double __Lots__ = 0.1; // 6B594B0
|
||||
double Lots = __Lots__;
|
||||
input bool __Compoundinterestswitch__ = false;
|
||||
bool Compoundinterestswitch = __Compoundinterestswitch__;
|
||||
input int __Risk__ = 40;
|
||||
int Risk = __Risk__;
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 602 行 - 函数内变量
|
||||
DFCC::OrderSend(
|
||||
Symbol(), // symbol
|
||||
0, // operation
|
||||
dbl_6B59728, // 开仓量
|
||||
Ask, // 开仓价
|
||||
dword_6B59540, // 滑点
|
||||
qword_6B59578, // 止损
|
||||
qword_6B59580, // 止盈
|
||||
CommentName,
|
||||
Magic,
|
||||
0,
|
||||
16711680); // 颜色
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 反编译器残留的**十六进制地址命名**(`dword_6B59710`),完全丧失语义
|
||||
- 每个 input 变量被**重复声明**两次(`__Lots__` 与 `Lots`),浪费 22 行且易造成修改遗漏
|
||||
- 命名风格**三套混用**:匈牙利反编译器风格(`dword_`)、input 风格(`__Lots__`)、缩写拼音(`Lots`/`Risk`),无可读性
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可维护性**: 任何维护者无法从变量名推断用途,必须靠反编译文档或注释(行末的 `// 6B594B0` 提示原地址)—— 是反编译产物的标志
|
||||
- **逻辑**: 实际不影响运行(MQL5 允许任意命名),但 `Lots` 与 `__Lots__` 的 double 隐式转换在调试时会引起歧义
|
||||
- **审计风险**: 第三方审查者无法快速确认"止损用的是 StopLoss 还是 dword_6B59714"
|
||||
|
||||
---
|
||||
|
||||
### 4.1.2 代码注释
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 159-430 行 - OnInit 大量注释掉的代码
|
||||
/*
|
||||
if ( Displayswitch == 1 )
|
||||
{
|
||||
sub_554698E(0, 0, 84520750, 0, 25, 15, 150, 60, 0, 84520770, v37, 10, 16443110, 3947580, 0, 0, 0, 0, 0, 0, 0);
|
||||
...
|
||||
ObjectSetInteger(0, v23, 102, 30, 0);
|
||||
...
|
||||
}*/
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 451-453 行 - 中文注释保留自原作者
|
||||
// qword_6B58FF4 = dword_6B5971C * qword_6B58FB0;
|
||||
// qword_6B58FF4 =dword_6B59720 * qword_6B58FB0;
|
||||
|
||||
//AccountFreeMarginCheck 检查可用保证金
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 270+ 行 `OnInit` 中 99% 是被 `/* */` 注释掉的 MQL4 图表对象创建代码(MQL4 的 `ObjectSetInteger` 调用)
|
||||
- 注释中混用**反编译的地址数字**(`84520750`、`84520810`)和**真实业务注释**("开仓量"、"开仓价"、"滑点"),可读性混乱
|
||||
- 翻译注释("多单平仓1"、"多单平仓2")仅是平仓触发条件不同时的区分,**缺乏说明为何分两段**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可维护性**: 大量死代码膨胀文件,IDE 索引耗时;维护时需小心不要"启用"这些注释
|
||||
- **编译期**: 注释代码不会被编译,**0 编译影响**
|
||||
- **运行期**: 注释中 `ObjectSetString` 等 MQL4 语法在 MQL5 中已改变(如 MQL5 用 `ObjectSetString(0, name, OBJPROP_TEXT, value)`),即使取消注释也**无法编译通过**——是定时炸弹
|
||||
|
||||
---
|
||||
|
||||
### 4.1.3 魔术数字
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 44-52 行 - 魔术数字常量
|
||||
int dword_6B59528 = 5; // WPR 阈值 = 5 → 条件 v66 < -95
|
||||
int dword_6B5952C = 5; // WPR 平仓阈值
|
||||
int dword_6B59530 = 20; // 时间窗起点(小时)
|
||||
int dword_6B59534 = 24; // 时间窗终点
|
||||
int dword_6B59538 = 0;
|
||||
int dword_6B5953C = 3; // 第二时间窗终点
|
||||
int dword_6B59540 = 30; // 开仓滑点
|
||||
int dword_6B59520 = 1000; // 带宽上限
|
||||
int dword_6B59524 = 150; // 带宽下限
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 529-530 行 - iBands 硬编码参数
|
||||
g_iBands_Upper = ::iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
|
||||
g_iBands_Lower = ::iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
|
||||
g_iWPR_Handle = ::iWPR(_Symbol, PERIOD_CURRENT, g_Volatility);
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 849, 891, 936, 973 行 - OrderClose 硬编码
|
||||
DFCC::OrderClose(DFCC::OrderTicket(), DFCC::OrderLots(), DFCC::OrderClosePrice(), 300, 0xFFFF);
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 616, 669, 743, 799 行 - 颜色魔术数字
|
||||
... 16711680); // 红色 clrRed
|
||||
... 255); // 蓝色 clrBlue
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 9 个"内部参数"以**全局变量初始值**形式定义,无法被 input 调整、无法在策略测试器中优化
|
||||
- iBands 的 period=20 / deviation=2.0 写死在 OnTick 中
|
||||
- OrderClose 的 300 slippage、0xFFFF arrow_color 无法配置
|
||||
- 16711680 实际是 `C'0xFF0000'` 红色,但 255 是 `C'0x0000FF'` 蓝色,**含义未注释**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可维护性**: 调参困难——回测时若想测"带宽阈值变化对收益影响"必须改源码
|
||||
- **性能**: 无法做参数优化(Strategy Tester 只识别 input)
|
||||
- **业务**: 300 点 slippage 对 4 位报价 30pips 是正常,对 5 位报价 3pips 偏紧——**该值未根据报价位数自适应**
|
||||
|
||||
---
|
||||
|
||||
### 4.1.4 代码复用
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 553-684 行 vs 第 686-815 行 - 测试模式与实盘模式几乎完全相同
|
||||
if (DFCC::IsTesting() == 1)
|
||||
{
|
||||
if (DFCC::Hour() >= dword_6B59530 && DFCC::Hour() <= dword_6B59534 || DFCC::Hour() >= dword_6B59538 && DFCC::Hour() <= dword_6B5953C)
|
||||
{
|
||||
if (v68-v67 < dword_6B5971C*qword_6B58FB0) {
|
||||
if (v68-v67 > dword_6B59720 * qword_6B58FB0) {
|
||||
if (dword_6B595A0 + dword_6B59598 < 1 && byte_6B59544 == 1) {
|
||||
if (v66 < dword_6B59528 - 100 && qword_6B59548 != qword_6B59550) {
|
||||
// 开多 ~50 行
|
||||
}
|
||||
}
|
||||
if (dword_6B595A0 + dword_6B59598 < 1 && byte_6B59544 == 1) {
|
||||
if (v66 > -dword_6B59528 && qword_6B59558 != qword_6B59560) {
|
||||
// 开空 ~50 行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DFCC::TimeHour(TimeGMT()) >= 18 || DFCC::TimeHour(TimeGMT()) <= 1) {
|
||||
if (v68-v67 < dword_6B5971C*qword_6B58FB0) {
|
||||
// 与上面 100% 相同结构 ~130 行
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 818-897 行 - 平仓WPR反转(多+空几乎复制)
|
||||
if (dword_6B59598 > 0 && byte_6B59544 == 1) {
|
||||
if (v66 > (-dword_6B5952C)) {
|
||||
for (int i = ...) { ... close buy ... }
|
||||
}
|
||||
}
|
||||
if (dword_6B595A0 > 0 && byte_6B59544 == 1) {
|
||||
if (v66 < dword_6B5952C - 100) {
|
||||
for (int j = ...) { ... close sell ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 899-979 行 - 止盈平仓(多+空几乎复制)
|
||||
if (dword_6B59598 > 0 && byte_6B59544 == 1) {
|
||||
if (Bid - qword_6B595F8 > dword_6B59718 * qword_6B58FB0) {
|
||||
for (int k = ...) { ... }
|
||||
}
|
||||
}
|
||||
if (dword_6B595A0 > 0 && byte_6B59544 == 1) {
|
||||
if (qword_6B59600 - Ask > dword_6B59718 * qword_6B58FB0) {
|
||||
for (int m = ...) { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 8 段近重复的 if 块(4 段开仓 × 2 模式 / 4 段平仓 × 2 触发条件)共 ~400 行,**结构对称度 95%**:
|
||||
- 4 段开仓:仅方向/阈值不同
|
||||
- 4 段平仓:仅多空+条件不同
|
||||
- 全部应封装为 `TryOpen(ENUM_ORDER_TYPE dir)` / `TryClose(ENUM_ORDER_TYPE dir, CLOSE_REASON reason)`
|
||||
- 测试/实盘模式只是时间窗不同,外层应抽出 `GetTimeHour()` 函数
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可维护性**: 任何逻辑修改(如改止损计算、加追踪止损)必须改 4~8 处,**必然遗漏**
|
||||
- **代码体积**: 当前 2574 行,若重构可压缩到 ~1500 行
|
||||
- **bug 风险**: 实测行 824 处的 `dword_6B59598 > 0` 复用了多仓计数变量,行 868 处的 `dword_6B595A0 > 0` 复用了空仓计数变量——对称但**容易在复制时搞反**(已发生:行 870 `if (v66 < dword_6B5952C - 100)` 闭区间 vs 行 911 等是开区间,但混在一起阅读极易出错)
|
||||
|
||||
---
|
||||
|
||||
### 4.1.5 文件组织
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 1210-2436 行 - DFCC 类 1200+ 行内联
|
||||
class DFCC {
|
||||
// ... 2000 行
|
||||
};
|
||||
class DFCC_TRADES {
|
||||
// ... 200 行
|
||||
};
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- DFCC 类(2000+ 行)作为**通用 MQL4→MQL5 兼容层**,本应独立为 `fxdreema_converter.mqh` 头文件,被任何移植项目共享
|
||||
- 当前内联在主 EA 中,导致核心策略(~700 行)只占文件 27%,其余 73% 是通用兼容代码——**关注点严重不分离**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可维护性**: 升级 DFCC 库需要修改 EA 文件
|
||||
- **复用**: 其他 fxDreema 移植的 EA 无法共享这份代码
|
||||
- **审查成本**: 审查者必须穿透 2000 行无关代码才能定位策略
|
||||
|
||||
---
|
||||
|
||||
## 4.2 交易逻辑正确性
|
||||
|
||||
### 4.2.1 信号闪烁(Repainting)
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 535-537 行 - 指标读取使用 shift=0(当前未完成K线)
|
||||
v68 = DFCC::_GetIndicatorValue_(g_iBands_Upper, 1, 0); // Upper band at bar 0
|
||||
v67 = DFCC::_GetIndicatorValue_(g_iBands_Lower, 2, 0); // Lower band at bar 0
|
||||
v66 = DFCC::_GetIndicatorValue_(g_iWPR_Handle, 0, 0); // WPR at bar 0
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 所有指标都用 `shift=0`,即**当前正在形成的K线**。K 线未收盘时 WPR/布林带**会随新 tick 不断重算**。
|
||||
- 实盘中:开仓信号可能在 tick 1 出现 → 500ms 后 K 线收盘时 WPR 已经回到正常区域 → 信号消失。**实盘会"漏掉"信号或成交后立即反向**。
|
||||
- 回测中:测试器在每根 K 线收盘时**仍可访问 shift=0 的最终值**,这意味着回测用的是 K 线收盘后的"未来" WPR。**回测结果虚高**。
|
||||
|
||||
**🔍 影响范围**
|
||||
- **回测 vs 实盘**: 严重背离。实盘开仓数可能比回测少 30-50%
|
||||
- **信号可靠性**: WPR 在 K 线未收盘时的极值(<-95 / >-5)比收盘后极值**更极端**,EA 实际上"在最有利的瞬间"成交,但**成交价不一定有利**
|
||||
- **建议方向**: 信号触发应改用 `shift=1`(已收盘K线)并加新柱检测——但本 EA 新柱检测只用 iTime 而非指标周期,**不能完全避免**
|
||||
|
||||
---
|
||||
|
||||
### 4.2.2 未来函数(Look-ahead Bias)
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 535-537 行(同上) + 第 540-541 行
|
||||
qword_6B59550 = DFCC::iTime("", 1, 1); // bar 1
|
||||
qword_6B59560 = DFCC::iTime("", 1, 1); // bar 1
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- iTime 用 shift=1(已收盘K线),是正确的
|
||||
- 但 WPR/BB 仍用 shift=0,**回测时 shift=0 的值是"已知未来"**(MT5 测试器在 tick 模拟时假定 K 线收盘后指标值已知)
|
||||
- MQL5 中 `iBands(handle, mode, 0, 1)` 在 K 线未收盘时返回**当前已知最高/最低价计算的临时值**,这个值会随 K 线进行**变化**——是 repainting 的根源
|
||||
|
||||
**🔍 影响范围**
|
||||
- **回测收益虚高**: 这是 MQL5 中典型的"未来函数"陷阱,回测报告的 Sharpe / Profit Factor 不可信
|
||||
- **实盘表现**: 当 WPR 极值条件在 K 线中间触发后,下一个 tick 不再满足条件 → 错过入场
|
||||
|
||||
---
|
||||
|
||||
### 4.2.3 多品种处理
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 460, 480-519 行 - 用 StringFind 识别品种
|
||||
if (StringFind(Symbol(), "CHFSGD", 0) < 0 && StringFind(Symbol(), "GBPSGD", 0) < 0)
|
||||
if (StringFind(Symbol(), "GBPCAD", 0) >= 0) { dword_6B59710 = 110; ... }
|
||||
if (StringFind(Symbol(), "EURSGD", 0) >= 0) { ... }
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 用**字符串子串匹配**识别品种,**极易出错**:
|
||||
- `GBPCAD` 也会匹配到不存在的 `GBPCADOT` 等变种名
|
||||
- 子串不区分大小写(在某些 MT5 版本中)
|
||||
- 增加新品种需要改源码
|
||||
- 魔术号 `888999` 在所有品种间**共享**——若把 EA 拖到多张图表(GBPCAD + EURSGD),不同品种的仓位会被混合统计 `dword_6B595A0 + dword_6B59598 < 1` 的判断将**全部品种视为同一仓**——可能造成在 GBPCAD 已有仓时仍尝试在 EURSGD 反向开仓,且**违反"单笔持仓"约束**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **多品种运行**: 完全不安全。EA 设计假设单品种单仓
|
||||
- **字符串匹配**: 不规范的经纪商 symbol 命名(带 `.` `,` `_`)可能导致误判
|
||||
- **可维护性**: 5 个品种硬编码,加新品种需改源码并重编译
|
||||
|
||||
---
|
||||
|
||||
### 4.2.4 时间过滤
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 555, 690 行 - 复杂的时间窗口条件
|
||||
if (DFCC::Hour() >= dword_6B59530 && DFCC::Hour() <= dword_6B59534
|
||||
|| DFCC::Hour() >= dword_6B59538 && DFCC::Hour() <= dword_6B5953C)
|
||||
// 即: (20-24) || (0-3) → 实际是 20:00 到次日 03:00
|
||||
|
||||
if (DFCC::TimeHour(TimeGMT()) >= 18 || DFCC::TimeHour(TimeGMT()) <= 1)
|
||||
// 即: GMT 18:00-次日 01:00(用 || 而非 &&,含义不清晰)
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 测试模式用 `Hour()`(**服务器时间**),实盘用 `TimeGMT()`(GMT)—— 两个时基混用,回测/实盘行为不一致
|
||||
- 第一个时间窗条件 `||` 缺少括号,按优先级 `(A && B) || (C && D)` 解析——**逻辑正确但极易误读**为 `(A && B && C) || D`
|
||||
- 第二个时间窗 `>= 18 || <= 1` 实际覆盖全天所有小时(18-23 或 0-1)—— **OR 的两段不是分时段,而是大多数时段**!意图可能是 18-次日 1 的连续窗口,但写法错误
|
||||
|
||||
**🔍 影响范围**
|
||||
- **行为**: 实盘"时间窗过滤"几乎不生效(GMT 18:00 到次日 01:00 之外也被允许?需重新解读)
|
||||
- **时基混乱**: 测试与实盘时基不同,回测时区设置会改变行为
|
||||
- **周末/节假日**: 无显式处理,依赖服务器的"市场关闭"自动拒绝订单
|
||||
|
||||
---
|
||||
|
||||
### 4.2.5 滑点与重报价
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 607, 662, 736, 792 行 - 开仓滑点
|
||||
DFCC::OrderSend(..., dword_6B59540, ...); // dword_6B59540 = 30
|
||||
|
||||
// 📍 问题位置:第 849, 891, 936, 973 行 - 平仓滑点
|
||||
DFCC::OrderClose(..., 300, ...); // 硬编码 300
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 开仓滑点 30、平仓滑点 300 严重不对称(10倍差距)—— 可能是反编译器对 MQL4 的 `slippage` 参数处理误差
|
||||
- 30 在 5 位报价下 = 0.3pips,**对市价单几乎无要求**;300 在 4 位报价下 = 30pips,**过宽**
|
||||
- DFCC 包装的 OrderSend 内部对 requote/error 有重试,但**没有显式的滑点统计/告警**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **成本**: 过宽的平仓滑点会在高波动时让经纪商以劣于要求价 30pips 平仓
|
||||
- **回测准确性**: Strategy Tester 模拟滑点方式与实盘不同,宽滑点会**掩盖实盘滑点问题**
|
||||
|
||||
---
|
||||
|
||||
## 4.3 订单与仓位管理
|
||||
|
||||
### 4.3.1 订单类型选择
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 602, 657, 731, 787 行 - 全部为市价单(DEAL)
|
||||
DFCC::OrderSend(Symbol(), 0, ...); // OP_BUY = 0
|
||||
DFCC::OrderSend(Symbol(), 1, ...); // OP_SELL = 1
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 只用市价单(DEAL),无挂单(pending order)
|
||||
- 市价单在 EA 中:触发延迟 0,但成交价不可控
|
||||
- 策略意图是"波动率极值反转入场",**实际可能更适合挂限价单**(在 BB 内/外一定距离挂单),减少滑点
|
||||
|
||||
**🔍 影响范围**
|
||||
- **执行质量**: 高波动时段市价单滑点可能 5-20pips
|
||||
- **回测 vs 实盘**: Strategy Tester 的"Every tick" 模式才能模拟市价单的真实成交,否则用"Open prices only" 会极大高估
|
||||
|
||||
---
|
||||
|
||||
### 4.3.2 订单填充模式
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:DFCC::OrderSend 行 1907-1913 - FOK/IOC/RETURN 智能选择
|
||||
if (DFCC_TRADES::IsFillingTypeAllowed(symbol, SYMBOL_FILLING_FOK))
|
||||
request.type_filling = ORDER_FILLING_FOK;
|
||||
else if (DFCC_TRADES::IsFillingTypeAllowed(symbol, SYMBOL_FILLING_IOC))
|
||||
request.type_filling = ORDER_FILLING_IOC;
|
||||
else if (DFCC_TRADES::IsFillingTypeAllowed(symbol, ORDER_FILLING_RETURN))
|
||||
request.type_filling = ORDER_FILLING_RETURN;
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 兼容层正确处理了三种填充模式
|
||||
- 但**对市价单而言优先 FOK/IOC**:在流动性不足时会**整笔被拒**(FOK 全部成交否则取消)—— 而本策略 WPR 极值信号出现时往往伴随流动性降低
|
||||
- **应优先 RETURN 模式**(部分成交即可),但代码选了 FOK 优先
|
||||
|
||||
**🔍 影响范围**
|
||||
- **成交率**: 极端行情 WPR 触及极值时,订单可能因 FOK 部分成交失败而被拒,错过入场
|
||||
|
||||
---
|
||||
|
||||
### 4.3.3 持仓对冲模式
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 567, 630, 702, 757 行 - 单仓约束
|
||||
if (dword_6B595A0 + dword_6B59598 < 1 && byte_6B59544 == 1)
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 限制"多仓数+空仓数 < 1" = 至多 1 个总仓位
|
||||
- **对冲模式下** (RETAIL_HEDGING): 单品种可有 1 个多 + 1 个空,"1 个总仓位"约束**阻止了对冲**(如多空锁仓)
|
||||
- **净值模式下** (RETAIL_NETTING): 同方向开仓会合并,**单仓约束失效**——dword_6B595A0/dword_6B59598 计数可能不准确
|
||||
- 净值模式下,DFCC 包装的 OrderSend 会先 `PositionSelect` 检查是否反仓(行 1826-1834),但本 EA 没意识到"反仓"会导致旧仓被关——**行为不一致**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **净值账户**: 实际持仓可能突破 1 笔(多次加仓合并),与策略设计冲突
|
||||
- **对冲账户**: 想锁仓时被禁止
|
||||
|
||||
---
|
||||
|
||||
### 4.3.4 部分平仓
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 849, 891, 936, 973 行 - 全部用 OrderLots() 全平
|
||||
DFCC::OrderClose(DFCC::OrderTicket(), DFCC::OrderLots(), DFCC::OrderClosePrice(), 300, 0xFFFF);
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 全部用 `OrderLots()` 平仓,即**全平**——无法分批止盈
|
||||
- 净值模式下加仓后 `OrderLots()` 是总手数,**会一平全平**——丢失"分批建仓分批平仓"的灵活性
|
||||
- 策略盈利目标 300 点平仓,但**没有"达到 150 点先平一半保本"等中间管理**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **盈利曲线**: 一次性平仓对滑点敏感(特别在高 spread 时段)
|
||||
- **风险**: 没有部分平仓来锁盈
|
||||
|
||||
---
|
||||
|
||||
### 4.3.5 订单修改限制
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 580, 642, 714, 769 行 - 止损计算
|
||||
qword_6B59578 = NormalizeDouble(Ask-dword_6B59714 *qword_6B58FB0, 5);
|
||||
qword_6B59578 = NormalizeDouble(Bid+dword_6B59714 *qword_6B58FB0, 5);
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- SL = `dword_6B59714 * _Point` = 800 * Point = 80pips (5位) / 800pips (4位)
|
||||
- **未检查 `SYMBOL_TRADE_STOPS_LEVEL`**(经纪商要求的最小止损距离)—— 部分经纪商要求 100pips+,本 EA 800pips 是 80pips,**多数情况下安全**但**未自适应**
|
||||
- **未检查 `SYMBOL_TRADE_FREEZE_LEVEL`**(止损冻结区)—— 在新闻/收盘时设止损可能被拒
|
||||
- SL 用 `NormalizeDouble(..., 5)` 硬编码 5 位—— 4 位报价的品种会被错误归一化(如 GBPJPY=3 位会出现 1.23456789 → 1.23457 但实际应是 1.235)
|
||||
|
||||
**🔍 影响范围**
|
||||
- **订单拒绝**: 某些经纪商在特定时段要求更严的 stops level,未检查会导致 10034 error
|
||||
- **精度错误**: 4 位报价下止损精度异常(小数位过多或过少)
|
||||
|
||||
---
|
||||
|
||||
## 4.4 风险管理
|
||||
|
||||
### 4.4.1 仓位计算
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 546 行 - 复利手数计算
|
||||
dbl_6B59728 = StringToDouble(DoubleToString(DFCC::AccountEquity() * Risk / qword_6B59510, 2));
|
||||
// 即: lots = (equity * 40 / 1_000_000) 截取2位小数
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 587-591 行 - 注释掉的 lot 边界检查
|
||||
/* if (dbl_6B59728 < SymbolInfoDouble(0, 35)) // MODE_MINLOT
|
||||
dbl_6B59728 = SymbolInfoDouble(0, 35);
|
||||
if (dbl_6B59728 > SymbolInfoDouble(0, 34)) // MODE_MAXLOT
|
||||
dbl_6B59728 = SymbolInfoDouble(0, 34); */
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 复利公式 `(equity * 40) / 1,000,000` 含义不清:
|
||||
- $10,000 账户 → 0.4 手
|
||||
- $1,000 账户 → 0.04 手
|
||||
- $100,000 账户 → 4.0 手
|
||||
- **与 StopLoss 完全无关**——没有按"风险金额 / 止损点数"的标准手数公式
|
||||
- `StringToDouble(DoubleToString(..., 2))` 是个**反模式**:先格式化再解析回 double,浪费 CPU 且有精度损失
|
||||
- **未应用最小/最大手数约束**(代码已注释掉)—— 0.04 手低于 SYMBOL_VOLUME_MIN(如 0.1)会被 OrderSend 拒绝
|
||||
- 复利开关关闭时硬编码 0.1 手—— 风险敞口固定,**$1,000 账户和 $100,000 账户用同样手数**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **资金管理失效**: 小账户可能被自动化为"小于最小手"而无法开仓
|
||||
- **大账户风险过大**: 0.1 手对 $100k 账户是 0.1% 风险,但对 $1k 账户是 10% 风险
|
||||
- **复利失控**: 公式中"Risk=40"含义不明确,调整时无依据
|
||||
|
||||
---
|
||||
|
||||
### 4.4.2 最大回撤控制
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:整个 __OnTick__ - 无任何回撤控制
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- **完全没有**:
|
||||
- 单日亏损限制
|
||||
- 最大持仓时长(依赖 StopLoss 兜底)
|
||||
- 最大连续亏损次数暂停
|
||||
- 账户净值低于某值时停止交易
|
||||
- StopLoss 是**唯一**的亏损控制手段——但 80pips (5位) 在 XAUUSD 等品种上可能相当于 0.5-1% 净值
|
||||
|
||||
**🔍 影响范围**
|
||||
- **极端行情**: 跳空、闪崩、stops level 不足时实际亏损可能远超 80pips
|
||||
- **回测可信度**: 无风控 = 任意极端年份的回测都会被这些极端事件主导
|
||||
|
||||
---
|
||||
|
||||
### 4.4.3 保证金监控
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 598, 652, 727, 782 行 - 保证金检查
|
||||
if (DFCC::AccountFreeMarginCheck(Symbol(),1, dbl_6B59728) > 0 && dword_6B59570 < 0 && dword_6B59588 < 10)
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 开仓前调用 `AccountFreeMarginCheck`,**返回值 > 0 才下单**——逻辑正确
|
||||
- 但 `AccountFreeMarginCheck` 是**同步阻塞函数**,在订单量大时会慢
|
||||
- **平仓前未做保证金检查**(实际上也不需要,但行情极端时 close → open 顺序可能因保证金不足失败)
|
||||
|
||||
**🔍 影响范围**
|
||||
- **保证金不足**: 极端行情下 `平 → 开` 之间的 tick 内保证金状态可能变化,导致开仓失败
|
||||
- **重试 10 次**: 重试期间保证金检查可能持续失败
|
||||
|
||||
---
|
||||
|
||||
### 4.4.4 杠杆适应性
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:全 EA - 未处理杠杆差异
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 复利公式 `equity*40/1e6` 隐含杠杆假设(约 1000:1 时 equity$1000 = 0.04 手)—— 杠杆变化时手数会偏离预期
|
||||
- 未读取 `ACCOUNT_LEVERAGE` 做自适应
|
||||
|
||||
**🔍 影响范围**
|
||||
- **跨账户**: 在 100:1 杠杆账户上手数会比 1000:1 账户小 10 倍
|
||||
|
||||
---
|
||||
|
||||
### 4.4.5 异常市场条件
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 447-455 行 - 仅做点差过滤
|
||||
if (DFCC::MarketInfo("", 13) <= Pointdifferencelimit)
|
||||
byte_6B59544 = 1;
|
||||
else
|
||||
byte_6B59544 = 0;
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 只过滤了点差(spread ≤ 50)
|
||||
- **未处理**:
|
||||
- 跳空(周末、周一开盘、央行决议)
|
||||
- 流动性枯竭(节假日、交投清淡时段)
|
||||
- 报价异常(bid > ask、零报价)
|
||||
- 服务器时间/本地时间异常
|
||||
|
||||
**🔍 影响范围**
|
||||
- **跳空**: 周一开盘 gap 可能让 WPR 极值持续 → 在不合理的价位开仓
|
||||
- **流动性**: 圣诞/元旦等节假日的滑点和拒单会显著上升
|
||||
|
||||
---
|
||||
|
||||
## 4.5 性能与资源管理
|
||||
|
||||
### 4.5.1 指标句柄复用
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 525-532 行 - 句柄管理
|
||||
if (g_iBands_Upper == INVALID_HANDLE || g_Volatility != Volatility)
|
||||
{
|
||||
CleanupIndicatorHandles();
|
||||
g_Volatility = Volatility;
|
||||
g_iBands_Upper = ::iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
|
||||
g_iBands_Lower = ::iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE); // 重复创建!
|
||||
g_iWPR_Handle = ::iWPR(_Symbol, PERIOD_CURRENT, g_Volatility);
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- `g_iBands_Upper` 和 `g_iBands_Lower` 是**两个独立的 iBands 句柄**(行 529-530)—— 但 iBands 在 MQL5 中**只用一个句柄**就能访问 3 个 buffer(BASE_LINE=0, UPPER=1, LOWER=2)
|
||||
- `_GetIndicatorValue_(handle, mode, shift)` 切换 mode 即可取不同 buffer
|
||||
- **当前浪费了一个句柄 + 一次 BarsCalculated 等待**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **性能**: 多占用 1 个指标槽(MT5 限制每个图表 ≤ 64 个指标)
|
||||
- **逻辑**: 两个句柄的 BarsCalculated 各自等待,最多 5 秒(行 2339-2345 的 100×50ms)
|
||||
- **资源**: 多一次 IndicatorRelease 调用
|
||||
|
||||
---
|
||||
|
||||
### 4.5.2 数组动态分配
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 2231-2252 行 - RefreshRates 中动态扩展数组
|
||||
if ((Bars_FXD > 0) && (Bars_FXD > prevBars)) {
|
||||
::ArrayResize(::Time, Bars_FXD);
|
||||
::ArrayResize(::Open, Bars_FXD);
|
||||
::ArrayResize(::High, Bars_FXD);
|
||||
::ArrayResize(::Low, Bars_FXD);
|
||||
::ArrayResize(::Close, Bars_FXD);
|
||||
::ArrayResize(::Volume, Bars_FXD);
|
||||
// 然后循环 CopyRates 填充
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 6 个全局数组在第一次 OnTick 时按 Bars_FXD 扩容(可能上万根)
|
||||
- 后续新增 bar 时只 CopyRates bar[i],不 resize
|
||||
- **首次扩容代价**: 6 次 ArrayResize + 最多 1 万次 CopyRates 调用
|
||||
- **本 EA 主逻辑未使用这些数组**(v68/v67/v66 直接来自 iBands/iWPR 句柄)—— 这些数组**实际上是死代码**
|
||||
|
||||
**🔍 影响范围**
|
||||
- **性能**: 首 tick 显著延迟(可达数百 ms)
|
||||
- **内存**: 6 × 10000 × sizeof(double/datetime) ≈ 0.5 MB 常驻
|
||||
- **可维护性**: 死代码误导维护者(以为 EA 用了价格数组)
|
||||
|
||||
---
|
||||
|
||||
### 4.5.3 字符串操作
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 546 行
|
||||
dbl_6B59728 = StringToDouble(DoubleToString(DFCC::AccountEquity() * Risk / qword_6B59510, 2));
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 每个 tick 都进行 `double → string → double` 转换,浪费 CPU
|
||||
- 截断到 2 位小数用 `NormalizeDouble(..., 2)` 即可
|
||||
|
||||
**🔍 影响范围**
|
||||
- **性能**: 单次约 0.5µs,但每秒数千 tick 时累计
|
||||
- **精度**: StringToDouble 路径可能有舍入误差
|
||||
|
||||
---
|
||||
|
||||
### 4.5.4 文件句柄
|
||||
|
||||
- **不涉及**文件 I/O
|
||||
- 无资源泄漏
|
||||
|
||||
---
|
||||
|
||||
### 4.5.5 定时器使用
|
||||
|
||||
- **未使用** `EventSetTimer`
|
||||
- 时间相关逻辑全部在 OnTick 中即时计算(Hour / iTime)
|
||||
- **可接受**——本策略对时间精度要求不高
|
||||
|
||||
---
|
||||
|
||||
## 4.6 错误处理与健壮性
|
||||
|
||||
### 4.6.1 交易返回码
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 617-624, 670-677, 744-750, 800-806 行 - OrderSend 后仅检查 ticket < 0
|
||||
if (dword_6B59570 < 0) {
|
||||
dword_6B58FA8 = 0;
|
||||
// 注释掉的 Print: "84521270", LODWORD(v60)
|
||||
}
|
||||
++dword_6B59588;
|
||||
} while (dword_6B59570 < 0 && dword_6B59588 < 10);
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 仅判断 `ticket < 0`(即返回值是否失败),未读取 `result.retcode`
|
||||
- `Print` 调试信息被注释,无法定位失败原因
|
||||
- 失败后**仅继续重试**,没有区分"可重试错误"(requote/timeout)和"致命错误"(invalid stops/not enough money)
|
||||
- 10 次重试是写死常量,**无自适应**(如 timeout 应加大间隔,invalid stops 应立即停止)
|
||||
|
||||
**🔍 影响范围**
|
||||
- **调试困难**: 出问题时无日志定位
|
||||
- **效率**: 持续失败时浪费 10 次重试时间
|
||||
|
||||
---
|
||||
|
||||
### 4.6.2 错误重试机制
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 2447-2520 行 - CheckForTradingError 有重试逻辑但未在主 EA 调用
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- DFCC_TRADES::CheckForTradingError 函数**已实现 5 次重试 + 1 秒间隔**(行 2453-2520)
|
||||
- 但 EA 主逻辑**没有调用它**!仅依赖 `do-while` 的 10 次重试
|
||||
- 两个重试机制并存但**互不协作**——一个在 DFCC 内部(被 bypass),一个在主 EA(功能简单)
|
||||
|
||||
**🔍 影响范围**
|
||||
- **重试浪费**: DFCC 内部的重试在主 EA 的循环中被跳过
|
||||
- **可维护性**: 错误处理分散在两处,行为不一致
|
||||
|
||||
---
|
||||
|
||||
### 4.6.3 连接状态
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:全 EA - 未检查连接
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 未检查 `TerminalInfoInteger(TERMINAL_CONNECTED)`
|
||||
- 断网时 `SymbolInfoDouble` 会用最后一次缓存值,导致"幽灵报价"
|
||||
|
||||
**🔍 影响范围**
|
||||
- **断网恢复**: 可能用过期报价开仓
|
||||
|
||||
---
|
||||
|
||||
### 4.6.4 报价有效性
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 1204, 2228-2229 行 - Ask/Bid 直接赋值
|
||||
::Ask = ::SymbolInfoDouble(::_Symbol, SYMBOL_ASK);
|
||||
::Bid = ::SymbolInfoDouble(::_Symbol, SYMBOL_BID);
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- `SymbolInfoDouble` 在交易服务器关闭/品种停盘时返回 0
|
||||
- 未做 `if (Ask > 0 && Bid > 0 && Ask >= Bid)` 校验
|
||||
- EA 用 `Ask/Bid` 计算 SL/TP 时若 Ask=0 会得到 SL=-dword_6B59714*P(**负数止损 = 方向错误的止损**)
|
||||
|
||||
**🔍 影响范围**
|
||||
- **周末/节假日**: 服务器停盘时收到"tick"(实际上是上一笔缓存)→ 开出 SL 反向的订单
|
||||
- **流动性事件**: 极端行情下短暂零报价可能造成严重错误
|
||||
|
||||
---
|
||||
|
||||
### 4.6.5 除零保护
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 546 行 - 复利公式
|
||||
dbl_6B59728 = DFCC::AccountEquity() * Risk / qword_6B59510;
|
||||
// qword_6B59510 = 1,000,000 (硬编码常量,恒非零)
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 555 行 - 时间窗判断
|
||||
if (DFCC::Hour() >= dword_6B59530 && DFCC::Hour() <= dword_6B59534 || ...)
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 主逻辑中**无除法运算**(v68-v67 是减法,×Point 是乘法)
|
||||
- 唯一除法是复利公式但分母是常量
|
||||
- **无除零风险**
|
||||
|
||||
---
|
||||
|
||||
## 4.7 平台特性合规
|
||||
|
||||
### 4.7.1 MQL5 vs MQL4 差异
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 4 行 - 文件头错标
|
||||
//| TopBottomEA.mq4 |
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 1169 行 - 转换器签名
|
||||
//== fxDreema MQL4 to MQL5 Converter ==//
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 文件头注释写 `.mq4`(**MQL4 扩展名**)但实际是 .mq5
|
||||
- 通过 fxDreema 工具**自动转换**而非手写——保留了 MQL4 风格:
|
||||
- 订单类型用整数 0/1/2/3 而非 `ORDER_TYPE_BUY` 等枚举
|
||||
- `OrderSend` 10 参数重载而非 MQL5 的 `MqlTradeRequest` 结构体
|
||||
- `MarketInfo` 用整数 mode 编码而非 MQL5 的 SYMBOL_* 枚举
|
||||
- **未使用 MQL5 标准库**: `CTrade` / `CSymbolInfo` / `CPositionInfo` 等更现代的封装
|
||||
|
||||
**🔍 影响范围**
|
||||
- **学习成本**: MQL5 原生开发者难以理解 fxDreema 生成的兼容层
|
||||
- **性能**: DFCC 包装增加了 ~20% 的调用开销(特别是 OrderSend 同步轮询 Sleep(10ms))
|
||||
- **未来兼容性**: fxDreema 转换器本身可能不再维护,升级 MQL5 版本时需重写 DFCC
|
||||
|
||||
---
|
||||
|
||||
### 4.7.2 枚举使用
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 602 行 - 订单类型用魔术数字
|
||||
DFCC::OrderSend(
|
||||
Symbol(),
|
||||
0, // OP_BUY = 0
|
||||
dbl_6B59728,
|
||||
Ask,
|
||||
dword_6B59540,
|
||||
qword_6B59578,
|
||||
qword_6B59580,
|
||||
CommentName,
|
||||
Magic,
|
||||
0,
|
||||
16711680);
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 838, 881, 925, 970 行 - OrderType 比较
|
||||
if (DFCC::OrderType() == 0) // 应为 POSITION_TYPE_BUY
|
||||
if (DFCC::OrderType() == 1) // 应为 POSITION_TYPE_SELL
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- 50+ 处用 `0/1/2/3` 代替 `POSITION_TYPE_BUY/POSITION_TYPE_SELL/ORDER_TYPE_BUY_LIMIT/ORDER_TYPE_SELL_LIMIT`
|
||||
- DFCC 包装内部才用枚举,对外是 MQL4 风格
|
||||
- 颜色 16711680 / 255 替代 `clrRed/clrBlue`
|
||||
|
||||
**🔍 影响范围**
|
||||
- **可读性**: 维护时需查表"0 是 BUY 还是 SELL"
|
||||
- **编译期**: 无影响(MQL5 允许整数→枚举隐式转换)
|
||||
- **审计风险**: 代码 review 时容易混淆
|
||||
|
||||
---
|
||||
|
||||
### 4.7.3 类型安全
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 2167 行 - 静态变量存 ticket
|
||||
static int memory = 0;
|
||||
if (ticket > 0) {
|
||||
memory = (int)ticket; // ulong → int 截断
|
||||
}
|
||||
return memory;
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 1358 行 - magic 用 long
|
||||
request.magic = ::PositionGetInteger(POSITION_MAGIC);
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 1805, 1793 行 - OrderSend 返回 ulong 转 int
|
||||
ulong ticket = -1; // ulong -1 = ULONG_MAX(约 1.8e19)
|
||||
return (int)ticket; // 截断
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- `int` 32 位最多 21 亿——MT5 ticket 在某些经纪商可能超过 int 范围(虽然实际少见)
|
||||
- `ulong ticket = -1` 是反编译器为兼容 MQL4 的 -1 失败约定,**实际值是 0xFFFFFFFFFFFFFFFF**——后续 `if (ticket > 0)` 判断会失败(无符号 > 0 永远 true,但 (int)ticket 转回 -1 后 `if (dword_6B59570 < 0)` 又变 true)——逻辑绕了一圈
|
||||
- 大量 `(ulong)slippage`、`(int)::PositionGetInteger(...)` 强转
|
||||
- `DoubleToString → StringToDouble` 路径(行 546)有精度损失
|
||||
|
||||
**🔍 影响范围**
|
||||
- **极端 ticket**: 极少触发但可能
|
||||
- **代码可读性**: 大量强转掩盖真实意图
|
||||
|
||||
---
|
||||
|
||||
### 4.7.4 时间处理
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 540-541 行
|
||||
qword_6B59550 = DFCC::iTime("", 1, 1); // bar 1
|
||||
qword_6B59560 = DFCC::iTime("", 1, 1); // bar 1
|
||||
```
|
||||
|
||||
```mql5
|
||||
// 📍 问题位置:第 555, 690 行
|
||||
if (DFCC::Hour() >= dword_6B59530 && ...) // 服务器时间
|
||||
if (DFCC::TimeHour(TimeGMT()) >= 18 || ...) // GMT
|
||||
```
|
||||
|
||||
**⚠️ 问题原因**
|
||||
- `qword_6B59550` 和 `qword_6B59560` 都在**同一行**被赋**相同的值**——两个本应独立的"新柱时间戳"完全相同,**防重入失效**(行 567 通道和 630 通道用的是同一个时间)
|
||||
- 测试与实盘时基不同(服务器时间 vs GMT),回测时区设置会改变行为
|
||||
|
||||
**🔍 影响范围**
|
||||
- **防重入失效**: `qword_6B59548 != qword_6B59550` 在多通道下可能永远成立
|
||||
- **行为不一致**: 回测与实盘时区差异导致开仓时机不同
|
||||
|
||||
---
|
||||
|
||||
### 4.7.5 指针与引用
|
||||
|
||||
- **不涉及**动态对象
|
||||
- 全局数组的 ArrayResize 是安全操作
|
||||
- 无指针使用
|
||||
|
||||
---
|
||||
|
||||
## 5. 风险评级与建议
|
||||
|
||||
### 5.1 严重问题(P0)
|
||||
|
||||
| 编号 | 位置 | 问题描述 | 影响范围 |
|
||||
|---|---|---|---|
|
||||
| P0-1 | 535-537 行 | 指标用 shift=0 导致 repainting + 未来函数 | 回测结果虚高 30-50%;实盘表现远差于回测 |
|
||||
| P0-2 | 540-541 行 | `qword_6B59550 == qword_6B59560`,新柱防重入失效 | 同一 K 线内可多次开仓,违反"单笔持仓"约束 |
|
||||
| P0-3 | 460-519 行 | 多品种用 StringFind + 共享 Magic 888999 | 多图表运行时仓位计数混乱,可能在 GBPCAD 持仓时仍开 EURSGD 仓位 |
|
||||
| P0-4 | 690 行 | `>= 18 || <= 1` 实盘时间窗条件可能是 OR 而非连续窗口 | 实际可交易时段远超设计意图 |
|
||||
| P0-5 | 220 行 + 全文 | `qword_6B59590 = MarketInfo("", 13)` 是死代码 | 误导维护者,但不影响功能 |
|
||||
|
||||
### 5.2 警告问题(P1)
|
||||
|
||||
| 编号 | 位置 | 问题描述 | 影响范围 |
|
||||
|---|---|---|---|
|
||||
| P1-1 | 529-530 行 | iBands 创建了 2 个独立句柄,浪费资源 | 性能下降 50%(多一次 BarsCalculated 等待) |
|
||||
| P1-2 | 546 行 | 复利公式 `StringToDouble(DoubleToString(...))` 反模式 | CPU 浪费 + 精度损失 |
|
||||
| P1-3 | 849-973 行 | 平仓 slippage 硬编码 300,开仓 30,10 倍差距 | 平仓成本估算错误 |
|
||||
| P1-4 | 587-591 行 | 注释掉的最小/最大手数检查 | 复利模式下可能产出小于最小手数 |
|
||||
| P1-5 | 2208-2277 行 | RefreshRates 中 6 个全局数组实际未被主逻辑使用 | 0.5 MB 死内存 + 首 tick 延迟 |
|
||||
| P1-6 | 567-624 行 | 4 段 OrderSend 各 10 次重试,但 DFCC 内部还有 CheckForTradingError | 错误处理分散、行为不一致 |
|
||||
| P1-7 | 159-430 行 | OnInit 中 270 行被注释的 MQL4 图表对象代码 | 维护陷阱,可能误启用 |
|
||||
| P1-8 | 553-815 行 | 4 段开仓 × 2 模式共 8 块近重复代码 | 任何修改需同步 8 处 |
|
||||
| P1-9 | 41-143 行 | 50+ `dword_6Bxxxxx` 全局变量无法 input 调参 | 策略优化困难 |
|
||||
| P1-10 | 全 EA | 无任何回撤/单日亏损/连续亏损风控 | 极端行情可能击穿账户 |
|
||||
|
||||
### 5.3 优化建议(P2)
|
||||
|
||||
| 编号 | 位置 | 问题描述 | 影响范围 |
|
||||
|---|---|---|---|
|
||||
| P2-1 | 1177-1200 行 | Bars_FXD/Digits_FXD 全局变量无 input 控制 | 兼容性测试困难 |
|
||||
| P2-2 | 580, 642, 714, 769 行 | SL 硬编码 `NormalizeDouble(..., 5)` 5 位 | 4 位品种下精度错误 |
|
||||
| P2-3 | 1169 行 | 2000 行 DFCC 内联而非独立 .mqh | 复用困难 |
|
||||
| P2-4 | 824, 868, 911, 957 行 | 平仓条件混用 `> 0` 和 `> -dword_6B5952C` 不一致 | 阅读时易误解 |
|
||||
| P2-5 | 525-532 行 | 句柄管理放在 OnTick 而非 OnInit | 启动延迟一次 tick |
|
||||
| P2-6 | 1202-1206 行 | OnTick 入口调 RefreshRates 再调 __OnTick__ | 间接调用,可直接合并 |
|
||||
| P2-7 | 438-982 行 | `__OnTick__` 540 行无任何子函数 | 代码可读性差 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 总体评价
|
||||
|
||||
### 6.1 代码质量评分
|
||||
|
||||
| 维度 | 分数 | 说明 |
|
||||
|---|---|---|
|
||||
| **代码规范** | 20 / 100 | 反编译器产物,命名、注释、可读性均极差 |
|
||||
| **交易逻辑** | 50 / 100 | 核心思想(BB 带宽 + WPR 极值 + 单仓反转)合理,但有 repainting 致命缺陷 |
|
||||
| **风险管理** | 25 / 100 | 几乎无风控(无回撤限制、无单日亏损、无连续亏损暂停) |
|
||||
| **性能优化** | 40 / 100 | 句柄管理基本正确,但 iBands 双句柄浪费 + RefreshRates 死数组 |
|
||||
| **错误处理** | 30 / 100 | 有重试但无日志、无重连检查、无报价校验 |
|
||||
|
||||
### 6.2 综合评级
|
||||
|
||||
**评级**: **D**
|
||||
|
||||
- **策略思路可识别**,但**实现质量极差**
|
||||
- 主要是**反编译器产物**而非手写代码,反编译器保留了 MQL4 风格
|
||||
- 关键**未来函数 / repainting** 问题使回测结果不可信
|
||||
- **不适合直接用于实盘**
|
||||
|
||||
### 6.3 实盘建议
|
||||
|
||||
- **是否适合实盘**: **否**(除非重写)
|
||||
- **需要修复的关键问题**:
|
||||
1. 修复指标 shift(改用 shift=1 + 新柱检测)
|
||||
2. 修复多品种参数识别(用 enum 而非 StringFind)
|
||||
3. 修复新柱防重入(拆分为 4 个独立时间戳)
|
||||
4. 修正实盘时间窗条件
|
||||
5. 重构为模块化结构(拆出 DFCC、拆出 4 段开/平仓)
|
||||
6. 增加最小/最大手数检查、回撤控制、风控熔断
|
||||
- **建议的测试方案**:
|
||||
- 先在 Strategy Tester 的 "Every tick based on real ticks" 模式下做 1 年回测
|
||||
- 重点检查:是否出现"开仓→立即平仓"或"开仓→同 K 线再开仓"的情况
|
||||
- 与 "1 minute OHLC" 模式对比,**两者结果差异过大 = 确认有 repainting**
|
||||
- 至少 3 个月模拟盘(demo)实盘观察,与回测对比
|
||||
- **强烈建议先用 MT5 策略测试器跑乱序(random delay)模式**,检测执行质量
|
||||
Reference in New Issue
Block a user