From ab074b46feb095f0df79bf024996d9b064277f0f Mon Sep 17 00:00:00 2001 From: Bell Date: Mon, 11 May 2026 22:15:20 +0700 Subject: [PATCH] Complete RSI mom indicator version 1 --- .../RsiMomentumIndicator/Lib/Alerts.mqh | 120 +++++++ .../RsiMomentumIndicator/Lib/Diagnostics.mqh | 39 +++ .../RsiMomentumIndicator/Lib/Handles.mqh | 50 +++ .../RsiMomentumIndicator/Lib/Inputs.mqh | 43 +++ Indicators/RsiMomentumIndicator/Lib/Panel.mqh | 85 +++++ .../RsiMomentumIndicator/Lib/SignalScan.mqh | 162 +++++++++ Indicators/RsiMomentumIndicator/Lib/State.mqh | 46 +++ .../RsiMomentumIndicator.ex5 | Bin 25806 -> 32704 bytes .../RsiMomentumIndicator.mq5 | 323 ++---------------- Profiles/Charts/Default/order.wnd | Bin 2550 -> 106 bytes compile_rsimom_ea.log | Bin 0 -> 4108 bytes experts.dat | Bin 79526 -> 80083 bytes 12 files changed, 580 insertions(+), 288 deletions(-) create mode 100644 Indicators/RsiMomentumIndicator/Lib/Alerts.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/Diagnostics.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/Handles.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/Inputs.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/Panel.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/SignalScan.mqh create mode 100644 Indicators/RsiMomentumIndicator/Lib/State.mqh create mode 100644 compile_rsimom_ea.log diff --git a/Indicators/RsiMomentumIndicator/Lib/Alerts.mqh b/Indicators/RsiMomentumIndicator/Lib/Alerts.mqh new file mode 100644 index 0000000..e93268f --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/Alerts.mqh @@ -0,0 +1,120 @@ +//+------------------------------------------------------------------+ +//| Alerts.mqh | +//| Hệ thống cảnh báo entry mới: Push (mobile), Alert popup, Sound, | +//| Email. Tự chống bắn trùng cùng 1 bar. | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_ALERTS_MQH +#define RSIMOM_ALERTS_MQH + +//+------------------------------------------------------------------+ +//| Bắn cảnh báo khi có signal mới | +//| - isBuy=true → BUY, false → SELL | +//| - barTime = thời gian của bar có signal | +//| - price = giá tham chiếu (close của bar đó) | +//| Đi kèm 4 kênh: Push (mobile), Alert popup, Sound, Email | +//+------------------------------------------------------------------+ +void FireSignalAlert(const bool isBuy, + const datetime barTime, + const double price, + const double rsiVal, + const double ema9Val, + const double wma45Val, + const double ema200Val) +{ + const string dir = isBuy ? "BUY" : "SELL"; + const string tf = EnumToString((ENUM_TIMEFRAMES)_Period); + // Lược TFM2/TFM5… → M2/M5… cho gọn + const string tfTxt = StringSubstr(tf, 7); // bỏ tiền tố "PERIOD_" + const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // Tin nhắn ngắn cho push notification (giới hạn 255 ký tự, mobile thường cắt sớm) + const string pushMsg = StringFormat("[RsiMom] %s %s %s @ %s | RSI=%.1f EMA9=%.1f WMA45=%.1f", + dir, _Symbol, tfTxt, + DoubleToString(price, digits), + rsiVal, ema9Val, wma45Val); + + // Tin nhắn dài cho Alert popup + email + const string fullMsg = StringFormat("RsiMom %s signal\n%s %s @ %s\nRSI=%.2f EMA9=%.2f WMA45=%.2f\nEMA200=%s (trend khớp)\nBar: %s", + dir, _Symbol, tfTxt, + DoubleToString(price, digits), + rsiVal, ema9Val, wma45Val, + DoubleToString(ema200Val, digits), + TimeToString(barTime, TIME_DATE|TIME_MINUTES)); + + // 1) Push lên điện thoại (cần đã đăng ký MetaQuotes ID ở Tools→Options→Notifications) + if (InpAlertPush) + { + if (!SendNotification(pushMsg)) + PrintFormat("[RsiMom] SendNotification FAILED (err=%d) — kiểm tra MetaQuotes ID trong Options", + GetLastError()); + } + + // 2) Popup Alert trên desktop + if (InpAlertPopup) + Alert(pushMsg); + + // 3) Âm thanh + if (InpAlertSound) + { + const string snd = isBuy ? InpSoundBuy : InpSoundSell; + if (StringLen(snd) > 0) + PlaySound(snd); + } + + // 4) Email (cần cấu hình SMTP ở Tools→Options→Email) + if (InpAlertEmail) + { + const string subj = StringFormat("RsiMom %s %s %s", dir, _Symbol, tfTxt); + SendMail(subj, fullMsg); + } + + // Vẫn log ra journal để dễ debug + Print("[RsiMom] >>> ", pushMsg); +} + +//+------------------------------------------------------------------+ +//| Kiểm tra bar [1] (hoặc bar [0] nếu InpAlertOnBar0) và bắn alert | +//| nếu có signal mới. Tự skip lần OnCalculate đầu tiên (load history)| +//+------------------------------------------------------------------+ +void Alerts_CheckAndFire(const datetime &timeArr[], + const double &closeArr[], + const double &ema200Arr[], + const int need, + const int rates_total) +{ + // Lần đầu chạy — chỉ ghi nhận bar hiện tại để không bắn alert cho lịch sử + if (g_firstCalc) + { + if (need > 1) + { + g_lastAlertBuyBar = timeArr[1]; + g_lastAlertSellBar = timeArr[1]; + } + g_firstCalc = false; + return; + } + + // Bar đang xét (1 = đã đóng, 0 = đang chạy) + const int alertShift = InpAlertOnBar0 ? 0 : 1; + + if (alertShift >= need || alertShift + 1 >= rates_total) return; + + const datetime alertBarTime = timeArr[alertShift]; + + if (buf_Signal[alertShift] > 0.5 && alertBarTime != g_lastAlertBuyBar) + { + FireSignalAlert(true, alertBarTime, closeArr[alertShift], + buf_RSI[alertShift], buf_EMA9[alertShift], + buf_WMA45[alertShift], ema200Arr[alertShift]); + g_lastAlertBuyBar = alertBarTime; + } + else if (buf_Signal[alertShift] < -0.5 && alertBarTime != g_lastAlertSellBar) + { + FireSignalAlert(false, alertBarTime, closeArr[alertShift], + buf_RSI[alertShift], buf_EMA9[alertShift], + buf_WMA45[alertShift], ema200Arr[alertShift]); + g_lastAlertSellBar = alertBarTime; + } +} + +#endif // RSIMOM_ALERTS_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/Diagnostics.mqh b/Indicators/RsiMomentumIndicator/Lib/Diagnostics.mqh new file mode 100644 index 0000000..523b736 --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/Diagnostics.mqh @@ -0,0 +1,39 @@ +//+------------------------------------------------------------------+ +//| Diagnostics.mqh | +//| Log chẩn đoán lần đầu OnCalculate hoàn thành full để xác minh | +//| data. Chỉ chạy 1 lần duy nhất (static guard). | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_DIAGNOSTICS_MQH +#define RSIMOM_DIAGNOSTICS_MQH + +//+------------------------------------------------------------------+ +//| In thông tin chẩn đoán khi indicator chạy xong lần đầu | +//+------------------------------------------------------------------+ +void Diagnostics_FirstPass(const int rates_total, + const int copyN, + const int trendN, + const int need, + const double &closeArr[]) +{ + static bool firstSuccess = false; + if (firstSuccess || copyN < rates_total) return; + firstSuccess = true; + + int upCount = 0, downCount = 0, rangeCount = 0, zeroEma = 0; + const int n = MathMin(500, rates_total - 2); + for (int i = 1; i <= n; i++) + { + if (buf_EMA200[i] <= 0.0) zeroEma++; + if (buf_Trend[i] > 0.5) upCount++; + else if (buf_Trend[i] < -0.5) downCount++; + else rangeCount++; + } + PrintFormat("[RsiMom] First-pass OK rates_total=%d copyN=%d trendN=%d need=%d", + rates_total, copyN, trendN, need); + PrintFormat("[RsiMom] bar1: RSI=%.2f EMA9=%.2f WMA45=%.2f EMA200=%.5f Trend=%.0f Signal=%.0f close[1]=%.5f", + buf_RSI[1], buf_EMA9[1], buf_WMA45[1], buf_EMA200[1], buf_Trend[1], buf_Signal[1], closeArr[1]); + PrintFormat("[RsiMom] last %d bars trend dist: UP=%d DOWN=%d RANGE=%d (zeroEma200=%d)", + n, upCount, downCount, rangeCount, zeroEma); +} + +#endif // RSIMOM_DIAGNOSTICS_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/Handles.mqh b/Indicators/RsiMomentumIndicator/Lib/Handles.mqh new file mode 100644 index 0000000..a2d1ef2 --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/Handles.mqh @@ -0,0 +1,50 @@ +//+------------------------------------------------------------------+ +//| Handles.mqh | +//| Tạo / giải phóng indicator handles (RSI, EMA9/RSI, WMA45/RSI, | +//| EMA200) và attach EMA200 lên main chart window. | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_HANDLES_MQH +#define RSIMOM_HANDLES_MQH + +//+------------------------------------------------------------------+ +//| Tạo tất cả indicator handle. Trả về true nếu thành công. | +//+------------------------------------------------------------------+ +bool Handles_CreateAll() +{ + // RSI(close) + h_RSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); + if (h_RSI == INVALID_HANDLE) + { Print("[RsiMom] Không tạo được handle RSI"); return false; } + + // EMA9 và WMA45 tính trên dữ liệu của h_RSI (buffer 0 của RSI) + h_EMA9 = iMA(_Symbol, _Period, InpEMA9Period, 0, MODE_EMA, h_RSI); + if (h_EMA9 == INVALID_HANDLE) + { Print("[RsiMom] Không tạo được handle EMA9(RSI)"); return false; } + + h_WMA45 = iMA(_Symbol, _Period, InpWMA45Period, 0, MODE_LWMA, h_RSI); + if (h_WMA45 == INVALID_HANDLE) + { Print("[RsiMom] Không tạo được handle WMA45(RSI)"); return false; } + + h_EMA200 = iMA(_Symbol, _Period, InpEMATrendPeriod, 0, MODE_EMA, PRICE_CLOSE); + if (h_EMA200 == INVALID_HANDLE) + { Print("[RsiMom] Không tạo được handle EMA200"); return false; } + + // Gắn EMA200 lên cửa sổ chính (window 0) của chart hiện tại + if (!ChartIndicatorAdd(ChartID(), 0, h_EMA200)) + PrintFormat("[RsiMom] Không thể gắn EMA200 lên chart (err=%d)", GetLastError()); + + return true; +} + +//+------------------------------------------------------------------+ +//| Giải phóng tất cả indicator handle | +//+------------------------------------------------------------------+ +void Handles_ReleaseAll() +{ + if (h_RSI != INVALID_HANDLE) IndicatorRelease(h_RSI); + if (h_EMA9 != INVALID_HANDLE) IndicatorRelease(h_EMA9); + if (h_WMA45 != INVALID_HANDLE) IndicatorRelease(h_WMA45); + if (h_EMA200 != INVALID_HANDLE) IndicatorRelease(h_EMA200); +} + +#endif // RSIMOM_HANDLES_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/Inputs.mqh b/Indicators/RsiMomentumIndicator/Lib/Inputs.mqh new file mode 100644 index 0000000..7de36e0 --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/Inputs.mqh @@ -0,0 +1,43 @@ +//+------------------------------------------------------------------+ +//| Inputs.mqh | +//| Toàn bộ input parameters của RsiMomentumIndicator. | +//| Được include từ RsiMomentumIndicator.mq5 (thư mục Lib/). | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_INPUTS_MQH +#define RSIMOM_INPUTS_MQH + +input group "Chỉ báo" +input int InpRSIPeriod = 14; +input int InpEMA9Period = 9; +input int InpWMA45Period = 45; +input int InpEMATrendPeriod = 200; + +input group "Bộ lọc trend" +input int InpTrendConfirmBars = 2; // số nến liên tiếp phải đóng cùng phía EMA200 + +input group "Bộ lọc RSI" +input double InpRSIOverbought = 70.0; // RSI ≥ ngưỡng này → bỏ qua tín hiệu BUY +input double InpRSIOversold = 30.0; // RSI ≤ ngưỡng này → bỏ qua tín hiệu SELL + +input group "Bộ lọc EMA9 vs WMA45 (chống nhiễu)" +input int InpEma9PersistBars = 3; // EMA9 phải ở cùng phía WMA45 N nến liên tiếp (gồm cả bar signal); 0 hoặc 1 = tắt + +input group "Mũi tên giao cắt" +input color InpArrowUpColor = clrLime; // màu mũi tên khi RSI cắt lên WMA45 +input color InpArrowDownColor = clrTomato; // màu mũi tên khi RSI cắt xuống WMA45 +input int InpArrowOffsetPts = 30; // khoảng cách mũi tên so với đỉnh/đáy nến (points) +input int InpArrowSize = 1; // kích thước mũi tên + +input group "Panel thông tin" +input bool InpShowPanel = true; + +input group "Cảnh báo / Notification (khi có entry mới)" +input bool InpAlertPush = true; // Push lên app MT5 trên điện thoại (cần MetaQuotes ID) +input bool InpAlertPopup = true; // Popup Alert() trên MT5 Desktop +input bool InpAlertSound = true; // Phát âm thanh khi có signal +input string InpSoundBuy = "alert.wav"; // file âm thanh BUY (Sounds/ hoặc đường dẫn đầy đủ) +input string InpSoundSell = "alert2.wav"; // file âm thanh SELL +input bool InpAlertEmail = false; // Gửi email (cần cấu hình SMTP ở Tools→Options→Email) +input bool InpAlertOnBar0 = false; // Cảnh báo NGAY trên bar đang chạy (false = chỉ cảnh báo khi bar đóng) + +#endif // RSIMOM_INPUTS_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/Panel.mqh b/Indicators/RsiMomentumIndicator/Lib/Panel.mqh new file mode 100644 index 0000000..ba585cb --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/Panel.mqh @@ -0,0 +1,85 @@ +//+------------------------------------------------------------------+ +//| Panel.mqh | +//| Panel thông tin góc trên-phải chart: trend, RSI/EMA9/WMA45. | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_PANEL_MQH +#define RSIMOM_PANEL_MQH + +//+------------------------------------------------------------------+ +//| Tạo một OBJ_LABEL trên chart chính | +//+------------------------------------------------------------------+ +void CreateLabel(const string name, const string text, const color clr, + const int x, const int y, const int fontSize = 9) +{ + if (ObjectFind(0, name) >= 0) return; + ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); + ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); + ObjectSetString (0, name, OBJPROP_FONT, "Consolas"); + ObjectSetInteger(0, name, OBJPROP_BACK, false); + ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetString (0, name, OBJPROP_TEXT, text); +} + +//+------------------------------------------------------------------+ +//| Update text + màu của label đã tồn tại | +//+------------------------------------------------------------------+ +void UpdateLabel(const string name, const string text, const color clr) +{ + if (ObjectFind(0, name) < 0) return; + ObjectSetString (0, name, OBJPROP_TEXT, text); + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); +} + +//+------------------------------------------------------------------+ +//| Tạo toàn bộ label ban đầu (gọi 1 lần từ OnInit) | +//+------------------------------------------------------------------+ +void Panel_CreateAll() +{ + if (!InpShowPanel) return; + CreateLabel(LBL_TITLE, "─ RSI MOMENTUM ─", clrWhite, 10, 14, 10); + CreateLabel(LBL_TREND, "Trend : ---", clrSilver, 10, 34, 9); + CreateLabel(LBL_RSI_VAL, "RSI : ---", clrMediumOrchid, 10, 51, 9); + CreateLabel(LBL_EMA9VAL, "EMA9 : ---", clrDarkOrange, 10, 68, 9); + CreateLabel(LBL_WMA45VAL, "WMA45 : ---", clrDodgerBlue, 10, 85, 9); +} + +//+------------------------------------------------------------------+ +//| Cập nhật panel mỗi tick (dùng nến vừa đóng = index 1) | +//| Sử dụng cùng quy tắc N nến liên tiếp như filter signal. | +//+------------------------------------------------------------------+ +void Panel_Update(const double &closeArr[], const double &ema200Arr[], + const int trendN, const int rates_total) +{ + if (!InpShowPanel) return; + if (rates_total <= trendN + 1) return; + if (ema200Arr[1] <= 0.0) return; + + // Áp dụng cùng quy tắc N nến liên tiếp cho panel hiển thị + bool panelTrendUp = true; + bool panelTrendDown = true; + for (int k = 0; k < trendN; k++) + { + const int idx = 1 + k; + if (ema200Arr[idx] <= 0.0) { panelTrendUp = false; panelTrendDown = false; break; } + if (closeArr[idx] <= ema200Arr[idx]) panelTrendUp = false; + if (closeArr[idx] >= ema200Arr[idx]) panelTrendDown = false; + } + + string trendTxt; + color trendClr; + if (panelTrendUp) { trendTxt = StringFormat("Trend : UPTREND (%d closes > EMA200)", trendN); trendClr = InpArrowUpColor; } + else if (panelTrendDown) { trendTxt = StringFormat("Trend : DOWNTREND (%d closes < EMA200)", trendN); trendClr = InpArrowDownColor; } + else { trendTxt = "Trend : RANGE / SWITCHING"; trendClr = clrSilver; } + + UpdateLabel(LBL_TREND, trendTxt, trendClr); + UpdateLabel(LBL_RSI_VAL, StringFormat("RSI : %6.2f", buf_RSI[1]), clrMediumOrchid); + UpdateLabel(LBL_EMA9VAL, StringFormat("EMA9 : %6.2f", buf_EMA9[1]), clrDarkOrange); + UpdateLabel(LBL_WMA45VAL, StringFormat("WMA45 : %6.2f", buf_WMA45[1]),clrDodgerBlue); +} + +#endif // RSIMOM_PANEL_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/SignalScan.mqh b/Indicators/RsiMomentumIndicator/Lib/SignalScan.mqh new file mode 100644 index 0000000..bda87bb --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/SignalScan.mqh @@ -0,0 +1,162 @@ +//+------------------------------------------------------------------+ +//| SignalScan.mqh | +//| Vòng for phát hiện giao cắt RSI vs WMA45, áp 5 filter: | +//| 1. crossUp / crossDown | +//| 2. trend EMA200 (N nến liên tiếp cùng phía) | +//| 3. EMA9 vs WMA45 (persist N bars) | +//| 4. slope EMA9 cùng chiều tín hiệu | +//| 5. RSI không ở extreme | +//| Sau đó vẽ mũi tên BUY/SELL lên chart. | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_SIGNALSCAN_MQH +#define RSIMOM_SIGNALSCAN_MQH + +//+------------------------------------------------------------------+ +//| Quét signal từ bar `barsToScan` về bar 1, ghi vào buf_Signal / | +//| buf_Trend / buf_EMA200 và tạo OBJ_ARROW khi có tín hiệu hợp lệ. | +//+------------------------------------------------------------------+ +void SignalScan_Run(const int barsToScan, + const int rates_total, + const int need, + const int trendN, + const datetime &timeArr[], + const double &highArr[], + const double &lowArr[], + const double &closeArr[], + const double &ema200Arr[]) +{ + const double arrowOffset = InpArrowOffsetPts * _Point; + + // Reset buffer cho bar 0 (đang hình thành) — đặt giá trị mặc định + buf_Signal[0] = 0.0; + buf_Trend [0] = 0.0; + buf_EMA200[0] = (need > 0) ? ema200Arr[0] : 0.0; + + // Phát hiện giao cắt RSI vs WMA45, chỉ vẽ khi khớp với trend EMA200 + for (int i = barsToScan; i >= 1; i--) + { + buf_Signal[i] = 0.0; // mặc định: không có tín hiệu + buf_Trend [i] = 0.0; + buf_EMA200[i] = (i < need) ? ema200Arr[i] : 0.0; + + if (i + 1 >= rates_total) continue; + if (i + trendN >= need) continue; // ngoài phạm vi mảng cục bộ + if (ema200Arr[i] <= 0.0) continue; // EMA200 chưa tính đủ bars + + // Lọc theo trend EMA200: cần N nến LIÊN TIẾP gần nhất đều đóng cùng phía + // Quét từ nến tín hiệu i → i+N-1 (tính LUÔN, kể cả khi không có cross) + bool trendUp = true; + bool trendDown = true; + for (int k = 0; k < trendN; k++) + { + const int idx = i + k; + if (ema200Arr[idx] <= 0.0) { trendUp = false; trendDown = false; break; } + if (closeArr[idx] <= ema200Arr[idx]) trendUp = false; + if (closeArr[idx] >= ema200Arr[idx]) trendDown = false; + if (!trendUp && !trendDown) break; + } + buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0); + + const bool crossUp = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]); + const bool crossDown = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]); + + if (!crossUp && !crossDown) continue; + + // Vị trí EMA9 vs WMA45 tại nến signal — "lực mua/bán mới bắt đầu chiếm ưu thế" + // BUY: EMA9 vẫn dưới WMA45 (lực mua chỉ vừa nhú lên qua RSI cross) + // SELL: EMA9 vẫn trên WMA45 (lực bán chỉ vừa nhú xuống) + const bool ema9BelowWma = (buf_EMA9[i] < buf_WMA45[i]); + const bool ema9AboveWma = (buf_EMA9[i] > buf_WMA45[i]); + + // ── Filter PERSIST (chống nhiễu) ───────────────────────────────────── + // Yêu cầu EMA9 ở CÙNG PHÍA WMA45 trong N nến liên tiếp (i, i+1, …, i+N-1) + // - Tránh trường hợp EMA9 cắt lên/xuống WMA45 liên tục (lực không rõ) + // - Mặc định N=5: EMA9 phải đã ổn định 1 phía ít nhất 5 bar + // - N <= 1 → tắt filter (chỉ check tại bar signal như cũ) + const int persistN = MathMax(1, InpEma9PersistBars); + bool ema9PersistBelow = ema9BelowWma; + bool ema9PersistAbove = ema9AboveWma; + if (persistN > 1) + { + // Cần thêm (persistN - 1) bar trước nến signal cùng phía + // Đảm bảo không vượt phạm vi buffer (i + persistN - 1 < rates_total) + if (i + persistN - 1 >= rates_total) + { + ema9PersistBelow = false; + ema9PersistAbove = false; + } + else + { + for (int k = 1; k < persistN; k++) + { + if (buf_EMA9[i+k] >= buf_WMA45[i+k]) ema9PersistBelow = false; + if (buf_EMA9[i+k] <= buf_WMA45[i+k]) ema9PersistAbove = false; + if (!ema9PersistBelow && !ema9PersistAbove) break; + } + } + } + + // Slope EMA9 — phải đang hướng cùng chiều với tín hiệu + const bool ema9SlopeUp = (buf_EMA9[i] > buf_EMA9[i+1]); + const bool ema9SlopeDown = (buf_EMA9[i] < buf_EMA9[i+1]); + + // Lọc RSI extreme — không mua đỉnh, không bán đáy + // RSI ≥ overbought → bỏ tín hiệu BUY + // RSI ≤ oversold → bỏ tín hiệu SELL + const bool rsiOkBuy = (buf_RSI[i] < InpRSIOverbought); + const bool rsiOkSell = (buf_RSI[i] > InpRSIOversold); + + // Tín hiệu hợp lệ khi đủ 6 điều kiện (thêm filter PERSIST) + const bool validBuy = crossUp && trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy; + const bool validSell = crossDown && trendDown && ema9PersistAbove && ema9SlopeDown && rsiOkSell; + + if (!validBuy && !validSell) continue; + + // Dùng timestamp làm tên duy nhất để tránh tạo trùng object + const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]); + if (ObjectFind(0, arrowName) >= 0) continue; + + if (validBuy) + { + buf_Signal[i] = 1.0; // ghi tín hiệu cho EA đọc qua iCustom + + // Mũi tên lên (↑) đặt dưới đáy nến — tín hiệu BUY trong uptrend + // ANCHOR_TOP: điểm anchor là đỉnh icon → arrow nằm xuôi xuống dưới price + // → tip ↑ ở phía trên, sát đáy nến (cách lowArr[i] một khoảng arrowOffset) + const double price = lowArr[i] - arrowOffset; + ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeArr[i], price); + ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 233); + ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP); + ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpArrowUpColor); + ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); + ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, arrowName, OBJPROP_HIDDEN, true); + ObjectSetString (0, arrowName, OBJPROP_TOOLTIP, + StringFormat("BUY signal\n%s\nRSI=%.2f EMA9=%.2f (slope+) WMA45=%.2f\nEMA200=%.5f (Uptrend)\nEMA9 below WMA45 ≥ %d bars", + TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), + buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i], persistN)); + } + else // validSell + { + buf_Signal[i] = -1.0; // ghi tín hiệu cho EA đọc qua iCustom + + // Mũi tên xuống (↓) đặt trên đỉnh nến — tín hiệu SELL trong downtrend + // ANCHOR_BOTTOM: điểm anchor là đáy icon → arrow nằm ngược lên trên price + // → tip ↓ ở phía dưới, sát đỉnh nến (cách highArr[i] một khoảng arrowOffset) + const double price = highArr[i] + arrowOffset; + ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeArr[i], price); + ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 234); + ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); + ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpArrowDownColor); + ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); + ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, arrowName, OBJPROP_HIDDEN, true); + ObjectSetString (0, arrowName, OBJPROP_TOOLTIP, + StringFormat("SELL signal\n%s\nRSI=%.2f EMA9=%.2f (slope-) WMA45=%.2f\nEMA200=%.5f (Downtrend)\nEMA9 above WMA45 ≥ %d bars", + TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), + buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i], persistN)); + } + } +} + +#endif // RSIMOM_SIGNALSCAN_MQH diff --git a/Indicators/RsiMomentumIndicator/Lib/State.mqh b/Indicators/RsiMomentumIndicator/Lib/State.mqh new file mode 100644 index 0000000..4b8978c --- /dev/null +++ b/Indicators/RsiMomentumIndicator/Lib/State.mqh @@ -0,0 +1,46 @@ +//+------------------------------------------------------------------+ +//| State.mqh | +//| Toàn bộ biến global: indicator buffers, handles, hằng số, | +//| state notification. KHÔNG chứa logic — chỉ khai báo. | +//+------------------------------------------------------------------+ +#ifndef RSIMOM_STATE_MQH +#define RSIMOM_STATE_MQH + +//+------------------------------------------------------------------+ +//| Indicator buffers | +//+------------------------------------------------------------------+ +double buf_RSI[]; +double buf_EMA9[]; +double buf_WMA45[]; +double buf_Signal[]; // hidden — +1 BUY, -1 SELL, 0 none +double buf_EMA200[]; // hidden — giá trị EMA200 (cho EA hiển thị) +double buf_Trend[]; // hidden — +1 UP, -1 DOWN, 0 RANGE/SWITCHING + +//+------------------------------------------------------------------+ +//| Indicator handles | +//+------------------------------------------------------------------+ +int h_RSI = INVALID_HANDLE; +int h_EMA9 = INVALID_HANDLE; +int h_WMA45 = INVALID_HANDLE; +int h_EMA200 = INVALID_HANDLE; + +//+------------------------------------------------------------------+ +//| Hằng số tên object | +//+------------------------------------------------------------------+ +const string OBJ_PREFIX = "RsiMom_"; +const string LBL_TITLE = OBJ_PREFIX + "title"; +const string LBL_TREND = OBJ_PREFIX + "trend"; +const string LBL_RSI_VAL = OBJ_PREFIX + "rsi"; +const string LBL_EMA9VAL = OBJ_PREFIX + "ema9"; +const string LBL_WMA45VAL= OBJ_PREFIX + "wma45"; + +//+------------------------------------------------------------------+ +//| State notification — chống bắn trùng cùng 1 bar | +//| Lưu time của bar đã alert riêng cho mỗi hướng để không miss | +//| tín hiệu ngược chiều trên cùng 1 bar (hiếm nhưng có thể). | +//+------------------------------------------------------------------+ +datetime g_lastAlertBuyBar = 0; +datetime g_lastAlertSellBar = 0; +bool g_firstCalc = true; // bỏ qua alert ở lần OnCalculate đầu (tránh spam history) + +#endif // RSIMOM_STATE_MQH diff --git a/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.ex5 b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.ex5 index c97355a5151a965219daf322d8df32cbcf6f57a4..70b7760a32927ab4a48ae25e9ec4e7f0de1d3639 100644 GIT binary patch literal 32704 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 O;r)Lj0OQ)P-2!W8%mpL> diff --git a/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 index b3bc312..f37dfa8 100644 --- a/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 +++ b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 @@ -3,9 +3,18 @@ //| Hiển thị trong cửa sổ phụ: RSI14, EMA9(RSI), WMA45(RSI) | //| Hiển thị trên chart chính: EMA200 + mũi tên đánh dấu giao cắt | //| Panel góc trên-phải: trend, giá trị RSI / EMA9 / WMA45 | +//| | +//| Source code được tách thành các module trong thư mục Lib/: | +//| Inputs.mqh — input parameters | +//| State.mqh — buffers, handles, constants, alert state | +//| Handles.mqh — tạo/giải phóng indicator handle | +//| Panel.mqh — panel thông tin góc trên-phải | +//| Alerts.mqh — Push/Popup/Sound/Email khi có entry | +//| SignalScan.mqh — phát hiện cross + vẽ arrow | +//| Diagnostics.mqh — log chẩn đoán lần đầu | //+------------------------------------------------------------------+ #property copyright "RsiMomentumIndicator" -#property version "1.00" +#property version "1.10" #property indicator_separate_window #property indicator_buffers 6 #property indicator_plots 3 @@ -34,87 +43,26 @@ #property indicator_width3 2 //+------------------------------------------------------------------+ -//| Inputs | +//| Modules — thứ tự include theo dependency | //+------------------------------------------------------------------+ -input group "Chỉ báo" -input int InpRSIPeriod = 14; -input int InpEMA9Period = 9; -input int InpWMA45Period = 45; -input int InpEMATrendPeriod = 200; - -input group "Bộ lọc trend" -input int InpTrendConfirmBars = 2; // số nến liên tiếp phải đóng cùng phía EMA200 - -input group "Bộ lọc RSI" -input double InpRSIOverbought = 70.0; // RSI ≥ ngưỡng này → bỏ qua tín hiệu BUY -input double InpRSIOversold = 30.0; // RSI ≤ ngưỡng này → bỏ qua tín hiệu SELL - -input group "Bộ lọc EMA9 vs WMA45 (chống nhiễu)" -input int InpEma9PersistBars = 3; // EMA9 phải ở cùng phía WMA45 N nến liên tiếp (gồm cả bar signal); 0 hoặc 1 = tắt - -input group "Mũi tên giao cắt" -input color InpArrowUpColor = clrLime; // màu mũi tên khi RSI cắt lên WMA45 -input color InpArrowDownColor = clrTomato; // màu mũi tên khi RSI cắt xuống WMA45 -input int InpArrowOffsetPts = 30; // khoảng cách mũi tên so với đỉnh/đáy nến (points) -input int InpArrowSize = 2; // kích thước mũi tên - -input group "Panel thông tin" -input bool InpShowPanel = true; - -//+------------------------------------------------------------------+ -//| Globals | -//+------------------------------------------------------------------+ -double buf_RSI[]; -double buf_EMA9[]; -double buf_WMA45[]; -double buf_Signal[]; // hidden — +1 BUY, -1 SELL, 0 none -double buf_EMA200[]; // hidden — giá trị EMA200 (cho EA hiển thị) -double buf_Trend[]; // hidden — +1 UP, -1 DOWN, 0 RANGE/SWITCHING - -int h_RSI = INVALID_HANDLE; -int h_EMA9 = INVALID_HANDLE; -int h_WMA45 = INVALID_HANDLE; -int h_EMA200 = INVALID_HANDLE; - -const string OBJ_PREFIX = "RsiMom_"; -const string LBL_TITLE = OBJ_PREFIX + "title"; -const string LBL_TREND = OBJ_PREFIX + "trend"; -const string LBL_RSI_VAL = OBJ_PREFIX + "rsi"; -const string LBL_EMA9VAL = OBJ_PREFIX + "ema9"; -const string LBL_WMA45VAL= OBJ_PREFIX + "wma45"; - -//+------------------------------------------------------------------+ -//| Tạo một OBJ_LABEL trên chart chính | -//+------------------------------------------------------------------+ -void CreateLabel(const string name, const string text, const color clr, - const int x, const int y, const int fontSize = 9) -{ - if (ObjectFind(0, name) >= 0) return; - ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); - ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); - ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); - ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); - ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); - ObjectSetString (0, name, OBJPROP_FONT, "Consolas"); - ObjectSetInteger(0, name, OBJPROP_BACK, false); - ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); - ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); - ObjectSetInteger(0, name, OBJPROP_COLOR, clr); - ObjectSetString (0, name, OBJPROP_TEXT, text); -} - -void UpdateLabel(const string name, const string text, const color clr) -{ - if (ObjectFind(0, name) < 0) return; - ObjectSetString (0, name, OBJPROP_TEXT, text); - ObjectSetInteger(0, name, OBJPROP_COLOR, clr); -} +#include "Lib/Inputs.mqh" +#include "Lib/State.mqh" +#include "Lib/Handles.mqh" +#include "Lib/Panel.mqh" +#include "Lib/Alerts.mqh" +#include "Lib/SignalScan.mqh" +#include "Lib/Diagnostics.mqh" //+------------------------------------------------------------------+ //| Khởi tạo indicator | //+------------------------------------------------------------------+ int OnInit() { + // Reset state notification (tránh re-fire alert cũ khi đổi TF / reload) + g_lastAlertBuyBar = 0; + g_lastAlertSellBar = 0; + g_firstCalc = true; + // Đăng ký buffer — đặt AS_SERIES để [0] = nến hiện tại SetIndexBuffer(0, buf_RSI, INDICATOR_DATA); SetIndexBuffer(1, buf_EMA9, INDICATOR_DATA); @@ -133,37 +81,9 @@ int OnInit() IndicatorSetString (INDICATOR_SHORTNAME, StringFormat("RsiMom(%d)", InpRSIPeriod)); IndicatorSetInteger(INDICATOR_DIGITS, 2); - // Tạo handles chỉ báo - h_RSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); - if (h_RSI == INVALID_HANDLE) - { Print("[RsiMom] Không tạo được handle RSI"); return INIT_FAILED; } + if (!Handles_CreateAll()) return INIT_FAILED; - // EMA9 và WMA45 tính trên dữ liệu của h_RSI (buffer 0 của RSI) - h_EMA9 = iMA(_Symbol, _Period, InpEMA9Period, 0, MODE_EMA, h_RSI); - if (h_EMA9 == INVALID_HANDLE) - { Print("[RsiMom] Không tạo được handle EMA9(RSI)"); return INIT_FAILED; } - - h_WMA45 = iMA(_Symbol, _Period, InpWMA45Period, 0, MODE_LWMA, h_RSI); - if (h_WMA45 == INVALID_HANDLE) - { Print("[RsiMom] Không tạo được handle WMA45(RSI)"); return INIT_FAILED; } - - h_EMA200 = iMA(_Symbol, _Period, InpEMATrendPeriod, 0, MODE_EMA, PRICE_CLOSE); - if (h_EMA200 == INVALID_HANDLE) - { Print("[RsiMom] Không tạo được handle EMA200"); return INIT_FAILED; } - - // Gắn EMA200 lên cửa sổ chính (window 0) của chart hiện tại - if (!ChartIndicatorAdd(ChartID(), 0, h_EMA200)) - PrintFormat("[RsiMom] Không thể gắn EMA200 lên chart (err=%d)", GetLastError()); - - // Tạo panel thông tin (góc trên-phải chart chính) - if (InpShowPanel) - { - CreateLabel(LBL_TITLE, "─ RSI MOMENTUM ─", clrWhite, 10, 14, 10); - CreateLabel(LBL_TREND, "Trend : ---", clrSilver, 10, 34, 9); - CreateLabel(LBL_RSI_VAL, "RSI : ---", clrMediumOrchid, 10, 51, 9); - CreateLabel(LBL_EMA9VAL, "EMA9 : ---", clrDarkOrange, 10, 68, 9); - CreateLabel(LBL_WMA45VAL, "WMA45 : ---", clrDodgerBlue, 10, 85, 9); - } + Panel_CreateAll(); return INIT_SUCCEEDED; } @@ -173,10 +93,7 @@ int OnInit() //+------------------------------------------------------------------+ void OnDeinit(const int reason) { - if (h_RSI != INVALID_HANDLE) IndicatorRelease(h_RSI); - if (h_EMA9 != INVALID_HANDLE) IndicatorRelease(h_EMA9); - if (h_WMA45 != INVALID_HANDLE) IndicatorRelease(h_WMA45); - if (h_EMA200 != INVALID_HANDLE) IndicatorRelease(h_EMA200); + Handles_ReleaseAll(); // Xóa tất cả object do indicator tạo ra (mũi tên + panel) ObjectsDeleteAll(0, OBJ_PREFIX); @@ -255,193 +172,23 @@ int OnCalculate(const int rates_total, if (CopyClose (_Symbol, _Period, 0, need, closeArr) < need) return prev_calculated; if (CopyBuffer(h_EMA200, 0, 0, need, ema200Arr) < need) return prev_calculated; - const double arrowOffset = InpArrowOffsetPts * _Point; + // ── Phát hiện cross + vẽ arrow ─────────────────────────────────── + SignalScan_Run(barsToScan, rates_total, need, trendN, + timeArr, highArr, lowArr, closeArr, ema200Arr); - // Reset buffer cho bar 0 (đang hình thành) — đặt giá trị mặc định - buf_Signal[0] = 0.0; - buf_Trend [0] = 0.0; - buf_EMA200[0] = (need > 0) ? ema200Arr[0] : 0.0; + // ── Bắn alert nếu có signal MỚI trên bar đã đóng ───────────────── + Alerts_CheckAndFire(timeArr, closeArr, ema200Arr, need, rates_total); - // Phát hiện giao cắt RSI vs WMA45, chỉ vẽ khi khớp với trend EMA200 - for (int i = barsToScan; i >= 1; i--) - { - buf_Signal[i] = 0.0; // mặc định: không có tín hiệu - buf_Trend [i] = 0.0; - buf_EMA200[i] = (i < need) ? ema200Arr[i] : 0.0; - - if (i + 1 >= rates_total) continue; - if (i + trendN >= need) continue; // ngoài phạm vi mảng cục bộ (timeArr/closeArr/ema200Arr) - if (ema200Arr[i] <= 0.0) continue; // EMA200 chưa tính đủ bars - - // Lọc theo trend EMA200: cần N nến LIÊN TIẾP gần nhất đều đóng cùng phía - // Quét từ nến tín hiệu i → i+N-1 (tính LUÔN, kể cả khi không có cross) - bool trendUp = true; - bool trendDown = true; - for (int k = 0; k < trendN; k++) - { - const int idx = i + k; - if (ema200Arr[idx] <= 0.0) { trendUp = false; trendDown = false; break; } - if (closeArr[idx] <= ema200Arr[idx]) trendUp = false; - if (closeArr[idx] >= ema200Arr[idx]) trendDown = false; - if (!trendUp && !trendDown) break; - } - buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0); - - const bool crossUp = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]); - const bool crossDown = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]); - - if (!crossUp && !crossDown) continue; - - // Vị trí EMA9 vs WMA45 tại nến signal — "lực mua/bán mới bắt đầu chiếm ưu thế" - // BUY: EMA9 vẫn dưới WMA45 (lực mua chỉ vừa nhú lên qua RSI cross) - // SELL: EMA9 vẫn trên WMA45 (lực bán chỉ vừa nhú xuống) - const bool ema9BelowWma = (buf_EMA9[i] < buf_WMA45[i]); - const bool ema9AboveWma = (buf_EMA9[i] > buf_WMA45[i]); - - // ── Filter PERSIST (chống nhiễu) ───────────────────────────────────── - // Yêu cầu EMA9 ở CÙNG PHÍA WMA45 trong N nến liên tiếp (i, i+1, …, i+N-1) - // - Tránh trường hợp EMA9 cắt lên/xuống WMA45 liên tục (lực không rõ) - // - Mặc định N=5: EMA9 phải đã ổn định 1 phía ít nhất 5 bar - // - N <= 1 → tắt filter (chỉ check tại bar signal như cũ) - const int persistN = MathMax(1, InpEma9PersistBars); - bool ema9PersistBelow = ema9BelowWma; - bool ema9PersistAbove = ema9AboveWma; - if (persistN > 1) - { - // Cần thêm (persistN - 1) bar trước nến signal cùng phía - // Đảm bảo không vượt phạm vi buffer (i + persistN - 1 < rates_total) - if (i + persistN - 1 >= rates_total) - { - ema9PersistBelow = false; - ema9PersistAbove = false; - } - else - { - for (int k = 1; k < persistN; k++) - { - if (buf_EMA9[i+k] >= buf_WMA45[i+k]) ema9PersistBelow = false; - if (buf_EMA9[i+k] <= buf_WMA45[i+k]) ema9PersistAbove = false; - if (!ema9PersistBelow && !ema9PersistAbove) break; - } - } - } - - // Slope EMA9 — phải đang hướng cùng chiều với tín hiệu - const bool ema9SlopeUp = (buf_EMA9[i] > buf_EMA9[i+1]); - const bool ema9SlopeDown = (buf_EMA9[i] < buf_EMA9[i+1]); - - // Lọc RSI extreme — không mua đỉnh, không bán đáy - // RSI ≥ overbought → bỏ tín hiệu BUY - // RSI ≤ oversold → bỏ tín hiệu SELL - const bool rsiOkBuy = (buf_RSI[i] < InpRSIOverbought); - const bool rsiOkSell = (buf_RSI[i] > InpRSIOversold); - - // Tín hiệu hợp lệ khi đủ 6 điều kiện (thêm filter PERSIST) - const bool validBuy = crossUp && trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy; - const bool validSell = crossDown && trendDown && ema9PersistAbove && ema9SlopeDown && rsiOkSell; - - if (!validBuy && !validSell) continue; - - // Dùng timestamp làm tên duy nhất để tránh tạo trùng object - const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]); - if (ObjectFind(0, arrowName) >= 0) continue; - - if (validBuy) - { - buf_Signal[i] = 1.0; // ghi tín hiệu cho EA đọc qua iCustom - - // Mũi tên lên (↑) đặt dưới đáy nến — tín hiệu BUY trong uptrend - // ANCHOR_TOP: điểm anchor là đỉnh icon → arrow nằm xuôi xuống dưới price - // → tip ↑ ở phía trên, sát đáy nến (cách lowArr[i] một khoảng arrowOffset) - const double price = lowArr[i] - arrowOffset; - ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeArr[i], price); - ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 233); - ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP); - ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpArrowUpColor); - ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); - ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); - ObjectSetInteger(0, arrowName, OBJPROP_HIDDEN, true); - ObjectSetString (0, arrowName, OBJPROP_TOOLTIP, - StringFormat("BUY signal\n%s\nRSI=%.2f EMA9=%.2f (slope+) WMA45=%.2f\nEMA200=%.5f (Uptrend)\nEMA9 below WMA45 ≥ %d bars", - TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), - buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i], persistN)); - } - else // validSell - { - buf_Signal[i] = -1.0; // ghi tín hiệu cho EA đọc qua iCustom - - // Mũi tên xuống (↓) đặt trên đỉnh nến — tín hiệu SELL trong downtrend - // ANCHOR_BOTTOM: điểm anchor là đáy icon → arrow nằm ngược lên trên price - // → tip ↓ ở phía dưới, sát đỉnh nến (cách highArr[i] một khoảng arrowOffset) - const double price = highArr[i] + arrowOffset; - ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeArr[i], price); - ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 234); - ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); - ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpArrowDownColor); - ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); - ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); - ObjectSetInteger(0, arrowName, OBJPROP_HIDDEN, true); - ObjectSetString (0, arrowName, OBJPROP_TOOLTIP, - StringFormat("SELL signal\n%s\nRSI=%.2f EMA9=%.2f (slope-) WMA45=%.2f\nEMA200=%.5f (Downtrend)\nEMA9 above WMA45 ≥ %d bars", - TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), - buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i], persistN)); - } - } - - // Cập nhật panel thông tin (dùng nến vừa đóng = index 1) - // closeArr và ema200Arr đã được copy ở trên — dùng lại luôn - if (InpShowPanel && rates_total > trendN + 1 && ema200Arr[1] > 0.0) - { - // Áp dụng cùng quy tắc N nến liên tiếp cho panel hiển thị - bool panelTrendUp = true; - bool panelTrendDown = true; - for (int k = 0; k < trendN; k++) - { - const int idx = 1 + k; - if (ema200Arr[idx] <= 0.0) { panelTrendUp = false; panelTrendDown = false; break; } - if (closeArr[idx] <= ema200Arr[idx]) panelTrendUp = false; - if (closeArr[idx] >= ema200Arr[idx]) panelTrendDown = false; - } - - string trendTxt; - color trendClr; - if (panelTrendUp) { trendTxt = StringFormat("Trend : UPTREND (%d closes > EMA200)", trendN); trendClr = InpArrowUpColor; } - else if (panelTrendDown) { trendTxt = StringFormat("Trend : DOWNTREND (%d closes < EMA200)", trendN); trendClr = InpArrowDownColor; } - else { trendTxt = "Trend : RANGE / SWITCHING"; trendClr = clrSilver; } - - UpdateLabel(LBL_TREND, trendTxt, trendClr); - UpdateLabel(LBL_RSI_VAL, StringFormat("RSI : %6.2f", buf_RSI[1]), clrMediumOrchid); - UpdateLabel(LBL_EMA9VAL, StringFormat("EMA9 : %6.2f", buf_EMA9[1]), clrDarkOrange); - UpdateLabel(LBL_WMA45VAL, StringFormat("WMA45 : %6.2f", buf_WMA45[1]),clrDodgerBlue); - } + // ── Cập nhật panel thông tin (góc trên-phải) ───────────────────── + Panel_Update(closeArr, ema200Arr, trendN, rates_total); ChartRedraw(0); - // Diagnostic: log lần đầu OnCalculate hoàn thành full để xác minh data - static bool firstSuccess = false; - if (!firstSuccess && copyN >= rates_total) - { - firstSuccess = true; - int upCount = 0, downCount = 0, rangeCount = 0, zeroEma = 0; - const int n = MathMin(500, rates_total - 2); - for (int i = 1; i <= n; i++) - { - if (buf_EMA200[i] <= 0.0) zeroEma++; - if (buf_Trend[i] > 0.5) upCount++; - else if (buf_Trend[i] < -0.5) downCount++; - else rangeCount++; - } - PrintFormat("[RsiMom] First-pass OK rates_total=%d copyN=%d trendN=%d need=%d", - rates_total, copyN, trendN, need); - PrintFormat("[RsiMom] bar1: RSI=%.2f EMA9=%.2f WMA45=%.2f EMA200=%.5f Trend=%.0f Signal=%.0f close[1]=%.5f", - buf_RSI[1], buf_EMA9[1], buf_WMA45[1], buf_EMA200[1], buf_Trend[1], buf_Signal[1], closeArr[1]); - PrintFormat("[RsiMom] last %d bars trend dist: UP=%d DOWN=%d RANGE=%d (zeroEma200=%d)", - n, upCount, downCount, rangeCount, zeroEma); - } + // ── Diagnostic: log lần đầu OnCalculate hoàn thành full ────────── + Diagnostics_FirstPass(rates_total, copyN, trendN, need, closeArr); // Return số bar thật sự đã xử lý — nếu copyN < rates_total thì lần sau MT5 // sẽ scan tiếp phần còn lại (do chênh prev_calculated) return copyN; } - //+------------------------------------------------------------------+ diff --git a/Profiles/Charts/Default/order.wnd b/Profiles/Charts/Default/order.wnd index 8b354166ef810749096000f9daa11041dd2eb551..bfaa30c6c44592c5caddbe96c34b451a251ecee8 100644 GIT binary patch delta 33 pcmew+oW=D2-$WU;$>%sVCb4k|7%&(!=rJTSWH1yl@G@{Q006h2S2k&& zkR0YxTEy;ZXJ%(-c4v3z|M}yky)c#BUfaYz+s;aBs3+Fi!j`tCp4iB?d{;JwI;B6h z4Q(Cx75$Hl66f0Xa9VoOa1(TOQd3(&8Cc(jmeRhn3(NR&$|bZbdus`t5xkg^LyZGp zoIJ=UVWw;Q0Dl7IJ2uJMHq17rUSi1xyGrbCv86_8WaqSB857}S6l|U&)j*X$Oa9k^ zQ-rZwD61ehm!zo;GA}S(YXZ90OuBe~e{o!OR z!VU1$>8Qe~(s}Dn%^Hmn>$PFEN(T)Qcf%_eJ&BW^%t=p=yUfS49=Dj6rqoH(rS;r2 z#crC0PMZ3>Q$8!+cZ=^nFQu1FUNIiK#kgL7AMaxF2_H=Zw-{G;<7Jaqj3;g}p18$$ z;uhlxS)|X358YyX=oaJ3rhU9i-C{g-i}4g>;NxB97UP*)jAw39(8IguzEj7HEh2vt zPC90EC3&2Xz*BjkvSi_>2=vv^9x>QGln(i+ZtP3Qp_NG&A!8KcitGgJ_$QpEto5<1 zQ0DU_PtdzyjvehCbIU8?yc*kt%>w?9^_#RbwcctqQYA+iBSMUu{^hjsO4v literal 0 HcmV?d00001 diff --git a/experts.dat b/experts.dat index 029014c8c9ad2e6ca0cd93a6452d9f00ab77a94f..fb89a62c59d07065d3ae452a4087aad306758873 100644 GIT binary patch delta 92 zcmV-i0Hgn=?F7^D1hA|D0gsc>a?7)L0zd&32LJ#7v>5_w000001ZuHr(KF%P0h7U( y!jo_h5VM~IykC=|0u~KL06_pQ0A&Dp05t#rvr%Fu0+UR52(waR2?Dc-W**QDTpvmR delta 55 zcmV-70LcH-@dT#r1hA|D0gjW=a?7)L0zd%>0{{R3QYryzlM#2%lW-3Zv!4XKUz3Pt N9Fy=}EVIyN1JY}A7BK(-