Cần cải thiện indicator
Đánh giá Indicator (dùng độc lập, manual trading) ⭐ Điểm mạnh # Điểm Chi tiết 1 Cốt lõi chiến lược chắc Trend (EMA200) + Momentum (RSI cross WMA45) — combo cổ điển nhưng hiệu quả: chỉ trade khi pullback trong trend 2 Không repaint Cross check [i+1] vs [i] — cả 2 bar đã đóng. Bar 0 (đang hình thành) bị skip. Mũi tên một khi vẽ là cố định. 3 Bộ filter đa lớp 6 điều kiện độc lập (trend, cross, EMA9 position, persist N bars, slope, RSI extreme) → giảm false signal đáng kể 4 EMA200 auto-attach lên main chart Trader không cần add tay 5 Tooltip mũi tên đầy đủ thông tin Hover thấy RSI/EMA9/WMA45/EMA200 tại bar signal 6 Clean code OnCalculate xử lý prev_calculated đúng, ArraySetAsSeries nhất quán, INDICATOR_DIGITS=2 cho subwindow rõ ❌ Điểm yếu (cho manual trading) # Vấn đề Tác động Ưu tiên fix 1 Không có Alert Trader phải dán mắt vào chart — bỏ lỡ signal khi rời máy 🔴 Critical 2 Không gợi ý Entry/SL/TP Mũi tên chỉ nói "có signal" — không có level entry, SL theo swing/ATR, TP theo R:R. Trader phải tự nghĩ 🔴 High 3 Không phân biệt signal mạnh/yếu Cross gần RSI=50 vs cross gần RSI=68 cùng vẽ arrow giống nhau → không có rating 🟡 Medium 4 Không có volatility filter (ATR) Trade trong thị trường quá lặng (Asia session) hoặc quá điên (news) đều dở 🟡 Medium 5 Không có session/time filter M15-H1 ở session Á cho signal yếu, nên skip 🟡 Medium 6 Trend filter có thể quá khắt N=2 closes trên EMA200 — choppy market sẽ rất ít signal 🟡 Medium 7 Mean-reversion bias Cross-back-into-trend tốt cho pullback, nhưng vào breakout/trending mạnh thì entry trễ 🟢 Low (design choice) 8 Panel position lỗi x=10 từ right edge → bị price scale che 🟡 Medium 9 Không track performance Trader không biết indicator này ở chart hiện tại win/loss thế nào trong N bar gần nhất 🟢 Nice-to-have 10 EMA200 attach có thể trùng Nếu user có EMA200 sẵn từ template → 2 line đè nhau 🟢 Low Phân tích logic chi tiết Entry rule (mạnh): crossUp && trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy Thực ra là pullback confirmation: trend up + RSI giảm về dưới WMA45 (pullback) + giờ cắt lên (resume momentum) ✅ Logic này có edge thực sự trên dữ liệu trending market Cảnh báo về pickup setup: Cross RSI vs WMA45 trên RSI có degree of freedom thấp (45 chu kỳ RSI rất mượt) → cross hiếm và đáng tin hơn cross EMA giá thông thường Tuy nhiên frequency của signal sẽ thấp — phù hợp swing trader hơn scalper Repaint check: ✅ Buffer signal được set TẠI BAR i (bar đã đóng) ✅ Arrow tạo bằng timeArr[i] (timestamp cố định) ⚠️ Lưu ý: trên timeframe nhỏ (M1/M5), nếu bar 0 đang hình thành biến động lớn, indicator panel "Trend" có thể flicker nhưng KHÔNG sai về signal đã vẽ Điểm tổng: 6.5 / 10 (dùng manual) Profile trader Phù hợp? Swing trader có hệ thống RM riêng ✅ Tốt — chỉ cần signal context, đã tự lo entry/SL/TP Discretionary day trader 🟡 Tạm — cần thêm alert + price levels Scalper ❌ Không — signal frequency thấp, lag Người mới ❌ Không — thiếu action info, dễ vào sai entry/SL Đề xuất nâng cấp ưu tiên (nếu muốn perfect cho manual) Alert system — Alert(), SendNotification(), PlaySound() khi có signal mới Entry/SL/TP suggestion — vẽ horizontal lines: Entry: 50% giữa close và swing low/high gần nhất (đúng spec ban đầu của bạn) SL: dưới swing low - ATR*0.2 TP: 2R từ entry Signal strength — coloring: arrow xanh nhạt (weak), xanh đậm (strong) dựa trên RSI distance to OB/OS + EMA9 slope steepness ATR filter — input InpMinATR, skip signal khi ATR(14) < threshold Win/Loss panel — đếm signal trong N bar gần nhất + giả định 2R outcome → hiển thị winrate ước tính Mình có thể implement bất kỳ item nào trên (đề xuất bắt đầu từ #1 + #2 vì là 2 thứ trader manual cần nhất). Bạn muốn làm tiếp không?
This commit is contained in:
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
#property copyright "RsiMomentumIndicator"
|
||||
#property version "1.00"
|
||||
#property indicator_separate_window
|
||||
#property indicator_buffers 3
|
||||
#property indicator_buffers 6
|
||||
#property indicator_plots 3
|
||||
#property indicator_minimum 0
|
||||
#property indicator_maximum 100
|
||||
@@ -40,7 +40,7 @@ input group "Chỉ báo"
|
||||
input int InpRSIPeriod = 14;
|
||||
input int InpEMA9Period = 9;
|
||||
input int InpWMA45Period = 45;
|
||||
input int InpEMA200Period = 200;
|
||||
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
|
||||
@@ -49,6 +49,9 @@ 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
|
||||
@@ -64,6 +67,9 @@ input bool InpShowPanel = true;
|
||||
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;
|
||||
@@ -110,13 +116,19 @@ 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_Signal, INDICATOR_CALCULATIONS); // ẩn — phục vụ EA
|
||||
SetIndexBuffer(4, buf_EMA200, INDICATOR_CALCULATIONS); // ẩn — EMA200 cho EA
|
||||
SetIndexBuffer(5, buf_Trend, INDICATOR_CALCULATIONS); // ẩn — trend cho EA
|
||||
|
||||
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_Signal, true);
|
||||
ArraySetAsSeries(buf_EMA200, true);
|
||||
ArraySetAsSeries(buf_Trend, true);
|
||||
|
||||
IndicatorSetString (INDICATOR_SHORTNAME, StringFormat("RsiMom(%d)", InpRSIPeriod));
|
||||
IndicatorSetInteger(INDICATOR_DIGITS, 2);
|
||||
@@ -135,7 +147,7 @@ int OnInit()
|
||||
if (h_WMA45 == INVALID_HANDLE)
|
||||
{ Print("[RsiMom] Không tạo được handle WMA45(RSI)"); return INIT_FAILED; }
|
||||
|
||||
h_EMA200 = iMA(_Symbol, _Period, InpEMA200Period, 0, MODE_EMA, PRICE_CLOSE);
|
||||
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; }
|
||||
|
||||
@@ -195,10 +207,20 @@ int OnCalculate(const int rates_total,
|
||||
const int wmaBars = BarsCalculated(h_WMA45);
|
||||
const int ema200Bars = BarsCalculated(h_EMA200);
|
||||
if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0)
|
||||
{
|
||||
static datetime lastWarn = 0;
|
||||
if (TimeCurrent() - lastWarn > 30)
|
||||
{
|
||||
PrintFormat("[RsiMom] Source not ready yet: RSI=%d EMA9=%d WMA45=%d EMA200=%d (rates_total=%d)",
|
||||
rsiBars, ema9Bars, wmaBars, ema200Bars, rates_total);
|
||||
lastWarn = TimeCurrent();
|
||||
}
|
||||
return 0; // chưa có dữ liệu — return 0 để MT5 retry ngay
|
||||
}
|
||||
|
||||
// Chỉ copy số bar mà tất cả source đã tính xong (tránh return giữa chừng)
|
||||
const int srcMin = MathMin(MathMin(rsiBars, ema9Bars), wmaBars);
|
||||
// Chỉ copy số bar mà TẤT CẢ source (incl. EMA200) đã tính xong
|
||||
// → tránh CopyBuffer EMA200 fail giữa chừng làm buf_EMA200/buf_Trend stale
|
||||
const int srcMin = MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars);
|
||||
const int copyN = MathMin(srcMin, rates_total);
|
||||
if (copyN < minBars) return 0;
|
||||
|
||||
@@ -235,20 +257,24 @@ int OnCalculate(const int rates_total,
|
||||
|
||||
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ộ (timeArr/closeArr/ema200Arr)
|
||||
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
|
||||
// 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++)
|
||||
@@ -259,13 +285,47 @@ int OnCalculate(const int rates_total,
|
||||
if (closeArr[idx] >= ema200Arr[idx]) trendDown = false;
|
||||
if (!trendUp && !trendDown) break;
|
||||
}
|
||||
buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0);
|
||||
|
||||
// Vị trí EMA9 vs WMA45 — kiểm tra "lực mua/bán mới bắt đầu chiếm ưu thế"
|
||||
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]);
|
||||
@@ -276,9 +336,9 @@ int OnCalculate(const int rates_total,
|
||||
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;
|
||||
// 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;
|
||||
|
||||
@@ -288,6 +348,8 @@ int OnCalculate(const int rates_total,
|
||||
|
||||
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)
|
||||
@@ -300,12 +362,14 @@ int OnCalculate(const int rates_total,
|
||||
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)",
|
||||
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]));
|
||||
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)
|
||||
@@ -318,9 +382,9 @@ int OnCalculate(const int rates_total,
|
||||
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)",
|
||||
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]));
|
||||
buf_RSI[i], buf_EMA9[i], buf_WMA45[i], ema200Arr[i], persistN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +416,29 @@ int OnCalculate(const int 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user