Condition chuẩn
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
# RsiMomentumEA — Tài liệu logic
|
||||
|
||||
**Phiên bản EA:** 4.14
|
||||
**File chính:** `Experts/RsiMomentumEA.mq5`
|
||||
**Module:** `Include/RsiMom/PhaseEntry.mqh`, `SignalDebug.mqh`, `TradeJournal.mqh`
|
||||
|
||||
EA giao dịch theo **động lượng RSI** (RSI + EMA9 + WMA45 trên cùng thang RSI), kết hợp **chuỗi 5 phase** trước khi RSI cắt WMA45. Logic **độc lập** với indicator `RsiMomentumIndicator` (không đọc buffer indicator ngoài).
|
||||
|
||||
---
|
||||
|
||||
## 1. Tổng quan kiến trúc
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph calc [Mỗi tick — OnCalculate]
|
||||
IND[Copy RSI / EMA9 / WMA45 / EMA200]
|
||||
SCAN[SignalScan_Run — quét nến]
|
||||
BUF[buf_Signal shift]
|
||||
end
|
||||
subgraph trade [Mỗi nến mới — OnTick]
|
||||
TRY[TradeTryOnBarOpen]
|
||||
EXE[TradeExecuteOrder — Buy/Sell Limit]
|
||||
PM[Position_ManageAt1R]
|
||||
PEND[Pending expiry / env cancel]
|
||||
end
|
||||
IND --> SCAN --> BUF
|
||||
BUF --> TRY --> EXE
|
||||
EXE --> PM
|
||||
EXE --> PEND
|
||||
```
|
||||
|
||||
| Thành phần | Vai trò |
|
||||
|------------|---------|
|
||||
| `buf_RSI`, `buf_EMA9`, `buf_WMA45` | Giá trị chỉ báo tại từng `shift` (series) |
|
||||
| `buf_Signal` | `+1` BUY hợp lệ, `-1` SELL hợp lệ, `0` không tín hiệu |
|
||||
| `buf_Trend` | `+1` / `-1` / `0` theo EMA200 (panel, không bắt buộc cho entry nếu tắt filter) |
|
||||
| Mũi tên `RsiMomEA_CR_*` | Chỉ vẽ khi tín hiệu **đủ điều kiện** (pass toàn pipeline tín hiệu) |
|
||||
| Mũi tên `RsiMomEA_DBG_*` | Mọi **cross RSI×WMA45** + lý do OK/SKIP (debug) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Chỉ báo
|
||||
|
||||
Tất cả tính trên **khung thời gian chart** EA gắn (`_Symbol`, `_Period`).
|
||||
|
||||
| Handle | Công thức MT5 | Ghi chú |
|
||||
|--------|---------------|---------|
|
||||
| `h_RSI` | `iRSI(..., PRICE_CLOSE)` | RSI(14) mặc định |
|
||||
| `h_EMA9` | `iMA(..., MODE_EMA, h_RSI)` | EMA9 **của RSI** |
|
||||
| `h_WMA45` | `iMA(..., MODE_LWMA, h_RSI)` | WMA45 **của RSI** |
|
||||
| `h_EMA200` | `iMA(..., PRICE_CLOSE)` | Trend trên **giá**, không phải RSI |
|
||||
| `h_ATR_Regime` | ATR(14) | Bộ lọc ATR mở rộng (tùy chọn) |
|
||||
| `h_ATR` | ATR(14) | Buffer SL sau swing |
|
||||
|
||||
**Quy ước index:** `shift = 1` là nến **vừa đóng**; `shift = 0` là nến đang hình thành. EA quét tín hiệu trên nến đã đóng (`i >= 1`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Logic vào lệnh — tổng thể
|
||||
|
||||
Entry chỉ xảy ra khi **hai tầng** đều pass:
|
||||
|
||||
1. **Tầng tín hiệu** (`SignalScan_Run`) → ghi `buf_Signal[InpSignalBarShift]` (mặc định `1`).
|
||||
2. **Tầng thực thi** (`TradeExecuteOrder` khi mở nến mới) → phiên/spread, Limit hợp lệ, SL/TP, volume.
|
||||
|
||||
### 3.1. Trigger bắt buộc (mọi lệnh)
|
||||
|
||||
Đánh giá tại nến `i` (thường `i = 1`).
|
||||
|
||||
**BUY — cross lên WMA45:**
|
||||
|
||||
```
|
||||
RSI[i+1] <= WMA45[i+1] AND RSI[i] > WMA45[i]
|
||||
```
|
||||
|
||||
**SELL — cross xuống WMA45:**
|
||||
|
||||
```
|
||||
RSI[i+1] >= WMA45[i+1] AND RSI[i] < WMA45[i]
|
||||
```
|
||||
|
||||
Không có cross → không xét tiếp.
|
||||
|
||||
### 3.2. Lõi entry: 5 phase hoặc EMA9 đơn giản
|
||||
|
||||
| `InpPhaseFilterEnabled` | Điều kiện lõi |
|
||||
|-------------------------|----------------|
|
||||
| `true` (mặc định) | `Phase_BuyPasses` / `Phase_SellPasses` — đủ P1→P5 (mục 4) |
|
||||
| `false` | BUY: `EMA9[i] < WMA45[i]` — SELL: `EMA9[i] > WMA45[i]` |
|
||||
|
||||
### 3.3. Bộ lọc tín hiệu (tùy chọn, có thể tắt)
|
||||
|
||||
| Input | Mặc định (debug) | Điều kiện khi **bật** |
|
||||
|-------|------------------|------------------------|
|
||||
| `InpTrendFilterEnabled` | `false` | BUY: `InpTrendConfirmBars` nến liên tiếp có `Close > EMA200`. SELL: `Close < EMA200`. |
|
||||
| `InpAtrExpFilterEnabled` | `false` | ATR(shift) / ATR(shift+cmp) ≥ `InpAtrExpMinRatio` **và** ATR tăng liên tiếp `InpAtrExpRiseBars` nến. |
|
||||
| `InpSessionFilterEnabled` | `false` | Thời gian **nến tín hiệu** nằm London hoặc NY; không trong cửa sổ giao phiên; trừ `InpSessionAvoidLastMin` phút cuối phiên. |
|
||||
|
||||
**Tín hiệu hợp lệ (ghi `buf_Signal`):**
|
||||
|
||||
```
|
||||
validBuy = crossUp AND coreBuy AND trendUp AND atrOk AND sessionOk
|
||||
validSell = crossDn AND coreSell AND trendDn AND atrOk AND sessionOk
|
||||
```
|
||||
|
||||
(`trendUp` / `atrOk` / `sessionOk` luôn `true` nếu filter tương ứng **tắt**.)
|
||||
|
||||
### 3.4. Tầng thực thi (đặt lệnh)
|
||||
|
||||
Khi **nến mới** mở (`OnTick`, `t0 != g_tradeBarAnchor`):
|
||||
|
||||
- Đọc `buf_Signal[1]`.
|
||||
- Nếu `InpTradeEnabled` và `buf_Signal[1] == ±1` → `TradeExecuteOrder`.
|
||||
|
||||
**Thêm kiểm tra tại thời điểm đặt lệnh** (`Env_AllowsTradeAtBar`):
|
||||
|
||||
- Phiên + giao phiên (nếu `InpSessionFilterEnabled`).
|
||||
- Spread ≤ `InpMaxSpreadPoints` (nếu `InpSpreadFilterEnabled`; trong Tester thường bỏ qua nếu `InpSpreadSkipInTester = true`).
|
||||
|
||||
**Không** đặt lệnh nếu:
|
||||
|
||||
- `InpOnePositionFlat` và đã có position/pending cùng magic.
|
||||
- Limit @ 50% body không hợp lệ so với Bid/Ask + `STOPS_LEVEL`.
|
||||
- Không tính được SL/TP swing hoặc volume = 0.
|
||||
|
||||
---
|
||||
|
||||
## 4. Entry 5 phase — chi tiết
|
||||
|
||||
Implement trong `Include/RsiMom/PhaseEntry.mqh`. Mô tả theo **BUY**; **SELL** đối xứng (mở rộng lên, EMA9 xuống, v.v.).
|
||||
|
||||
Quét trên các nến **đã đóng** trong lookback: từ `shift+1` trở về quá khứ (index series tăng = quá khứ).
|
||||
|
||||
### Phase 1 — Mở rộng (expansion)
|
||||
|
||||
**Ý nghĩa:** Trước đó ba đường RSI / EMA9 / WMA45 đã **xếp lớp** và giãn đủ mạnh (sóng động lượng).
|
||||
|
||||
**BUY:** Trong `InpPhaseExpandLookback` nến (mặc định 25), tìm nến `j` thỏa:
|
||||
|
||||
```
|
||||
RSI[j] < EMA9[j] < WMA45[j]
|
||||
spread[j] = WMA45[j] - RSI[j] → max(spread) >= InpPhaseMinExpandSpread (mặc định 8 điểm RSI)
|
||||
```
|
||||
|
||||
**SELL:** `RSI[j] > EMA9[j] > WMA45[j]`, `spread = RSI[j] - WMA45[j]`.
|
||||
|
||||
Fail tag: `P1-mở rộng`.
|
||||
|
||||
### Phase 2 — Cuộn quanh EMA9 (coil)
|
||||
|
||||
**Ý nghĩa:** RSI không chỉ **xuyên** EMA9 một lần mà **đan** qua lại (chuẩn bị trước cắt WMA45).
|
||||
|
||||
Trong `InpPhaseCoilLookback` nến (mặc định 12), đếm số lần RSI cắt EMA9 (lên hoặc xuống):
|
||||
|
||||
```
|
||||
cross tại j nếu:
|
||||
(RSI[j+1] <= EMA9[j+1] && RSI[j] > EMA9[j]) OR
|
||||
(RSI[j+1] >= EMA9[j+1] && RSI[j] < EMA9[j])
|
||||
```
|
||||
|
||||
Yêu cầu: `số_cross >= InpPhaseMinRsiEma9Cross` (mặc định **2**).
|
||||
|
||||
> **Lưu ý:** `InpPhaseCoilBand` được khai báo trong input nhưng **chưa dùng** trong `Phase_BuyPasses` (chỉ đếm cross). Hàm `Phase_CountCoilBars` có sẵn để mở rộng sau.
|
||||
|
||||
Fail tag: `P2-cuộn`.
|
||||
|
||||
### Phase 3 — EMA9 quay hướng (đã nới lỏng)
|
||||
|
||||
**BUY:** EMA9 **không được giảm quá mạnh** trong cửa sổ `InpPhaseEma9SlopeBars` (mặc định **2**):
|
||||
|
||||
```
|
||||
EMA9[i] >= EMA9[i + bars] - InpPhaseEma9SlopeTol (tol mặc định 1.5 pt RSI)
|
||||
```
|
||||
|
||||
Cho phép EMA9 **phẳng hoặc hơi lệch** thay vì bắt buộc tăng rõ.
|
||||
|
||||
**SELL:** `EMA9[i] <= EMA9[i + bars] + tol`.
|
||||
|
||||
Fail tag: `P3-EMA9↑` / `P3-EMA9↓`.
|
||||
|
||||
### Phase 4 — WMA45 phẳng lại
|
||||
|
||||
**Ý nghĩa:** WMA45 trước đó dốc theo hướng sóng; tại nến tín hiệu đã **làm phẳng** (sắp cắt).
|
||||
|
||||
Với `b = InpPhaseWmaFlatBars` (mặc định 5):
|
||||
|
||||
```
|
||||
recent = (WMA45[i] - WMA45[i+b]) / b
|
||||
prior = (WMA45[i+b] - WMA45[i+2b]) / b
|
||||
```
|
||||
|
||||
**BUY:**
|
||||
|
||||
- `|recent| <= InpPhaseWmaFlatMaxSlope` (mặc định **0.55** — lỏng hơn 0.35 cũ).
|
||||
- Quá khứ WMA45:
|
||||
- `InpPhaseWmaRelaxPrior = true` (mặc định): chỉ cần `prior < 0` (từng giảm, không cần dốc mạnh).
|
||||
- `false`: cần `prior <= -InpPhaseWmaWasSlopeMin` (mặc định **0.08**).
|
||||
|
||||
**SELL:** đối xứng (`prior > 0` khi relax).
|
||||
|
||||
Fail tag: `P4-WMA45 phẳng`.
|
||||
|
||||
### Phase 5 — EMA9 gần WMA45 tại cắt
|
||||
|
||||
**Ý nghĩa:** Cross xảy ra **gần** WMA45, không phải RSI đã bứt xa (tín hiệu yếu / muộn).
|
||||
|
||||
**BUY:**
|
||||
|
||||
```
|
||||
EMA9[i] < WMA45[i]
|
||||
(WMA45[i] - EMA9[i]) <= InpPhaseMaxEma9WmaGap (mặc định **18** điểm RSI)
|
||||
```
|
||||
|
||||
**SELL:** `EMA9[i] > WMA45[i]`, `(EMA9[i] - WMA45[i]) <= gap`.
|
||||
|
||||
Fail tag: `P5-EMA9 xa WMA45`.
|
||||
|
||||
### Thứ tự kiểm tra trong code
|
||||
|
||||
P1 → P2 → P3 → P4 → P5 (fail fast, `failTag` ghi phase đầu tiên lỗi).
|
||||
|
||||
**Cross WMA45 (trigger) không nằm trong P5** — luôn kiểm tra **trước** trong `SignalScan_Run`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Sơ đồ chuỗi BUY (hình dung)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P1 as P1 Mở rộng
|
||||
participant P2 as P2 Cuộn EMA9
|
||||
participant P3 as P3 EMA9 lên
|
||||
participant P4 as P4 WMA45 phẳng
|
||||
participant P5 as P5 EMA9 gần WMA45
|
||||
participant X as Cross RSI lên WMA45
|
||||
participant F as Filter tùy chọn
|
||||
participant T as Trade Limit
|
||||
|
||||
P1->>P2: quá khứ 12-25 nến
|
||||
P2->>P3: tại nến i
|
||||
P3->>P4: tại nến i
|
||||
P4->>P5: tại nến i
|
||||
P5->>X: cùng nến i
|
||||
X->>F: EMA200 / ATR / phiên
|
||||
F->>T: buf_Signal=1, nến sau
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Thực thi lệnh
|
||||
|
||||
### 6.1. Loại lệnh và giá vào
|
||||
|
||||
- **Buy Limit / Sell Limit** tại **50% thân nến tín hiệu**:
|
||||
|
||||
```
|
||||
entry = (Open[InpSignalBarShift] + Close[InpSignalBarShift]) / 2
|
||||
```
|
||||
|
||||
- BUY Limit: giá **dưới** Ask (có khoảng cách `STOPS_LEVEL` / `FREEZE_LEVEL`).
|
||||
- SELL Limit: giá **trên** Bid.
|
||||
|
||||
### 6.2. Stop loss
|
||||
|
||||
- Tìm **swing pivot** gần nhất trong `InpSwingMaxBars` (mặc định 30):
|
||||
- BUY: đáy pivot (low thấp hơn 2 nến kề).
|
||||
- SELL: đỉnh pivot.
|
||||
- Nếu không có pivot: min/max low/high trong cửa sổ.
|
||||
- SL = pivot ± **buffer**:
|
||||
- `spread × Point` nếu `InpSlAtrAddSpread`.
|
||||
- `+ ATR(1) × InpSlAtrMultiplier` nếu `InpSlAtrBufferEnabled`.
|
||||
|
||||
### 6.3. Take profit
|
||||
|
||||
```
|
||||
risk = |entry - SL|
|
||||
TP = entry ± risk × InpRewardRiskRatio (mặc định 1.1 R)
|
||||
```
|
||||
|
||||
### 6.4. Khối lượng
|
||||
|
||||
```
|
||||
riskMoney = Balance × (InpRiskPercent / 100)
|
||||
volume = riskMoney / lossPerLot(1 lot từ entry → SL)
|
||||
```
|
||||
|
||||
Làm tròn theo `SYMBOL_VOLUME_STEP`, tối thiểu `SYMBOL_VOLUME_MIN`.
|
||||
|
||||
### 6.5. Pending
|
||||
|
||||
| Hành vi | Input |
|
||||
|---------|--------|
|
||||
| Hủy sau N nến không khớp | `InpLimitExpireBars` (40) |
|
||||
| Hủy khi phiên/spread xấu | `Pending_EnvCancelIfBad` |
|
||||
| Hủy pending cũ trước khi đặt mới | `Pending_CancelMine` trong `TradeExecuteOrder` |
|
||||
| Một vị thế / một pending | `InpOnePositionFlat` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Quản lý lệnh mở
|
||||
|
||||
**`InpManageAt1R` (mặc định bật):**
|
||||
|
||||
Khi lợi nhuận floating ≥ **1R** (R = khoảng cách entry → SL lúc mở lệnh):
|
||||
|
||||
1. Chốt `InpPartialCloseRatio` volume (mặc định 50%).
|
||||
2. Dời SL về entry ± `InpBreakevenOffsetPts`.
|
||||
|
||||
Chỉ thực hiện một lần mỗi ticket (`g_pmAt1RDone`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Debug trên chart
|
||||
|
||||
| Input | Mô tả |
|
||||
|-------|--------|
|
||||
| `InpDebugMarkSignals` | Vẽ `RsiMomEA_DBG_*` tại **mọi** cross RSI×WMA45 |
|
||||
| `InpDebugMarkMaxBars` | Giới hạn N nến gần nhất |
|
||||
| `InpDebugLogExperts` | In chi tiết tại `InpSignalBarShift` |
|
||||
|
||||
**Màu / nhãn:**
|
||||
|
||||
| Hiển thị | Ý nghĩa |
|
||||
|----------|---------|
|
||||
| Xanh **OK** | Pass tín hiệu + pass đặt lệnh (tại nến signal) |
|
||||
| Vàng **SIG** | Pass tín hiệu, không đặt (trade tắt / env) |
|
||||
| Đỏ **SKIP** | Cross nhưng fail P1–P5 hoặc filter |
|
||||
|
||||
Tooltip: `RSI↑WMA45 | P1:OK | P2:FAIL(x=1) | … | EMA200:OFF | ATR:OFF | Phiên:OFF | Đặt lệnh:OK`.
|
||||
|
||||
Logic đánh giá: `Include/RsiMom/SignalDebug.mqh`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Phiên & spread (khi bật lại)
|
||||
|
||||
**Phiên** (`InpSessionFilterEnabled = true`):
|
||||
|
||||
- London: `InpLondonStartHour`–`InpLondonEndHour` (mặc định 8–17).
|
||||
- New York: `InpNYStartHour`–`InpNYEndHour` (13–22).
|
||||
- Giờ: **server broker** nếu `InpEnvUseUtc = false`, ngược lại quy đổi UTC.
|
||||
- Trừ `InpSessionAvoidLastMin` phút cuối mỗi cửa sổ.
|
||||
- `InpTransitionBlockEnabled`: chặn thêm cửa sổ 7–8h và 21–22h (giao phiên).
|
||||
|
||||
Khi `InpSessionFilterEnabled = false`: **bỏ toàn bộ** lọc phiên và giao phiên.
|
||||
|
||||
**Spread:** `SYMBOL_SPREAD` (points) ≤ `InpMaxSpreadPoints`. Tester: thường dùng `InpSpreadSkipInTester = true`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Journal CSV
|
||||
|
||||
`InpExportTradeJournal`:
|
||||
|
||||
- `FILE_COMMON/RsiMomEA/journal_SYMBOL_PERIOD.csv` — từng lệnh đóng.
|
||||
- `summary_SYMBOL_PERIOD.csv` — tổng hợp theo tháng.
|
||||
|
||||
`InpJournalResetOnInit`: xóa CSV cũ mỗi lần chạy backtest mới trong Tester.
|
||||
|
||||
---
|
||||
|
||||
## 11. Cấu trúc file
|
||||
|
||||
```
|
||||
Experts/
|
||||
RsiMomentumEA.mq5 # EA chính, inputs, scan, trade
|
||||
RsiMomentumEA_README.md # Tài liệu này
|
||||
|
||||
Include/RsiMom/
|
||||
PhaseEntry.mqh # P1–P5
|
||||
SignalDebug.mqh # Đánh dấu + checklist tooltip
|
||||
TradeJournal.mqh # Xuất CSV
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Preset gợi ý
|
||||
|
||||
### Debug 5 phase (mặc định hiện tại)
|
||||
|
||||
```
|
||||
InpPhaseFilterEnabled = true
|
||||
InpTrendFilterEnabled = false
|
||||
InpAtrExpFilterEnabled = false
|
||||
InpSessionFilterEnabled = false
|
||||
InpDebugMarkSignals = true
|
||||
```
|
||||
|
||||
Chỉ còn: **cross RSI×WMA45 + P1–P5** (+ spread nếu bật và không skip tester).
|
||||
|
||||
### Live / backtest đầy đủ
|
||||
|
||||
```
|
||||
InpTrendFilterEnabled = true
|
||||
InpTrendConfirmBars = 1
|
||||
InpAtrExpFilterEnabled = true
|
||||
InpSessionFilterEnabled = true
|
||||
InpSpreadFilterEnabled = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Khác biệt với indicator
|
||||
|
||||
EA **không** đọc file indicator. Indicator `Indicators/RsiMomentumIndicator` có thể có thêm rule (EMA9 persist, slope RSI, OB/OS) chưa port sang EA — khi so sánh chart indicator vs EA cần kiểm tra từng input tương ứng.
|
||||
|
||||
---
|
||||
|
||||
## 14. Tham chiếu nhanh input entry
|
||||
|
||||
| Input | Mặc định | Phase / vai trò |
|
||||
|-------|----------|-----------------|
|
||||
| `InpPhaseExpandLookback` | 25 | P1 |
|
||||
| `InpPhaseMinExpandSpread` | 8.0 | P1 |
|
||||
| `InpPhaseCoilLookback` | 12 | P2 |
|
||||
| `InpPhaseMinRsiEma9Cross` | 2 | P2 |
|
||||
| `InpPhaseEma9SlopeBars` | 2 | P3 |
|
||||
| `InpPhaseEma9SlopeTol` | 1.5 | P3 |
|
||||
| `InpPhaseWmaFlatBars` | 4 | P4 |
|
||||
| `InpPhaseWmaWasSlopeMin` | 0.08 | P4 (khi RelaxPrior=false) |
|
||||
| `InpPhaseWmaFlatMaxSlope` | 0.55 | P4 |
|
||||
| `InpPhaseWmaRelaxPrior` | true | P4 |
|
||||
| `InpPhaseMaxEma9WmaGap` | 18.0 | P5 |
|
||||
| `InpSignalBarShift` | 1 | Nến tín hiệu & Limit |
|
||||
|
||||
---
|
||||
|
||||
*Tài liệu đồng bộ với mã nguồn v4.14. P1–P2 giữ nguyên; P3–P5 đã nới lỏng mặc định.*
|
||||
@@ -0,0 +1,266 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| PhaseEntry.mqh — entry 5 phase (mở rộng → cuộn → EMA9↑ → WMA45 phẳng → cắt) |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef RSI_MOM_PHASE_ENTRY_MQH
|
||||
#define RSI_MOM_PHASE_ENTRY_MQH
|
||||
|
||||
struct PhaseEntryConfig
|
||||
{
|
||||
bool enabled;
|
||||
int expandLookback; // quét phase 1 trong [shift+1 .. shift+N]
|
||||
double minExpandSpread; // min (WMA45-RSI) khi xếp lớp bear/bull
|
||||
int coilLookback; // quét cuộn RSI↔EMA9 trước nến tín hiệu
|
||||
int minRsiEma9Crosses; // số lần RSI cắt EMA9 (chống xuyên 1 lần — dấu hiệu 3)
|
||||
double coilBand; // |RSI-EMA9| <= band tính là quanh EMA9
|
||||
int ema9SlopeBars; // cửa sổ so sánh hướng EMA9 (P3)
|
||||
double ema9SlopeTol; // BUY: cho phép EMA9 giảm tối đa X pt RSI trong cửa sổ P3
|
||||
int wmaFlatBars; // |WMA45[i]-WMA45[i+bars]| <= flatMax
|
||||
double wmaWasSlopeMin; // P4: WMA45 trước đó dốc đủ (khi wmaRelaxPrior=false)
|
||||
double wmaFlatMaxSlope; // WMA45 gần phẳng tại signal
|
||||
bool wmaRelaxPrior; // P4: chỉ cần WMA45 từng giảm/tăng, không cần dốc tối thiểu
|
||||
double maxEma9WmaGap; // EMA9 gần WMA45 tại cắt (chống dấu hiệu 1)
|
||||
};
|
||||
|
||||
bool Phase_Ema9TurningBuy(const int shift, const PhaseEntryConfig &cfg, const double &ema9[])
|
||||
{
|
||||
const int b = cfg.ema9SlopeBars;
|
||||
if(ema9[shift + b] <= 0.0)
|
||||
return false;
|
||||
return (ema9[shift] >= ema9[shift + b] - cfg.ema9SlopeTol);
|
||||
}
|
||||
|
||||
bool Phase_Ema9TurningSell(const int shift, const PhaseEntryConfig &cfg, const double &ema9[])
|
||||
{
|
||||
const int b = cfg.ema9SlopeBars;
|
||||
if(ema9[shift + b] <= 0.0)
|
||||
return false;
|
||||
return (ema9[shift] <= ema9[shift + b] + cfg.ema9SlopeTol);
|
||||
}
|
||||
|
||||
int Phase_CountRsiEma9Crosses(const int shift, const int lookback, const int rates_total,
|
||||
const double &rsi[], const double &ema9[])
|
||||
{
|
||||
int crosses = 0;
|
||||
const int end = MathMin(shift + lookback, rates_total - 2);
|
||||
for(int j = shift + 1; j < end; j++)
|
||||
{
|
||||
const bool up = (rsi[j+1] <= ema9[j+1]) && (rsi[j] > ema9[j]);
|
||||
const bool dn = (rsi[j+1] >= ema9[j+1]) && (rsi[j] < ema9[j]);
|
||||
if(up || dn)
|
||||
crosses++;
|
||||
}
|
||||
return crosses;
|
||||
}
|
||||
|
||||
int Phase_CountCoilBars(const int shift, const int lookback, const int rates_total,
|
||||
const double &rsi[], const double &ema9[], const double band)
|
||||
{
|
||||
int n = 0;
|
||||
const int end = MathMin(shift + lookback, rates_total - 1);
|
||||
for(int j = shift + 1; j <= end; j++)
|
||||
{
|
||||
if(MathAbs(rsi[j] - ema9[j]) <= band)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool Phase_FindMaxSpreadDown(const int shift, const int lookback, const int rates_total,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const double minSpread, double &maxSpread)
|
||||
{
|
||||
maxSpread = 0.0;
|
||||
const int end = MathMin(shift + lookback, rates_total - 1);
|
||||
for(int j = shift + 1; j <= end; j++)
|
||||
{
|
||||
if(rsi[j] >= ema9[j] || ema9[j] >= wma[j])
|
||||
continue;
|
||||
const double sp = wma[j] - rsi[j];
|
||||
if(sp > maxSpread)
|
||||
maxSpread = sp;
|
||||
}
|
||||
return (maxSpread >= minSpread - 1e-8);
|
||||
}
|
||||
|
||||
bool Phase_FindMaxSpreadUp(const int shift, const int lookback, const int rates_total,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const double minSpread, double &maxSpread)
|
||||
{
|
||||
maxSpread = 0.0;
|
||||
const int end = MathMin(shift + lookback, rates_total - 1);
|
||||
for(int j = shift + 1; j <= end; j++)
|
||||
{
|
||||
if(rsi[j] <= ema9[j] || ema9[j] <= wma[j])
|
||||
continue;
|
||||
const double sp = rsi[j] - wma[j];
|
||||
if(sp > maxSpread)
|
||||
maxSpread = sp;
|
||||
}
|
||||
return (maxSpread >= minSpread - 1e-8);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningBuyEx(const int shift, const int flatBars,
|
||||
const double flatMaxSlope, const double wasSlopeMin,
|
||||
const bool relaxPrior, const double &wma[])
|
||||
{
|
||||
const int b = MathMax(2, flatBars);
|
||||
const double recent = (wma[shift] - wma[shift + b]) / (double)b;
|
||||
const double prior = (wma[shift + b] - wma[shift + 2 * b]) / (double)b;
|
||||
if(relaxPrior)
|
||||
{
|
||||
if(prior > 0.0)
|
||||
return false;
|
||||
}
|
||||
else if(prior > -wasSlopeMin)
|
||||
return false;
|
||||
return (MathAbs(recent) <= flatMaxSlope);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningBuy(const int shift, const int flatBars,
|
||||
const double flatMaxSlope, const double wasSlopeMin,
|
||||
const double &wma[])
|
||||
{
|
||||
return Phase_Wma45FlatteningBuyEx(shift, flatBars, flatMaxSlope, wasSlopeMin, false, wma);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningSellEx(const int shift, const int flatBars,
|
||||
const double flatMaxSlope, const double wasSlopeMin,
|
||||
const bool relaxPrior, const double &wma[])
|
||||
{
|
||||
const int b = MathMax(2, flatBars);
|
||||
const double recent = (wma[shift] - wma[shift + b]) / (double)b;
|
||||
const double prior = (wma[shift + b] - wma[shift + 2 * b]) / (double)b;
|
||||
if(relaxPrior)
|
||||
{
|
||||
if(prior < 0.0)
|
||||
return false;
|
||||
}
|
||||
else if(prior < wasSlopeMin)
|
||||
return false;
|
||||
return (MathAbs(recent) <= flatMaxSlope);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningSell(const int shift, const int flatBars,
|
||||
const double flatMaxSlope, const double wasSlopeMin,
|
||||
const double &wma[])
|
||||
{
|
||||
return Phase_Wma45FlatteningSellEx(shift, flatBars, flatMaxSlope, wasSlopeMin, false, wma);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningBuyCfg(const int shift, const PhaseEntryConfig &cfg, const double &wma[])
|
||||
{
|
||||
return Phase_Wma45FlatteningBuyEx(shift, cfg.wmaFlatBars, cfg.wmaFlatMaxSlope,
|
||||
cfg.wmaWasSlopeMin, cfg.wmaRelaxPrior, wma);
|
||||
}
|
||||
|
||||
bool Phase_Wma45FlatteningSellCfg(const int shift, const PhaseEntryConfig &cfg, const double &wma[])
|
||||
{
|
||||
return Phase_Wma45FlatteningSellEx(shift, cfg.wmaFlatBars, cfg.wmaFlatMaxSlope,
|
||||
cfg.wmaWasSlopeMin, cfg.wmaRelaxPrior, wma);
|
||||
}
|
||||
|
||||
bool Phase_Ema9NearWmaBuy(const int shift, const double maxGap,
|
||||
const double &ema9[], const double &wma[])
|
||||
{
|
||||
if(ema9[shift] >= wma[shift])
|
||||
return false;
|
||||
return ((wma[shift] - ema9[shift]) <= maxGap + 1e-8);
|
||||
}
|
||||
|
||||
bool Phase_Ema9NearWmaSell(const int shift, const double maxGap,
|
||||
const double &ema9[], const double &wma[])
|
||||
{
|
||||
if(ema9[shift] <= wma[shift])
|
||||
return false;
|
||||
return ((ema9[shift] - wma[shift]) <= maxGap + 1e-8);
|
||||
}
|
||||
|
||||
bool Phase_BuyPasses(const int shift, const int rates_total,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const PhaseEntryConfig &cfg, string &failTag)
|
||||
{
|
||||
failTag = "";
|
||||
if(!cfg.enabled)
|
||||
return true;
|
||||
|
||||
double maxSp = 0.0;
|
||||
if(!Phase_FindMaxSpreadDown(shift, cfg.expandLookback, rates_total,
|
||||
rsi, ema9, wma, cfg.minExpandSpread, maxSp))
|
||||
{
|
||||
failTag = "P1-mở rộng ";
|
||||
return false;
|
||||
}
|
||||
|
||||
const int crosses = Phase_CountRsiEma9Crosses(shift, cfg.coilLookback, rates_total, rsi, ema9);
|
||||
if(crosses < cfg.minRsiEma9Crosses)
|
||||
{
|
||||
failTag = "P2-cuộn ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Ema9TurningBuy(shift, cfg, ema9))
|
||||
{
|
||||
failTag = "P3-EMA9↑ ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Wma45FlatteningBuyCfg(shift, cfg, wma))
|
||||
{
|
||||
failTag = "P4-WMA45 phẳng ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Ema9NearWmaBuy(shift, cfg.maxEma9WmaGap, ema9, wma))
|
||||
{
|
||||
failTag = "P5-EMA9 xa WMA45 ";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Phase_SellPasses(const int shift, const int rates_total,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const PhaseEntryConfig &cfg, string &failTag)
|
||||
{
|
||||
failTag = "";
|
||||
if(!cfg.enabled)
|
||||
return true;
|
||||
|
||||
double maxSp = 0.0;
|
||||
if(!Phase_FindMaxSpreadUp(shift, cfg.expandLookback, rates_total,
|
||||
rsi, ema9, wma, cfg.minExpandSpread, maxSp))
|
||||
{
|
||||
failTag = "P1-mở rộng ";
|
||||
return false;
|
||||
}
|
||||
|
||||
const int crosses = Phase_CountRsiEma9Crosses(shift, cfg.coilLookback, rates_total, rsi, ema9);
|
||||
if(crosses < cfg.minRsiEma9Crosses)
|
||||
{
|
||||
failTag = "P2-cuộn ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Ema9TurningSell(shift, cfg, ema9))
|
||||
{
|
||||
failTag = "P3-EMA9↓ ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Wma45FlatteningSellCfg(shift, cfg, wma))
|
||||
{
|
||||
failTag = "P4-WMA45 phẳng ";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Phase_Ema9NearWmaSell(shift, cfg.maxEma9WmaGap, ema9, wma))
|
||||
{
|
||||
failTag = "P5-EMA9 xa WMA45 ";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,542 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SignalDebug.mqh — đánh dấu cross RSI×WMA45: OK / SKIP + tooltip |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef RSI_MOM_SIGNAL_DEBUG_MQH
|
||||
#define RSI_MOM_SIGNAL_DEBUG_MQH
|
||||
|
||||
#include <RsiMom/PhaseEntry.mqh>
|
||||
|
||||
struct SignalEvalResult
|
||||
{
|
||||
bool signalOk;
|
||||
bool tradeOk;
|
||||
string summary;
|
||||
string detail;
|
||||
string failTag; // lý do chính: P1-mở rộng, P2-cuộn, EMA200, ATR, …
|
||||
};
|
||||
|
||||
void SignalEval_Append(string &detail, const string part)
|
||||
{
|
||||
if(StringLen(detail) > 0)
|
||||
detail += " | ";
|
||||
detail += part;
|
||||
}
|
||||
|
||||
void SignalEval_SetFail(string &failTag, const string tag)
|
||||
{
|
||||
if(StringLen(failTag) == 0 && StringLen(tag) > 0)
|
||||
failTag = tag;
|
||||
}
|
||||
|
||||
string SignalEval_FormatTooltip(const bool isBuy, const SignalEvalResult &ev)
|
||||
{
|
||||
const string side = isBuy ? "BUY" : "SELL";
|
||||
string tip = side + " @ " + ev.summary;
|
||||
if(StringLen(ev.failTag) > 0 && !ev.signalOk)
|
||||
tip += "\nFail: " + ev.failTag;
|
||||
tip += "\n---\n" + ev.detail;
|
||||
return tip;
|
||||
}
|
||||
|
||||
string SignalEval_ChartLabel(const SignalEvalResult &ev)
|
||||
{
|
||||
if(ev.tradeOk)
|
||||
return "OK";
|
||||
if(ev.signalOk)
|
||||
return "SIG";
|
||||
if(StringLen(ev.failTag) > 0)
|
||||
{
|
||||
string tag = ev.failTag;
|
||||
StringTrimRight(tag);
|
||||
StringTrimLeft(tag);
|
||||
return "SKIP " + tag;
|
||||
}
|
||||
return "SKIP";
|
||||
}
|
||||
|
||||
SignalEvalResult Signal_EvaluateBuyAt(const int shift, const int rates_total, const int trendN,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const double &closeArr[], const double &ema200Arr[],
|
||||
const PhaseEntryConfig &phaseCfg,
|
||||
const bool phaseFilterEnabled,
|
||||
const bool trendFilterEnabled,
|
||||
const bool atrFilterEnabled,
|
||||
const bool rsiObOsFilterEnabled,
|
||||
const double rsiOverbought,
|
||||
const double rsiOversold,
|
||||
const bool sessionFilterEnabled,
|
||||
const bool atrExpAtBar,
|
||||
const bool sessionAtBar,
|
||||
const string sessionFailWhy = "")
|
||||
{
|
||||
SignalEvalResult r;
|
||||
r.signalOk = false;
|
||||
r.tradeOk = false;
|
||||
r.summary = "";
|
||||
r.detail = "";
|
||||
r.failTag = "";
|
||||
|
||||
const bool cross = (shift + 1 < rates_total) &&
|
||||
(rsi[shift + 1] <= wma[shift + 1]) && (rsi[shift] > wma[shift + 1]);
|
||||
if(!cross)
|
||||
{
|
||||
r.summary = "no cross";
|
||||
SignalEval_Append(r.detail, "RSI chưa cắt lên WMA45");
|
||||
return r;
|
||||
}
|
||||
|
||||
SignalEval_Append(r.detail, "RSI↑WMA45");
|
||||
|
||||
bool coreOk = true;
|
||||
string phaseFail = "";
|
||||
if(phaseFilterEnabled)
|
||||
{
|
||||
coreOk = Phase_BuyPasses(shift, rates_total, rsi, ema9, wma, phaseCfg, phaseFail);
|
||||
double maxSp = 0.0;
|
||||
const bool p1 = Phase_FindMaxSpreadDown(shift, phaseCfg.expandLookback, rates_total,
|
||||
rsi, ema9, wma, phaseCfg.minExpandSpread, maxSp);
|
||||
const int xc = Phase_CountRsiEma9Crosses(shift, phaseCfg.coilLookback, rates_total, rsi, ema9);
|
||||
const bool p3 = Phase_Ema9TurningBuy(shift, phaseCfg, ema9);
|
||||
const bool p4 = Phase_Wma45FlatteningBuyCfg(shift, phaseCfg, wma);
|
||||
const double gap = wma[shift] - ema9[shift];
|
||||
const bool p5 = Phase_Ema9NearWmaBuy(shift, phaseCfg.maxEma9WmaGap, ema9, wma);
|
||||
|
||||
SignalEval_Append(r.detail, p1 ? StringFormat("P1:OK(sp=%.1f)", maxSp) : "P1:FAIL");
|
||||
SignalEval_Append(r.detail, StringFormat("P2:%s(x=%d)", xc >= phaseCfg.minRsiEma9Crosses ? "OK" : "FAIL", xc));
|
||||
SignalEval_Append(r.detail, p3 ? "P3:OK" : "P3:FAIL");
|
||||
SignalEval_Append(r.detail, p4 ? "P4:OK" : "P4:FAIL");
|
||||
SignalEval_Append(r.detail, p5 ? StringFormat("P5:OK(gap=%.1f)", gap) : StringFormat("P5:FAIL(gap=%.1f)", gap));
|
||||
|
||||
if(!p1) SignalEval_SetFail(r.failTag, "P1-mở rộng");
|
||||
else if(xc < phaseCfg.minRsiEma9Crosses)
|
||||
SignalEval_SetFail(r.failTag, "P2-cuộn");
|
||||
else if(!p3) SignalEval_SetFail(r.failTag, "P3-EMA9↑");
|
||||
else if(!p4) SignalEval_SetFail(r.failTag, "P4-WMA45 phẳng");
|
||||
else if(!p5) SignalEval_SetFail(r.failTag, "P5-EMA9 xa WMA45");
|
||||
else if(!coreOk && StringLen(phaseFail) > 0)
|
||||
SignalEval_SetFail(r.failTag, phaseFail);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreOk = (ema9[shift] < wma[shift]);
|
||||
SignalEval_Append(r.detail, coreOk ? "EMA9<WMA45" : "EMA9>=WMA45 FAIL");
|
||||
if(!coreOk)
|
||||
SignalEval_SetFail(r.failTag, "EMA9>=WMA45");
|
||||
}
|
||||
|
||||
bool trendUp = true;
|
||||
if(!trendFilterEnabled)
|
||||
SignalEval_Append(r.detail, "EMA200:OFF");
|
||||
else
|
||||
{
|
||||
for(int k = 0; k < trendN; k++)
|
||||
{
|
||||
const int idx = shift + k;
|
||||
if(closeArr[idx] <= ema200Arr[idx])
|
||||
{
|
||||
trendUp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SignalEval_Append(r.detail, trendUp ? "EMA200:OK" : "EMA200:FAIL");
|
||||
if(!trendUp)
|
||||
SignalEval_SetFail(r.failTag, "EMA200");
|
||||
}
|
||||
|
||||
SignalEval_Append(r.detail, !atrFilterEnabled ? "ATR:OFF" : (atrExpAtBar ? "ATR:OK" : "ATR:FAIL"));
|
||||
if(atrFilterEnabled && !atrExpAtBar)
|
||||
SignalEval_SetFail(r.failTag, "ATR co");
|
||||
|
||||
bool rsiOk = true;
|
||||
if(!rsiObOsFilterEnabled)
|
||||
SignalEval_Append(r.detail, "RSI OB/OS:OFF");
|
||||
else
|
||||
{
|
||||
rsiOk = (rsi[shift] < rsiOverbought);
|
||||
if(rsiOk)
|
||||
SignalEval_Append(r.detail, StringFormat("RSI:OK(%.1f<%g)", rsi[shift], rsiOverbought));
|
||||
else
|
||||
{
|
||||
SignalEval_Append(r.detail, StringFormat("RSI:OB %.1f>=%g", rsi[shift], rsiOverbought));
|
||||
SignalEval_SetFail(r.failTag, "RSI quá mua");
|
||||
}
|
||||
}
|
||||
|
||||
const bool envBar = (!sessionFilterEnabled) || sessionAtBar;
|
||||
if(!sessionFilterEnabled)
|
||||
SignalEval_Append(r.detail, "Phiên:OFF");
|
||||
else if(envBar)
|
||||
SignalEval_Append(r.detail, "Phiên(bar):OK");
|
||||
else
|
||||
{
|
||||
SignalEval_Append(r.detail, StringLen(sessionFailWhy) > 0
|
||||
? "Phiên(bar):FAIL " + sessionFailWhy
|
||||
: "Phiên(bar):FAIL");
|
||||
SignalEval_SetFail(r.failTag, "Phiên");
|
||||
}
|
||||
|
||||
r.signalOk = cross && coreOk && trendUp
|
||||
&& (!atrFilterEnabled || atrExpAtBar) && rsiOk && envBar;
|
||||
r.tradeOk = r.signalOk;
|
||||
|
||||
if(r.signalOk && r.tradeOk)
|
||||
r.summary = "HỢP LỆ → vào lệnh";
|
||||
else if(r.signalOk)
|
||||
r.summary = "Tín hiệu OK | chờ đặt lệnh";
|
||||
else
|
||||
r.summary = StringLen(r.failTag) > 0
|
||||
? "SKIP → " + r.failTag
|
||||
: "SKIP";
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
SignalEvalResult Signal_EvaluateSellAt(const int shift, const int rates_total, const int trendN,
|
||||
const double &rsi[], const double &ema9[], const double &wma[],
|
||||
const double &closeArr[], const double &ema200Arr[],
|
||||
const PhaseEntryConfig &phaseCfg,
|
||||
const bool phaseFilterEnabled,
|
||||
const bool trendFilterEnabled,
|
||||
const bool atrFilterEnabled,
|
||||
const bool rsiObOsFilterEnabled,
|
||||
const double rsiOverbought,
|
||||
const double rsiOversold,
|
||||
const bool sessionFilterEnabled,
|
||||
const bool atrExpAtBar,
|
||||
const bool sessionAtBar,
|
||||
const string sessionFailWhy = "")
|
||||
{
|
||||
SignalEvalResult r;
|
||||
r.signalOk = false;
|
||||
r.tradeOk = false;
|
||||
r.summary = "";
|
||||
r.detail = "";
|
||||
r.failTag = "";
|
||||
|
||||
const bool cross = (shift + 1 < rates_total) &&
|
||||
(rsi[shift + 1] >= wma[shift + 1]) && (rsi[shift] < wma[shift + 1]);
|
||||
if(!cross)
|
||||
{
|
||||
r.summary = "no cross";
|
||||
SignalEval_Append(r.detail, "RSI chưa cắt xuống WMA45");
|
||||
return r;
|
||||
}
|
||||
|
||||
SignalEval_Append(r.detail, "RSI↓WMA45");
|
||||
|
||||
bool coreOk = true;
|
||||
string phaseFail = "";
|
||||
if(phaseFilterEnabled)
|
||||
{
|
||||
coreOk = Phase_SellPasses(shift, rates_total, rsi, ema9, wma, phaseCfg, phaseFail);
|
||||
double maxSp = 0.0;
|
||||
const bool p1 = Phase_FindMaxSpreadUp(shift, phaseCfg.expandLookback, rates_total,
|
||||
rsi, ema9, wma, phaseCfg.minExpandSpread, maxSp);
|
||||
const int xc = Phase_CountRsiEma9Crosses(shift, phaseCfg.coilLookback, rates_total, rsi, ema9);
|
||||
const bool p3 = Phase_Ema9TurningSell(shift, phaseCfg, ema9);
|
||||
const bool p4 = Phase_Wma45FlatteningSellCfg(shift, phaseCfg, wma);
|
||||
const double gap = ema9[shift] - wma[shift];
|
||||
const bool p5 = Phase_Ema9NearWmaSell(shift, phaseCfg.maxEma9WmaGap, ema9, wma);
|
||||
|
||||
SignalEval_Append(r.detail, p1 ? StringFormat("P1:OK(sp=%.1f)", maxSp) : "P1:FAIL");
|
||||
SignalEval_Append(r.detail, StringFormat("P2:%s(x=%d)", xc >= phaseCfg.minRsiEma9Crosses ? "OK" : "FAIL", xc));
|
||||
SignalEval_Append(r.detail, p3 ? "P3:OK" : "P3:FAIL");
|
||||
SignalEval_Append(r.detail, p4 ? "P4:OK" : "P4:FAIL");
|
||||
SignalEval_Append(r.detail, p5 ? StringFormat("P5:OK(gap=%.1f)", gap) : StringFormat("P5:FAIL(gap=%.1f)", gap));
|
||||
|
||||
if(!p1) SignalEval_SetFail(r.failTag, "P1-mở rộng");
|
||||
else if(xc < phaseCfg.minRsiEma9Crosses) SignalEval_SetFail(r.failTag, "P2-cuộn");
|
||||
else if(!p3) SignalEval_SetFail(r.failTag, "P3-EMA9↓");
|
||||
else if(!p4) SignalEval_SetFail(r.failTag, "P4-WMA45 phẳng");
|
||||
else if(!p5) SignalEval_SetFail(r.failTag, "P5-EMA9 xa WMA45");
|
||||
else if(!coreOk && StringLen(phaseFail) > 0)
|
||||
SignalEval_SetFail(r.failTag, phaseFail);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreOk = (ema9[shift] > wma[shift]);
|
||||
SignalEval_Append(r.detail, coreOk ? "EMA9>WMA45" : "EMA9<=WMA45 FAIL");
|
||||
if(!coreOk)
|
||||
SignalEval_SetFail(r.failTag, "EMA9<=WMA45");
|
||||
}
|
||||
|
||||
bool trendDown = true;
|
||||
if(!trendFilterEnabled)
|
||||
SignalEval_Append(r.detail, "EMA200:OFF");
|
||||
else
|
||||
{
|
||||
for(int k = 0; k < trendN; k++)
|
||||
{
|
||||
const int idx = shift + k;
|
||||
if(closeArr[idx] >= ema200Arr[idx])
|
||||
{
|
||||
trendDown = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SignalEval_Append(r.detail, trendDown ? "EMA200:OK" : "EMA200:FAIL");
|
||||
if(!trendDown)
|
||||
SignalEval_SetFail(r.failTag, "EMA200");
|
||||
}
|
||||
|
||||
SignalEval_Append(r.detail, !atrFilterEnabled ? "ATR:OFF" : (atrExpAtBar ? "ATR:OK" : "ATR:FAIL"));
|
||||
if(atrFilterEnabled && !atrExpAtBar)
|
||||
SignalEval_SetFail(r.failTag, "ATR co");
|
||||
|
||||
bool rsiOk = true;
|
||||
if(!rsiObOsFilterEnabled)
|
||||
SignalEval_Append(r.detail, "RSI OB/OS:OFF");
|
||||
else
|
||||
{
|
||||
rsiOk = (rsi[shift] > rsiOversold);
|
||||
if(rsiOk)
|
||||
SignalEval_Append(r.detail, StringFormat("RSI:OK(%.1f>%g)", rsi[shift], rsiOversold));
|
||||
else
|
||||
{
|
||||
SignalEval_Append(r.detail, StringFormat("RSI:OS %.1f<=%g", rsi[shift], rsiOversold));
|
||||
SignalEval_SetFail(r.failTag, "RSI quá bán");
|
||||
}
|
||||
}
|
||||
|
||||
const bool envBar = (!sessionFilterEnabled) || sessionAtBar;
|
||||
if(!sessionFilterEnabled)
|
||||
SignalEval_Append(r.detail, "Phiên:OFF");
|
||||
else if(envBar)
|
||||
SignalEval_Append(r.detail, "Phiên(bar):OK");
|
||||
else
|
||||
{
|
||||
SignalEval_Append(r.detail, StringLen(sessionFailWhy) > 0
|
||||
? "Phiên(bar):FAIL " + sessionFailWhy
|
||||
: "Phiên(bar):FAIL");
|
||||
SignalEval_SetFail(r.failTag, "Phiên");
|
||||
}
|
||||
|
||||
r.signalOk = cross && coreOk && trendDown && (!atrFilterEnabled || atrExpAtBar) && rsiOk && envBar;
|
||||
r.tradeOk = r.signalOk;
|
||||
|
||||
if(r.signalOk && r.tradeOk)
|
||||
r.summary = "HỢP LỆ → vào lệnh";
|
||||
else if(r.signalOk)
|
||||
r.summary = "Tín hiệu OK | chờ đặt lệnh";
|
||||
else
|
||||
r.summary = StringLen(r.failTag) > 0
|
||||
? "SKIP → " + r.failTag
|
||||
: "SKIP";
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
const string DBG_HOVER_LABEL = "RsiMomEA_dbg_hover";
|
||||
|
||||
void Signal_DebugSetTooltip(const long chartId, const string name, const string tip)
|
||||
{
|
||||
ObjectSetString(chartId, name, OBJPROP_TOOLTIP, tip);
|
||||
}
|
||||
|
||||
void Signal_DebugEnsureHoverLabel(const long chartId)
|
||||
{
|
||||
if(ObjectFind(chartId, DBG_HOVER_LABEL) >= 0)
|
||||
return;
|
||||
|
||||
ObjectCreate(chartId, DBG_HOVER_LABEL, OBJ_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_CORNER, CORNER_LEFT_LOWER);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_XDISTANCE, 8);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_YDISTANCE, 72);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_FONTSIZE, 8);
|
||||
ObjectSetString (chartId, DBG_HOVER_LABEL, OBJPROP_FONT, "Consolas");
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_COLOR, clrWhite);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_BGCOLOR, clrBlack);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(chartId, DBG_HOVER_LABEL, OBJPROP_HIDDEN, true);
|
||||
ObjectSetString (chartId, DBG_HOVER_LABEL, OBJPROP_TEXT, "");
|
||||
}
|
||||
|
||||
void Signal_DebugShowHoverHint(const long chartId, const string tip)
|
||||
{
|
||||
if(StringLen(tip) == 0)
|
||||
return;
|
||||
Signal_DebugEnsureHoverLabel(chartId);
|
||||
ObjectSetString(chartId, DBG_HOVER_LABEL, OBJPROP_TEXT, tip);
|
||||
ChartRedraw(chartId);
|
||||
}
|
||||
|
||||
void Signal_DebugHideHoverHint(const long chartId)
|
||||
{
|
||||
if(ObjectFind(chartId, DBG_HOVER_LABEL) < 0)
|
||||
return;
|
||||
const string cur = ObjectGetString(chartId, DBG_HOVER_LABEL, OBJPROP_TEXT);
|
||||
if(StringLen(cur) == 0)
|
||||
return;
|
||||
ObjectSetString(chartId, DBG_HOVER_LABEL, OBJPROP_TEXT, "");
|
||||
ChartRedraw(chartId);
|
||||
}
|
||||
|
||||
bool Signal_DebugObjectHasPrefix(const string name, const string prefix)
|
||||
{
|
||||
return (StringLen(name) >= StringLen(prefix) && StringSubstr(name, 0, StringLen(prefix)) == prefix);
|
||||
}
|
||||
|
||||
bool Signal_DebugReadTipFromMark(const long chartId, const string objName, string &tip)
|
||||
{
|
||||
tip = "";
|
||||
if(ObjectFind(chartId, objName) < 0)
|
||||
return false;
|
||||
tip = ObjectGetString(chartId, objName, OBJPROP_TOOLTIP);
|
||||
return (StringLen(tip) > 0);
|
||||
}
|
||||
|
||||
bool Signal_DebugFindTipAtMouse(const long chartId, const string prefix,
|
||||
const int mouseX, const int mouseY, string &tip)
|
||||
{
|
||||
tip = "";
|
||||
datetime tMouse = 0;
|
||||
double pMouse = 0.0;
|
||||
int subwin = 0;
|
||||
if(!ChartXYToTimePrice(chartId, mouseX, mouseY, subwin, tMouse, pMouse))
|
||||
return false;
|
||||
|
||||
const int total = ObjectsTotal(chartId, 0, -1);
|
||||
for(int i = total - 1; i >= 0; i--)
|
||||
{
|
||||
const string name = ObjectName(chartId, i, 0, -1);
|
||||
if(!Signal_DebugObjectHasPrefix(name, prefix))
|
||||
continue;
|
||||
|
||||
const bool isHit = (StringFind(name, "_hit") >= 0);
|
||||
const bool isArrow = (StringFind(name, "_ar") >= 0);
|
||||
if(!isHit && !isArrow)
|
||||
continue;
|
||||
|
||||
if(isHit)
|
||||
{
|
||||
const datetime t0 = (datetime)ObjectGetInteger(chartId, name, OBJPROP_TIME, 0);
|
||||
const datetime t1 = (datetime)ObjectGetInteger(chartId, name, OBJPROP_TIME, 1);
|
||||
const double p0 = ObjectGetDouble(chartId, name, OBJPROP_PRICE, 0);
|
||||
const double p1 = ObjectGetDouble(chartId, name, OBJPROP_PRICE, 1);
|
||||
if(tMouse < t0 || tMouse > t1)
|
||||
continue;
|
||||
if(pMouse < MathMin(p0, p1) || pMouse > MathMax(p0, p1))
|
||||
continue;
|
||||
return Signal_DebugReadTipFromMark(chartId, name, tip);
|
||||
}
|
||||
|
||||
if(isArrow)
|
||||
{
|
||||
const datetime ta = (datetime)ObjectGetInteger(chartId, name, OBJPROP_TIME, 0);
|
||||
const int halfSec = MathMax(1, PeriodSeconds(_Period) / 2);
|
||||
if(MathAbs((int)(tMouse - ta)) > halfSec)
|
||||
continue;
|
||||
return Signal_DebugReadTipFromMark(chartId, name, tip);
|
||||
}
|
||||
}
|
||||
|
||||
const int sh = iBarShift(_Symbol, _Period, tMouse, false);
|
||||
if(sh < 0)
|
||||
return false;
|
||||
|
||||
for(int d = -1; d <= 1; d++)
|
||||
{
|
||||
const int s = sh + d;
|
||||
if(s < 0)
|
||||
continue;
|
||||
const datetime bt = iTime(_Symbol, _Period, s);
|
||||
const string btKey = IntegerToString((int)bt);
|
||||
const string tryN[4];
|
||||
tryN[0] = prefix + btKey + "_B_hit";
|
||||
tryN[1] = prefix + btKey + "_S_hit";
|
||||
tryN[2] = prefix + btKey + "_B_ar";
|
||||
tryN[3] = prefix + btKey + "_S_ar";
|
||||
for(int k = 0; k < 4; k++)
|
||||
{
|
||||
if(Signal_DebugReadTipFromMark(chartId, tryN[k], tip))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Signal_DebugOnMouseMove(const long chartId, const string prefix, const int mouseX, const int mouseY)
|
||||
{
|
||||
string tip = "";
|
||||
if(Signal_DebugFindTipAtMouse(chartId, prefix, mouseX, mouseY, tip))
|
||||
Signal_DebugShowHoverHint(chartId, tip);
|
||||
else
|
||||
Signal_DebugHideHoverHint(chartId);
|
||||
}
|
||||
|
||||
void Signal_DebugDrawMark(const long chartId, const string prefix,
|
||||
const datetime barTime, const double barHigh, const double barLow,
|
||||
const double markPrice, const bool isBuy, const SignalEvalResult &ev)
|
||||
{
|
||||
const string base = prefix + IntegerToString((int)barTime) + (isBuy ? "_B" : "_S");
|
||||
const string arrowName = base + "_ar";
|
||||
const string hitName = base + "_hit";
|
||||
|
||||
// Giữ dấu cũ — không xóa/vẽ lại khi có nến mới (hover xem lý do skip sau)
|
||||
if(ObjectFind(chartId, arrowName) >= 0)
|
||||
return;
|
||||
|
||||
const string tip = SignalEval_FormatTooltip(isBuy, ev);
|
||||
|
||||
color clr = clrDimGray;
|
||||
int arrowCode = 251;
|
||||
int arrowW = 2;
|
||||
if(ev.signalOk && ev.tradeOk)
|
||||
{
|
||||
clr = clrLime;
|
||||
arrowCode = isBuy ? 233 : 234;
|
||||
arrowW = 1;
|
||||
}
|
||||
else if(ev.signalOk)
|
||||
{
|
||||
clr = clrGold;
|
||||
arrowCode = isBuy ? 233 : 234;
|
||||
arrowW = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
clr = clrOrangeRed;
|
||||
arrowCode = 251;
|
||||
arrowW = 3;
|
||||
}
|
||||
|
||||
if(ObjectCreate(chartId, arrowName, OBJ_ARROW, 0, barTime, markPrice))
|
||||
{
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_ARROWCODE, arrowCode);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_WIDTH, arrowW);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_ANCHOR, isBuy ? ANCHOR_TOP : ANCHOR_BOTTOM);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_SELECTABLE, true);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_HIDDEN, true);
|
||||
ObjectSetInteger(chartId, arrowName, OBJPROP_BACK, false);
|
||||
Signal_DebugSetTooltip(chartId, arrowName, tip);
|
||||
}
|
||||
|
||||
const datetime tEnd = barTime + (datetime)PeriodSeconds(_Period);
|
||||
const double pad = MathMax(_Point * 8.0, (barHigh - barLow) * 0.08);
|
||||
const double rHi = barHigh + pad;
|
||||
const double rLo = barLow - pad;
|
||||
const color hitFill = ColorToARGB(clr, (uchar)24);
|
||||
|
||||
if(ObjectCreate(chartId, hitName, OBJ_RECTANGLE, 0, barTime, rHi, tEnd, rLo))
|
||||
{
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_COLOR, hitFill);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_STYLE, STYLE_SOLID);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_SELECTABLE, true);
|
||||
ObjectSetInteger(chartId, hitName, OBJPROP_HIDDEN, true);
|
||||
Signal_DebugSetTooltip(chartId, hitName, tip);
|
||||
}
|
||||
}
|
||||
|
||||
void Signal_DebugConfigureChart(const long chartId)
|
||||
{
|
||||
ChartSetInteger(chartId, CHART_SHOW_OBJECT_DESCR, false);
|
||||
ChartSetInteger(chartId, CHART_EVENT_MOUSE_MOVE, true);
|
||||
Signal_DebugEnsureHoverLabel(chartId);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,411 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| TradeJournal.mqh — CSV lệnh đóng + tổng hợp tháng (RsiMomentumEA)|
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef RSI_MOM_TRADE_JOURNAL_MQH
|
||||
#define RSI_MOM_TRADE_JOURNAL_MQH
|
||||
|
||||
struct RsiMomMonthRow
|
||||
{
|
||||
string ym;
|
||||
int trades;
|
||||
int wins;
|
||||
int losses;
|
||||
double netProfit;
|
||||
double grossProfit;
|
||||
double grossLoss;
|
||||
int slCount;
|
||||
int tpCount;
|
||||
int otherCount;
|
||||
};
|
||||
|
||||
int g_rsiMomJournalHandle = INVALID_HANDLE;
|
||||
RsiMomMonthRow g_rsiMomMonths[];
|
||||
int g_rsiMomMonthCount = 0;
|
||||
|
||||
string RsiMomJournal_TradesPath(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
return StringFormat("RsiMomEA\\journal_%s_%s.csv", sym, EnumToString(tf));
|
||||
}
|
||||
|
||||
string RsiMomJournal_SummaryPath(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
return StringFormat("RsiMomEA\\summary_%s_%s.csv", sym, EnumToString(tf));
|
||||
}
|
||||
|
||||
int RsiMomJournal_FindMonth(const string ym)
|
||||
{
|
||||
for(int i = 0; i < g_rsiMomMonthCount; i++)
|
||||
{
|
||||
if(g_rsiMomMonths[i].ym == ym)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void RsiMomJournal_AddMonth(const string ym, const double profit, const string exitTag)
|
||||
{
|
||||
int idx = RsiMomJournal_FindMonth(ym);
|
||||
if(idx < 0)
|
||||
{
|
||||
idx = g_rsiMomMonthCount;
|
||||
g_rsiMomMonthCount++;
|
||||
ArrayResize(g_rsiMomMonths, g_rsiMomMonthCount);
|
||||
g_rsiMomMonths[idx].ym = ym;
|
||||
g_rsiMomMonths[idx].trades = 0;
|
||||
g_rsiMomMonths[idx].wins = 0;
|
||||
g_rsiMomMonths[idx].losses = 0;
|
||||
g_rsiMomMonths[idx].netProfit = 0.0;
|
||||
g_rsiMomMonths[idx].grossProfit = 0.0;
|
||||
g_rsiMomMonths[idx].grossLoss = 0.0;
|
||||
g_rsiMomMonths[idx].slCount = 0;
|
||||
g_rsiMomMonths[idx].tpCount = 0;
|
||||
g_rsiMomMonths[idx].otherCount = 0;
|
||||
}
|
||||
|
||||
RsiMomMonthRow r = g_rsiMomMonths[idx];
|
||||
r.trades++;
|
||||
r.netProfit += profit;
|
||||
if(profit > 0.0)
|
||||
{
|
||||
r.wins++;
|
||||
r.grossProfit += profit;
|
||||
}
|
||||
else if(profit < 0.0)
|
||||
{
|
||||
r.losses++;
|
||||
r.grossLoss += profit;
|
||||
}
|
||||
|
||||
if(exitTag == "SL")
|
||||
r.slCount++;
|
||||
else if(exitTag == "TP")
|
||||
r.tpCount++;
|
||||
else
|
||||
r.otherCount++;
|
||||
|
||||
g_rsiMomMonths[idx] = r;
|
||||
}
|
||||
|
||||
bool RsiMomJournal_DeleteIfExists(const string path)
|
||||
{
|
||||
if(!FileIsExist(path, FILE_COMMON))
|
||||
return false;
|
||||
return FileDelete(path, FILE_COMMON);
|
||||
}
|
||||
|
||||
bool RsiMomJournal_ResetFiles(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
RsiMomJournal_CloseFiles();
|
||||
RsiMomJournal_DeleteIfExists(RsiMomJournal_TradesPath(sym, tf));
|
||||
RsiMomJournal_DeleteIfExists(RsiMomJournal_SummaryPath(sym, tf));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsiMomJournal_OpenTrades(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
if(g_rsiMomJournalHandle != INVALID_HANDLE)
|
||||
return true;
|
||||
|
||||
FolderCreate("RsiMomEA", FILE_COMMON);
|
||||
const string path = RsiMomJournal_TradesPath(sym, tf);
|
||||
const bool exists = FileIsExist(path, FILE_COMMON);
|
||||
|
||||
g_rsiMomJournalHandle = FileOpen(path, FILE_READ | FILE_WRITE | FILE_CSV | FILE_COMMON | FILE_ANSI, ',');
|
||||
if(g_rsiMomJournalHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("[RsiMomEA] Journal: không mở file ", path, " err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!exists || FileSize(g_rsiMomJournalHandle) == 0)
|
||||
{
|
||||
FileWrite(g_rsiMomJournalHandle,
|
||||
"close_time", "year_month", "profit", "dir", "exit",
|
||||
"entry_time", "entry_price", "sl", "tp", "volume",
|
||||
"risk_pts", "profit_r",
|
||||
"rsi", "wma45", "ema200", "close_entry", "above_ema200",
|
||||
"atr", "atr_ratio", "atr_exp_ok",
|
||||
"session_ok", "spread_pts",
|
||||
"symbol", "timeframe", "magic");
|
||||
}
|
||||
else
|
||||
{
|
||||
FileSeek(g_rsiMomJournalHandle, 0, SEEK_END);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RsiMomJournal_CloseFiles()
|
||||
{
|
||||
if(g_rsiMomJournalHandle != INVALID_HANDLE)
|
||||
{
|
||||
FileClose(g_rsiMomJournalHandle);
|
||||
g_rsiMomJournalHandle = INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
bool RsiMomJournal_Copy1(const int handle, const int shift, double &v)
|
||||
{
|
||||
v = 0.0;
|
||||
if(handle == INVALID_HANDLE || shift < 0)
|
||||
return false;
|
||||
double buf[];
|
||||
ArraySetAsSeries(buf, true);
|
||||
if(CopyBuffer(handle, 0, shift, 1, buf) < 1)
|
||||
return false;
|
||||
v = buf[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsiMomJournal_AtrRatioAt(const int hAtr, const int shift,
|
||||
const int cmpBars, const double minRatio,
|
||||
double &atrNow, double &ratio, bool &expOk)
|
||||
{
|
||||
atrNow = 0.0;
|
||||
ratio = 0.0;
|
||||
expOk = false;
|
||||
double atrRef = 0.0;
|
||||
if(!RsiMomJournal_Copy1(hAtr, shift, atrNow) || !RsiMomJournal_Copy1(hAtr, shift + cmpBars, atrRef))
|
||||
return false;
|
||||
if(atrRef <= 0.0)
|
||||
return false;
|
||||
ratio = atrNow / atrRef;
|
||||
expOk = (ratio >= MathMax(1.0, minRatio) - 1e-8);
|
||||
return true;
|
||||
}
|
||||
|
||||
string RsiMomJournal_ExitTag(const long dealReason, const double profit)
|
||||
{
|
||||
if(dealReason == DEAL_REASON_SL)
|
||||
return "SL";
|
||||
if(dealReason == DEAL_REASON_TP)
|
||||
return "TP";
|
||||
if(profit >= 0.0)
|
||||
return "WIN";
|
||||
return "LOSS";
|
||||
}
|
||||
|
||||
bool RsiMomJournal_FindEntryDeal(const ulong positionId,
|
||||
ulong &entryDeal,
|
||||
datetime &entryTime,
|
||||
long &posType,
|
||||
double &entryPrice,
|
||||
double &sl,
|
||||
double &tp,
|
||||
double &volume)
|
||||
{
|
||||
entryDeal = 0;
|
||||
entryTime = 0;
|
||||
posType = 0;
|
||||
entryPrice = sl = tp = volume = 0.0;
|
||||
|
||||
if(positionId == 0 || !HistorySelectByPosition(positionId))
|
||||
return false;
|
||||
|
||||
const int total = HistoryDealsTotal();
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
const ulong d = HistoryDealGetTicket(i);
|
||||
if(d == 0 || !HistoryDealSelect(d))
|
||||
continue;
|
||||
if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_IN)
|
||||
continue;
|
||||
|
||||
entryDeal = d;
|
||||
entryTime = (datetime)HistoryDealGetInteger(d, DEAL_TIME);
|
||||
posType = HistoryDealGetInteger(d, DEAL_TYPE);
|
||||
entryPrice = HistoryDealGetDouble(d, DEAL_PRICE);
|
||||
volume = HistoryDealGetDouble(d, DEAL_VOLUME);
|
||||
|
||||
const ulong orderTicket = (ulong)HistoryDealGetInteger(d, DEAL_ORDER);
|
||||
if(orderTicket > 0 && HistoryOrderSelect(orderTicket))
|
||||
{
|
||||
sl = HistoryOrderGetDouble(orderTicket, ORDER_SL);
|
||||
tp = HistoryOrderGetDouble(orderTicket, ORDER_TP);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RsiMomJournal_WriteSummary(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
if(g_rsiMomMonthCount <= 0)
|
||||
return;
|
||||
|
||||
const string path = RsiMomJournal_SummaryPath(sym, tf);
|
||||
const int h = FileOpen(path, FILE_WRITE | FILE_CSV | FILE_COMMON | FILE_ANSI, ',');
|
||||
if(h == INVALID_HANDLE)
|
||||
{
|
||||
Print("[RsiMomEA] Summary CSV lỗi ", path, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
FileWrite(h,
|
||||
"year_month", "trades", "wins", "losses", "winrate_pct",
|
||||
"net_profit", "gross_profit", "gross_loss", "avg_profit",
|
||||
"sl_count", "tp_count", "other_count");
|
||||
|
||||
for(int i = 0; i < g_rsiMomMonthCount; i++)
|
||||
{
|
||||
const RsiMomMonthRow r = g_rsiMomMonths[i];
|
||||
double winrate = 0.0;
|
||||
if(r.trades > 0)
|
||||
winrate = 100.0 * (double)r.wins / (double)r.trades;
|
||||
const double avg = (r.trades > 0) ? r.netProfit / (double)r.trades : 0.0;
|
||||
|
||||
FileWrite(h,
|
||||
r.ym,
|
||||
IntegerToString(r.trades),
|
||||
IntegerToString(r.wins),
|
||||
IntegerToString(r.losses),
|
||||
DoubleToString(winrate, 2),
|
||||
DoubleToString(r.netProfit, 2),
|
||||
DoubleToString(r.grossProfit, 2),
|
||||
DoubleToString(r.grossLoss, 2),
|
||||
DoubleToString(avg, 2),
|
||||
IntegerToString(r.slCount),
|
||||
IntegerToString(r.tpCount),
|
||||
IntegerToString(r.otherCount));
|
||||
}
|
||||
|
||||
FileClose(h);
|
||||
Print("[RsiMomEA] Summary CSV → ", path, " (FILE_COMMON, ", g_rsiMomMonthCount, " tháng)");
|
||||
}
|
||||
|
||||
void RsiMomJournal_ResetMonths()
|
||||
{
|
||||
ArrayResize(g_rsiMomMonths, 0);
|
||||
g_rsiMomMonthCount = 0;
|
||||
}
|
||||
|
||||
void RsiMomJournal_OnDeinit(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
RsiMomJournal_WriteSummary(sym, tf);
|
||||
RsiMomJournal_CloseFiles();
|
||||
}
|
||||
|
||||
void RsiMomJournal_RecordClosedDeal(const ulong dealOut,
|
||||
const string sym,
|
||||
const ENUM_TIMEFRAMES tf,
|
||||
const ulong magic,
|
||||
const int hRsi,
|
||||
const int hWma45,
|
||||
const int hEma200,
|
||||
const int hAtrRegime,
|
||||
const int atrCmpBars,
|
||||
const double atrMinRatio,
|
||||
const int sessionOkAtEntry)
|
||||
{
|
||||
if(dealOut == 0 || !HistoryDealSelect(dealOut))
|
||||
return;
|
||||
if(HistoryDealGetString(dealOut, DEAL_SYMBOL) != sym)
|
||||
return;
|
||||
if((ulong)HistoryDealGetInteger(dealOut, DEAL_MAGIC) != magic)
|
||||
return;
|
||||
if(HistoryDealGetInteger(dealOut, DEAL_ENTRY) != DEAL_ENTRY_OUT)
|
||||
return;
|
||||
|
||||
if(!RsiMomJournal_OpenTrades(sym, tf))
|
||||
return;
|
||||
|
||||
const ulong posId = (ulong)HistoryDealGetInteger(dealOut, DEAL_POSITION_ID);
|
||||
ulong entryDeal = 0;
|
||||
datetime entryTime = 0;
|
||||
long posType = 0;
|
||||
double entryPrice = 0.0, sl = 0.0, tp = 0.0, volume = 0.0;
|
||||
if(!RsiMomJournal_FindEntryDeal(posId, entryDeal, entryTime, posType,
|
||||
entryPrice, sl, tp, volume))
|
||||
{
|
||||
Print("[RsiMomEA] Journal: không tìm entry pos=", posId);
|
||||
return;
|
||||
}
|
||||
|
||||
const int entryShift = iBarShift(sym, tf, entryTime, true);
|
||||
if(entryShift < 1)
|
||||
{
|
||||
Print("[RsiMomEA] Journal: entry shift invalid ", TimeToString(entryTime));
|
||||
return;
|
||||
}
|
||||
|
||||
const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
|
||||
double rsi = 0.0, wma = 0.0, ema200 = 0.0, atrNow = 0.0, atrRatio = 0.0;
|
||||
bool atrExpOk = false;
|
||||
RsiMomJournal_Copy1(hRsi, entryShift, rsi);
|
||||
RsiMomJournal_Copy1(hWma45, entryShift, wma);
|
||||
RsiMomJournal_Copy1(hEma200, entryShift, ema200);
|
||||
RsiMomJournal_AtrRatioAt(hAtrRegime, entryShift, atrCmpBars, atrMinRatio,
|
||||
atrNow, atrRatio, atrExpOk);
|
||||
|
||||
const double closeEntry = iClose(sym, tf, entryShift);
|
||||
const int aboveEma = (ema200 > 0.0 && closeEntry > ema200) ? 1 : 0;
|
||||
|
||||
const int sessionOk = (sessionOkAtEntry != 0) ? 1 : 0;
|
||||
const int spreadPts = (int)SymbolInfoInteger(sym, SYMBOL_SPREAD);
|
||||
|
||||
double riskPts = 0.0;
|
||||
if(posType == DEAL_TYPE_BUY && sl > 0.0)
|
||||
riskPts = (entryPrice - sl) / _Point;
|
||||
else if(posType == DEAL_TYPE_SELL && sl > 0.0)
|
||||
riskPts = (sl - entryPrice) / _Point;
|
||||
if(riskPts < 0.0)
|
||||
riskPts = 0.0;
|
||||
|
||||
const datetime closeTime = (datetime)HistoryDealGetInteger(dealOut, DEAL_TIME);
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(closeTime, dt);
|
||||
const string yearMonth = StringFormat("%04d-%02d", dt.year, dt.mon);
|
||||
|
||||
const double profit = HistoryDealGetDouble(dealOut, DEAL_PROFIT)
|
||||
+ HistoryDealGetDouble(dealOut, DEAL_SWAP)
|
||||
+ HistoryDealGetDouble(dealOut, DEAL_COMMISSION);
|
||||
const long dealReason = HistoryDealGetInteger(dealOut, DEAL_REASON);
|
||||
const string exitTag = RsiMomJournal_ExitTag(dealReason, profit);
|
||||
const string dir = (posType == DEAL_TYPE_BUY) ? "BUY" : "SELL";
|
||||
|
||||
double profitR = 0.0;
|
||||
if(riskPts > 0.0)
|
||||
{
|
||||
const double tickVal = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
|
||||
const double tickSz = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
||||
if(tickSz > 0.0 && tickVal > 0.0)
|
||||
{
|
||||
const double riskMoney = (riskPts * _Point / tickSz) * tickVal * volume;
|
||||
if(riskMoney > 0.0)
|
||||
profitR = profit / riskMoney;
|
||||
}
|
||||
}
|
||||
|
||||
RsiMomJournal_AddMonth(yearMonth, profit, exitTag);
|
||||
|
||||
FileWrite(g_rsiMomJournalHandle,
|
||||
TimeToString(closeTime, TIME_DATE | TIME_MINUTES),
|
||||
yearMonth,
|
||||
DoubleToString(profit, 2),
|
||||
dir,
|
||||
exitTag,
|
||||
TimeToString(entryTime, TIME_DATE | TIME_MINUTES),
|
||||
DoubleToString(entryPrice, dig),
|
||||
DoubleToString(sl, dig),
|
||||
DoubleToString(tp, dig),
|
||||
DoubleToString(volume, 2),
|
||||
DoubleToString(riskPts, 1),
|
||||
DoubleToString(profitR, 3),
|
||||
DoubleToString(rsi, 2),
|
||||
DoubleToString(wma, dig),
|
||||
DoubleToString(ema200, dig),
|
||||
DoubleToString(closeEntry, dig),
|
||||
IntegerToString(aboveEma),
|
||||
DoubleToString(atrNow, dig),
|
||||
DoubleToString(atrRatio, 4),
|
||||
IntegerToString(atrExpOk ? 1 : 0),
|
||||
IntegerToString(sessionOk),
|
||||
IntegerToString(spreadPts),
|
||||
sym,
|
||||
EnumToString(tf),
|
||||
IntegerToString((int)magic));
|
||||
|
||||
FileFlush(g_rsiMomJournalHandle);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user