first commit

This commit is contained in:
2026-07-06 03:32:12 +08:00
commit 160c68da39
54 changed files with 26556 additions and 0 deletions
@@ -0,0 +1,244 @@
# MQL5代码审查报告 - 第一、二阶段
**审查对象**: TopBottomEA.mq5
**审查日期**: 2026-06-03
**代码总行数**: 2574 行
---
## 1. 程序意图推测
### 1.1 文件类型判断
- **文件类型**: Expert AdvisorEA,自动交易程序)
- **文件路径**: `C:\Users\Administrator\Desktop\mt5_backtest\TopBottomEA.mq5`
- **依赖头文件**: 无(DFCC 转换库以 class 形式内联在文件 12102436 行)
- **文件描述**: 自称"波动率自适应机制"、单笔持仓、平均每天约4单、持仓约12小时、用Dukascopy历史数据通过$20小额回测10年+
- **文件来源判断**: 顶部 copyright 写 2022 MetaQuotesversion 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位报价下=30pips5位下=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 句柄**(行 529530),而一个 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-43099% 注释,仅 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 个 bufferBASE_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)模式**,检测执行质量