diff --git a/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.ex5 b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.ex5 new file mode 100644 index 0000000..1bf30f1 Binary files /dev/null and b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.ex5 differ diff --git a/Indicators/RsiMomentumIndicator.mq5 b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 similarity index 54% rename from Indicators/RsiMomentumIndicator.mq5 rename to Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 index 439b3b9..503905f 100644 --- a/Indicators/RsiMomentumIndicator.mq5 +++ b/Indicators/RsiMomentumIndicator/RsiMomentumIndicator.mq5 @@ -7,15 +7,15 @@ #property copyright "RsiMomentumIndicator" #property version "1.00" #property indicator_separate_window -#property indicator_buffers 3 +#property indicator_buffers 4 #property indicator_plots 3 #property indicator_minimum 0 #property indicator_maximum 100 -// --- Buffer 0: RSI14 --- +// --- Plot 0 (Buffer 0 + Color buffer 3): RSI14 với màu thay đổi theo vùng extreme --- #property indicator_label1 "RSI(14)" -#property indicator_type1 DRAW_LINE -#property indicator_color1 clrMediumOrchid +#property indicator_type1 DRAW_COLOR_LINE +#property indicator_color1 clrMediumOrchid, clrRed // index 0 = bình thường, index 1 = extreme #property indicator_style1 STYLE_SOLID #property indicator_width1 2 @@ -42,6 +42,15 @@ input int InpEMA9Period = 9; input int InpWMA45Period = 45; input int InpEMA200Period = 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 extreme" +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 double InpRSIExtremeHigh = 75.0; // RSI > ngưỡng này → tô đỏ trên biểu đồ +input double InpRSIExtremeLow = 25.0; // RSI < ngưỡng này → tô đỏ trên biểu đồ + 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 @@ -57,6 +66,7 @@ input bool InpShowPanel = true; double buf_RSI[]; double buf_EMA9[]; double buf_WMA45[]; +double buf_RSI_color[]; // color index buffer cho RSI (0 = normal, 1 = extreme red) int h_RSI = INVALID_HANDLE; int h_EMA9 = INVALID_HANDLE; @@ -103,17 +113,31 @@ void UpdateLabel(const string name, const string text, const color clr) int OnInit() { // Đă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); - SetIndexBuffer(2, buf_WMA45, INDICATOR_DATA); + SetIndexBuffer(0, buf_RSI, INDICATOR_DATA); + SetIndexBuffer(1, buf_EMA9, INDICATOR_DATA); + SetIndexBuffer(2, buf_WMA45, INDICATOR_DATA); + SetIndexBuffer(3, buf_RSI_color, INDICATOR_COLOR_INDEX); - ArraySetAsSeries(buf_RSI, true); - ArraySetAsSeries(buf_EMA9, true); - ArraySetAsSeries(buf_WMA45, true); + ArraySetAsSeries(buf_RSI, true); + ArraySetAsSeries(buf_EMA9, true); + ArraySetAsSeries(buf_WMA45, true); + ArraySetAsSeries(buf_RSI_color, true); IndicatorSetString (INDICATOR_SHORTNAME, StringFormat("RsiMom(%d)", InpRSIPeriod)); IndicatorSetInteger(INDICATOR_DIGITS, 2); + // Vẽ 2 đường ngang: Overbought (70) và Oversold (30) + IndicatorSetInteger(INDICATOR_LEVELS, 2); + IndicatorSetDouble (INDICATOR_LEVELVALUE, 0, InpRSIOverbought); + IndicatorSetDouble (INDICATOR_LEVELVALUE, 1, InpRSIOversold); + IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrSilver); + IndicatorSetInteger(INDICATOR_LEVELCOLOR, 1, clrSilver); + IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DOT); + IndicatorSetInteger(INDICATOR_LEVELSTYLE, 1, STYLE_DOT); + IndicatorSetInteger(INDICATOR_LEVELWIDTH, 0, 1); + IndicatorSetInteger(INDICATOR_LEVELWIDTH, 1, 1); + PlotIndexSetString (0, PLOT_LABEL, "RSI(14)"); + // Tạo handles chỉ báo h_RSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); if (h_RSI == INVALID_HANDLE) @@ -186,98 +210,156 @@ int OnCalculate(const int rates_total, if (CopyBuffer(h_EMA9, 0, 0, rates_total, buf_EMA9) < rates_total) return prev_calculated; if (CopyBuffer(h_WMA45, 0, 0, rates_total, buf_WMA45) < rates_total) return prev_calculated; + // Tô màu RSI theo vùng extreme: + // value > InpRSIExtremeHigh hoặc < InpRSIExtremeLow → color index 1 (đỏ) + // ngược lại → color index 0 (mặc định) + // Buffer đang AS_SERIES → [0] = bar mới nhất; quét toàn bộ cho đơn giản (O(N) nhẹ). + for (int i = 0; i < rates_total; i++) + { + const double v = buf_RSI[i]; + buf_RSI_color[i] = (v > InpRSIExtremeHigh || v < InpRSIExtremeLow) ? 1.0 : 0.0; + } + // Số nến cần quét để phát hiện giao cắt mới int barsToScan = (prev_calculated == 0) ? rates_total - 2 : (rates_total - prev_calculated + 2); barsToScan = MathMin(barsToScan, rates_total - 2); - // Sao chép time, high, low vào mảng cục bộ AS_SERIES để dùng trong vòng lặp + // Sao chép time, high, low, close và EMA200 để phát hiện giao cắt + lọc trend + // +InpTrendConfirmBars vì cần check N nến liên tiếp đóng cùng phía EMA200 + const int trendN = MathMax(1, InpTrendConfirmBars); + const int need = barsToScan + 2 + trendN; datetime timeArr[]; - double highArr[]; - double lowArr[]; - ArraySetAsSeries(timeArr, true); - ArraySetAsSeries(highArr, true); - ArraySetAsSeries(lowArr, true); + double highArr[], lowArr[], closeArr[], ema200Arr[]; + ArraySetAsSeries(timeArr, true); + ArraySetAsSeries(highArr, true); + ArraySetAsSeries(lowArr, true); + ArraySetAsSeries(closeArr, true); + ArraySetAsSeries(ema200Arr, true); - if (CopyTime (_Symbol, _Period, 0, barsToScan + 2, timeArr) < barsToScan + 2) return prev_calculated; - if (CopyHigh (_Symbol, _Period, 0, barsToScan + 2, highArr) < barsToScan + 2) return prev_calculated; - if (CopyLow (_Symbol, _Period, 0, barsToScan + 2, lowArr) < barsToScan + 2) return prev_calculated; + if (CopyTime (_Symbol, _Period, 0, need, timeArr) < need) return prev_calculated; + if (CopyHigh (_Symbol, _Period, 0, need, highArr) < need) return prev_calculated; + if (CopyLow (_Symbol, _Period, 0, need, lowArr) < need) return prev_calculated; + 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 giao cắt RSI vs WMA45 và vẽ mũi tên lên chart chính + // 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--) { - // Tránh truy cập vượt quá phạm vi buffer - if (i + 1 >= rates_total) continue; + if (i + 1 >= rates_total) continue; + if (i + trendN >= rates_total) continue; // không đủ lịch sử cho N nến trend + if (ema200Arr[i] <= 0.0) continue; // EMA200 chưa tính đủ bars 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; + // 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 + 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; + } + + // Vị trí EMA9 vs WMA45 — kiểm tra "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]); + + // 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 đủ 5 điều kiện + const bool validBuy = crossUp && trendUp && ema9BelowWma && ema9SlopeUp && rsiOkBuy; + const bool validSell = crossDown && trendDown && ema9AboveWma && 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; // đã có → bỏ qua + if (ObjectFind(0, arrowName) >= 0) continue; - if (crossUp) + if (validBuy) { - // Mũi tên lên dưới đáy nến + // 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); // ↑ Wingdings + 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("RSI cắt lên WMA45 | %s | RSI=%.2f WMA45=%.2f", + StringFormat("BUY signal\n%s\nRSI=%.2f EMA9=%.2f (slope+) WMA45=%.2f\nEMA200=%.5f (Uptrend)", TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), - buf_RSI[i], buf_WMA45[i])); + buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i])); } - else // crossDown + else // validSell { - // Mũi tên xuống trên đỉnh nến + // 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); // ↓ Wingdings + 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("RSI cắt xuống WMA45 | %s | RSI=%.2f WMA45=%.2f", + StringFormat("SELL signal\n%s\nRSI=%.2f EMA9=%.2f (slope-) WMA45=%.2f\nEMA200=%.5f (Downtrend)", TimeToString(timeArr[i], TIME_DATE|TIME_MINUTES), - buf_RSI[i], buf_WMA45[i])); + buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i])); } } // Cập nhật panel thông tin (dùng nến vừa đóng = index 1) - if (InpShowPanel && rates_total > 2) + // closeArr và ema200Arr đã được copy ở trên — dùng lại luôn + if (InpShowPanel && rates_total > trendN + 1 && ema200Arr[1] > 0.0) { - double ema200Arr[]; - ArraySetAsSeries(ema200Arr, true); - - double closeArr[]; - ArraySetAsSeries(closeArr, true); - - bool panelOk = (CopyBuffer(h_EMA200, 0, 0, 3, ema200Arr) >= 3) && - (CopyClose(_Symbol, _Period, 0, 3, closeArr) >= 3); - - if (panelOk && 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 bool trendUp = (closeArr[1] > ema200Arr[1]); - const color trendClr = trendUp ? InpArrowUpColor : InpArrowDownColor; - const string trendTxt = trendUp - ? "Trend : UPTREND (Close > EMA200)" - : "Trend : DOWNTREND (Close < EMA200)"; - - 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); + 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); } ChartRedraw(0);