Update FVG filter, MS filter cho RSI momentum

This commit is contained in:
Bell
2026-05-20 22:33:51 +07:00
parent fc8ec7c2a5
commit 63ed6a1ae0
+743 -82
View File
@@ -4,7 +4,7 @@
//| RSI + EMA9/WMA45 trên RSI + EMA200(close), signal, mũi tên, panel |
//+------------------------------------------------------------------+
#property copyright "RsiMomentumEA"
#property version "3.13"
#property version "3.22"
#include <Trade/Trade.mqh>
@@ -25,6 +25,20 @@ input double InpRSIOversold = 30.0;
input group "Bộ lọc EMA9 vs WMA45 (chống nhiễu)"
input int InpEma9PersistBars = 3;
input group "Bộ lọc cấu trúc 4 swing (H0 H1 L0 L1)"
input bool InpStructFilterEnabled = false; // BUY: chặn LH-LL | SELL: chặn HH-HL | false = tắt
input int InpStructSwingRange = 2; // pivot: N nến mỗi phía (giống HyperICT)
input int InpStructLookbackBars = 120; // quét tối đa bao nhiêu nến về quá khứ
input group "FVG + lệnh Limit (5 nến từ nến hiện tại)"
input bool InpFvgFilterEnabled = true; // không có FVG hợp lệ trong 5 nến → bỏ lệnh
input int InpFvgScanBars = 5; // số nến quét (từ shift 0 hoặc signal về trước)
input int InpFvgMinGapPoints = 0; // khe FVG tối thiểu (point)
input int InpFvgLimitExpireBars = 20; // hủy pending nếu không khớp sau N nến
input int InpFvgWaitBarsAfterSignal = 1; // đợi thêm N nến đóng sau tín hiệu rồi mới quét FVG
input double InpFvgLimitMidRatio = 0.5; // giá Limit trong FVG: 0.5 = 50% (CE)
input bool InpFvgUseSignalBarWindow = false; // false: quét từ nến 0 | true: từ nến signal (1)
input group "Mũi tên giao cắt"
input color InpArrowUpColor = clrLime;
input color InpArrowDownColor = clrTomato;
@@ -54,11 +68,24 @@ input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-ma
input int InpSlippagePoints = 30;
input bool InpOnePositionFlat = true;
input group "SL buffer theo ATR (đẩy SL xa đáy/đỉnh swing)"
input bool InpSlAtrBufferEnabled = true;
input int InpSlAtrPeriod = 14;
input double InpSlAtrMultiplier = 0.5; // khoảng cách thêm = ATR(shift 1) × hệ số
input bool InpSlAtrAddSpread = true; // cộng thêm buffer spread vào SL
input group "Thống kê (góc dưới-trái chart)"
input bool InpShowStats = true;
input int InpStatFontSize = 9;
input int InpStatLinePad = 26; // khoảng cách dọc giữa các dòng (pixel)
input int InpStatBottomMargin = 28; // lề dưới block thống kê
input color InpStatColor = clrSilver;
input group "Nhãn lý do vào/không vào (dưới mũi tên)"
input bool InpShowSignalReason = true;
input int InpReasonFontSize = 8;
input int InpReasonOffsetPts = 45; // khoảng cách dưới/ trên mũi tên (point)
//--- Buffers & state (trùng State.mqh)
double buf_RSI[];
double buf_EMA9[];
@@ -71,6 +98,7 @@ int h_RSI = INVALID_HANDLE;
int h_EMA9 = INVALID_HANDLE;
int h_WMA45 = INVALID_HANDLE;
int h_EMA200 = INVALID_HANDLE;
int h_ATR = INVALID_HANDLE;
const string OBJ_PREFIX = "RsiMomEA_";
const string LBL_TITLE = OBJ_PREFIX + "title";
@@ -83,9 +111,7 @@ const string STAT_PREFIX = "RsiMomEA_ST_";
const string STAT_L1 = STAT_PREFIX + "line1";
const string STAT_L2 = STAT_PREFIX + "line2";
const string STAT_L3 = STAT_PREFIX + "line3";
// Khoảng cách dọc giữa các dòng thống kê (pixel): bước = fontSize + STAT_LINE_PAD
const int STAT_Y_ANCHOR = 18;
const int STAT_LINE_PAD = 16;
const string REASON_PREFIX = OBJ_PREFIX + "RSN_";
long g_statExitDeals = 0;
long g_statSL = 0;
@@ -100,18 +126,47 @@ bool g_firstCalc = true;
static int g_prevCalculated = 0;
datetime g_tradeBarAnchor = 0;
datetime g_pendingPlacedBarTime = 0;
datetime g_deferSignalBarTime = 0;
bool g_deferIsBuy = false;
CTrade g_trade;
long ActChart() { return ChartID(); }
void SetTradeFillingFromSymbol();
double Sl_GetBufferDistance(const int atrShift = 1);
bool NearestSwingSlTp(const bool isBuy, const double entry, const int dig, double &sl, double &tp);
bool StopsValid(const bool isBuy, const double price, const double sl, const double tp);
int CountMyMagicPositions();
int CountMyMagicPendingOrders();
bool HasMyMagicPositionOrPending();
double NormalizeLots(double v);
double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice);
void TradeTryOnBarOpen(const int calcRet);
void TradeExecuteOrder(const bool isBuy);
void Pending_ManageExpiry();
bool Pending_CancelMine();
bool Struct_IsSwingHigh(const int shift, const int range);
bool Struct_IsSwingLow(const int shift, const int range);
bool Struct_CollectHL2(const int fromShift, double &h0, double &h1, double &l0, double &l1);
bool Struct_IsHHHL(const int fromShift);
bool Struct_IsLHLL(const int fromShift);
bool Struct_AllowsBuy(const int fromShift);
bool Struct_AllowsSell(const int fromShift);
int Fvg_TradeWindowStart();
bool Fvg_TryBullZone(const int s0, double &limitPrice);
bool Fvg_TryBearZone(const int s0, double &limitPrice);
bool Fvg_SelectBestBuyLimit(const int windowStart, const int scanBars, double &limitPrice);
bool Fvg_SelectBestSellLimit(const int windowStart, const int scanBars, double &limitPrice);
bool Fvg_AllowsBuy(const int windowStart);
bool Fvg_AllowsSell(const int windowStart);
string Signal_ReasonBuy(const bool trendUp, const bool ema9PersistBelow, const bool ema9SlopeUp,
const bool rsiOkBuy, const bool structOkBuy, const bool fvgOkBuy, const bool valid);
string Signal_ReasonSell(const bool trendDown, const bool ema9PersistAbove, const bool ema9SlopeDown,
const bool rsiOkSell, const bool structOkSell, const bool fvgOkSell, const bool valid);
void Signal_DrawReasonLabel(const long ch, const datetime barTime, const double anchorPrice,
const bool isBuy, const bool canEnter, const string detail);
void Stats_CreateObjects();
void Stats_UpdateDisplay();
void OnTradeTransaction(const MqlTradeTransaction &trans,
@@ -145,6 +200,12 @@ bool Handles_CreateAll()
Print("[RsiMomEA] Không tạo được handle EMA200");
return false;
}
h_ATR = iATR(_Symbol, _Period, MathMax(1, InpSlAtrPeriod));
if (h_ATR == INVALID_HANDLE)
{
Print("[RsiMomEA] Không tạo được handle ATR");
return false;
}
return true;
}
@@ -155,7 +216,8 @@ void Handles_ReleaseAll()
if (h_EMA9 != INVALID_HANDLE) IndicatorRelease(h_EMA9);
if (h_WMA45 != INVALID_HANDLE) IndicatorRelease(h_WMA45);
if (h_EMA200 != INVALID_HANDLE) IndicatorRelease(h_EMA200);
h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = INVALID_HANDLE;
if (h_ATR != INVALID_HANDLE) IndicatorRelease(h_ATR);
h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = h_ATR = INVALID_HANDLE;
}
//+------------------------------------------------------------------+
@@ -296,6 +358,345 @@ void Alerts_CheckAndFire(const datetime &timeArr[], const double &closeArr[],
}
}
//+------------------------------------------------------------------+
//| Pivot swing trên giá (độc lập HyperICT) — H0/L0 = gần nhất |
//+------------------------------------------------------------------+
bool Struct_IsSwingHigh(const int shift, const int range)
{
const int str = MathMax(1, range);
const double h = iHigh(_Symbol, _Period, shift);
for(int k = 1; k <= str; k++)
{
if(shift + k >= Bars(_Symbol, _Period) || shift - k < 0)
return false;
if(h <= iHigh(_Symbol, _Period, shift + k) || h <= iHigh(_Symbol, _Period, shift - k))
return false;
}
return true;
}
bool Struct_IsSwingLow(const int shift, const int range)
{
const int str = MathMax(1, range);
const double l = iLow(_Symbol, _Period, shift);
for(int k = 1; k <= str; k++)
{
if(shift + k >= Bars(_Symbol, _Period) || shift - k < 0)
return false;
if(l >= iLow(_Symbol, _Period, shift + k) || l >= iLow(_Symbol, _Period, shift - k))
return false;
}
return true;
}
bool Struct_CollectHL2(const int fromShift, double &h0, double &h1, double &l0, double &l1)
{
const int rng = MathMax(1, InpStructSwingRange);
const int lb = MathMax(20, InpStructLookbackBars);
const int start = fromShift + rng;
const int bars = Bars(_Symbol, _Period);
const int last = MathMin(lb, bars - rng - 1);
if(start > last)
return false;
double hiP[], loP[];
datetime hiT[], loT[];
ArrayResize(hiP, 0);
ArrayResize(loP, 0);
ArrayResize(hiT, 0);
ArrayResize(loT, 0);
for(int sh = start; sh <= last; sh++)
{
if(Struct_IsSwingHigh(sh, rng))
{
const int n = ArraySize(hiP);
ArrayResize(hiP, n + 1);
ArrayResize(hiT, n + 1);
hiP[n] = iHigh(_Symbol, _Period, sh);
hiT[n] = iTime(_Symbol, _Period, sh);
}
if(Struct_IsSwingLow(sh, rng))
{
const int n = ArraySize(loP);
ArrayResize(loP, n + 1);
ArrayResize(loT, n + 1);
loP[n] = iLow(_Symbol, _Period, sh);
loT[n] = iTime(_Symbol, _Period, sh);
}
}
const int nH = ArraySize(hiP);
const int nL = ArraySize(loP);
if(nH < 2 || nL < 2)
return false;
int iNewH = 0;
for(int j = 1; j < nH; j++)
if(hiT[j] > hiT[iNewH])
iNewH = j;
int iOldH = -1;
for(int j = 0; j < nH; j++)
{
if(j == iNewH)
continue;
if(iOldH < 0 || hiT[j] > hiT[iOldH])
iOldH = j;
}
int iNewL = 0;
for(int j = 1; j < nL; j++)
if(loT[j] > loT[iNewL])
iNewL = j;
int iOldL = -1;
for(int j = 0; j < nL; j++)
{
if(j == iNewL)
continue;
if(iOldL < 0 || loT[j] > loT[iOldL])
iOldL = j;
}
h0 = hiP[iNewH];
h1 = hiP[iOldH];
l0 = loP[iNewL];
l1 = loP[iOldL];
return true;
}
bool Struct_IsHHHL(const int fromShift)
{
double h0 = 0, h1 = 0, l0 = 0, l1 = 0;
if(!Struct_CollectHL2(fromShift, h0, h1, l0, l1))
return false;
return (h0 > h1 && l0 > l1);
}
bool Struct_IsLHLL(const int fromShift)
{
double h0 = 0, h1 = 0, l0 = 0, l1 = 0;
if(!Struct_CollectHL2(fromShift, h0, h1, l0, l1))
return false;
return (h0 < h1 && l0 < l1);
}
// BUY: chỉ chặn khi 4 swing = LH-LL (ngược tín hiệu mua). HH-HL hoặc lộn xộn = OK.
bool Struct_AllowsBuy(const int fromShift)
{
if(!InpStructFilterEnabled)
return true;
return !Struct_IsLHLL(fromShift);
}
// SELL: chỉ chặn khi 4 swing = HH-HL (ngược tín hiệu bán). LH-LL hoặc lộn xộn = OK.
bool Struct_AllowsSell(const int fromShift)
{
if(!InpStructFilterEnabled)
return true;
return !Struct_IsHHHL(fromShift);
}
//+------------------------------------------------------------------+
//| FVG ICT 3 nến trong cửa sổ scanBars (mọi vị trí s0..s0+2) |
//| Bull zone: [High(s2) .. Low(s0)] — Buy Limit tại % FVG (mặc định 50% CE) |
//| Bear zone: [High(s0) .. Low(s2)] — Sell Limit tại % FVG (mặc định 50% CE) |
//+------------------------------------------------------------------+
int Fvg_TradeWindowStart()
{
return InpFvgUseSignalBarWindow ? 1 : 0;
}
bool Fvg_TryBullZone(const int s0, double &limitPrice)
{
const int s2 = s0 + 2;
if(s2 >= Bars(_Symbol, _Period))
return false;
const double minGap = InpFvgMinGapPoints * _Point;
const double bottom = iHigh(_Symbol, _Period, s2);
const double top = iLow(_Symbol, _Period, s0);
if(top - bottom <= minGap)
return false;
const double ratio = MathMax(0.0, MathMin(1.0, InpFvgLimitMidRatio));
limitPrice = bottom + (top - bottom) * ratio;
return true;
}
bool Fvg_TryBearZone(const int s0, double &limitPrice)
{
const int s2 = s0 + 2;
if(s2 >= Bars(_Symbol, _Period))
return false;
const double minGap = InpFvgMinGapPoints * _Point;
const double top = iLow(_Symbol, _Period, s2);
const double bottom = iHigh(_Symbol, _Period, s0);
if(top - bottom <= minGap)
return false;
const double ratio = MathMax(0.0, MathMin(1.0, InpFvgLimitMidRatio));
limitPrice = top + (bottom - top) * ratio;
return true;
}
bool Fvg_SelectBestBuyLimit(const int windowStart, const int scanBars, double &limitPrice)
{
const int bars = MathMax(3, scanBars);
const int wEnd = windowStart + bars - 1;
if(wEnd + 2 >= Bars(_Symbol, _Period))
return false;
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
bool found = false;
double best = 0.0;
for(int s0 = windowStart; s0 <= wEnd - 2; s0++)
{
double lp = 0.0;
if(!Fvg_TryBullZone(s0, lp))
continue;
lp = NormalizeDouble(lp, dig);
if(!found || lp > best)
{
best = lp;
found = true;
}
}
if(found)
limitPrice = best;
return found;
}
bool Fvg_SelectBestSellLimit(const int windowStart, const int scanBars, double &limitPrice)
{
const int bars = MathMax(3, scanBars);
const int wEnd = windowStart + bars - 1;
if(wEnd + 2 >= Bars(_Symbol, _Period))
return false;
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
bool found = false;
double best = 0.0;
for(int s0 = windowStart; s0 <= wEnd - 2; s0++)
{
double lp = 0.0;
if(!Fvg_TryBearZone(s0, lp))
continue;
lp = NormalizeDouble(lp, dig);
if(!found || lp < best)
{
best = lp;
found = true;
}
}
if(found)
limitPrice = best;
return found;
}
bool Fvg_AllowsBuy(const int windowStart)
{
if(!InpFvgFilterEnabled)
return true;
double lp = 0.0;
return Fvg_SelectBestBuyLimit(windowStart, InpFvgScanBars, lp);
}
bool Fvg_AllowsSell(const int windowStart)
{
if(!InpFvgFilterEnabled)
return true;
double lp = 0.0;
return Fvg_SelectBestSellLimit(windowStart, InpFvgScanBars, lp);
}
//+------------------------------------------------------------------+
string Signal_ReasonBuy(const bool trendUp, const bool ema9PersistBelow, const bool ema9SlopeUp,
const bool rsiOkBuy, const bool structOkBuy, const bool fvgOkBuy, const bool valid)
{
if(valid)
{
string s = "RSI cross | trend | EMA9 | struct OK";
if(InpFvgFilterEnabled)
{
s += " | FVG";
if(InpFvgWaitBarsAfterSignal > 0)
s += StringFormat(" (đợi %d nến)", InpFvgWaitBarsAfterSignal);
s += StringFormat(" Limit %.0f%% FVG", InpFvgLimitMidRatio * 100.0);
}
return s;
}
string f = "";
if(!trendUp) f += "trend ";
if(!ema9PersistBelow) f += "EMA9<WMA45 ";
if(!ema9SlopeUp) f += "EMA9 slope ";
if(!rsiOkBuy) f += "RSI OB ";
if(!structOkBuy) f += "swing LH-LL ";
if(!fvgOkBuy) f += "no FVG ";
return f;
}
string Signal_ReasonSell(const bool trendDown, const bool ema9PersistAbove, const bool ema9SlopeDown,
const bool rsiOkSell, const bool structOkSell, const bool fvgOkSell, const bool valid)
{
if(valid)
{
string s = "RSI cross | trend | EMA9 | struct OK";
if(InpFvgFilterEnabled)
{
s += " | FVG";
if(InpFvgWaitBarsAfterSignal > 0)
s += StringFormat(" (đợi %d nến)", InpFvgWaitBarsAfterSignal);
s += StringFormat(" Limit %.0f%% FVG", InpFvgLimitMidRatio * 100.0);
}
return s;
}
string f = "";
if(!trendDown) f += "trend ";
if(!ema9PersistAbove) f += "EMA9>WMA45 ";
if(!ema9SlopeDown) f += "EMA9 slope ";
if(!rsiOkSell) f += "RSI OS ";
if(!structOkSell) f += "swing HH-HL ";
if(!fvgOkSell) f += "no FVG ";
return f;
}
void Signal_DrawReasonLabel(const long ch, const datetime barTime, const double anchorPrice,
const bool isBuy, const bool canEnter, const string detail)
{
if(!InpShowSignalReason || barTime == 0)
return;
const string name = REASON_PREFIX + IntegerToString((int)barTime) + (isBuy ? "_B" : "_S");
const double yOff = InpReasonOffsetPts * _Point;
const double y = isBuy ? (anchorPrice - yOff) : (anchorPrice + yOff);
const string txt = (canEnter ? "VÀO: " : "KHÔNG: ") + detail;
const color clr = canEnter ? (isBuy ? InpArrowUpColor : InpArrowDownColor) : clrDarkGray;
if(ObjectFind(ch, name) < 0)
{
PrintFormat("[RsiMomEA] %s | %s %s", TimeToString(barTime, TIME_DATE | TIME_MINUTES), txt, _Symbol);
ObjectCreate(ch, name, OBJ_TEXT, 0, barTime, y);
ObjectSetInteger(ch, name, OBJPROP_ANCHOR, isBuy ? ANCHOR_UPPER : ANCHOR_LOWER);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpReasonFontSize));
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
}
ObjectSetDouble (ch, name, OBJPROP_PRICE, 0, y);
ObjectSetString (ch, name, OBJPROP_TEXT, txt);
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpReasonFontSize));
}
//+------------------------------------------------------------------+
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[],
@@ -363,38 +764,61 @@ void SignalScan_Run(const int barsToScan, const int rates_total, const int need,
const bool rsiOkBuy = (buf_RSI[i] < InpRSIOverbought);
const bool rsiOkSell = (buf_RSI[i] > InpRSIOversold);
const bool validBuy = crossUp && trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy;
const bool validSell = crossDown && trendDown && ema9PersistAbove && ema9SlopeDown && rsiOkSell;
if (!validBuy && !validSell) continue;
const bool structOkBuy = Struct_AllowsBuy(i);
const bool structOkSell = Struct_AllowsSell(i);
const bool fvgOkBuy = Fvg_AllowsBuy(i);
const bool fvgOkSell = Fvg_AllowsSell(i);
const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]);
if (ObjectFind(ch, arrowName) >= 0) continue;
if (validBuy)
if(crossUp)
{
buf_Signal[i] = 1.0;
const double price = lowArr[i] - arrowOffset;
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], price);
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 233);
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP);
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowUpColor);
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "BUY (EA)");
const bool validBuy = trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy && structOkBuy && fvgOkBuy;
const string reason = Signal_ReasonBuy(trendUp, ema9PersistBelow, ema9SlopeUp, rsiOkBuy,
structOkBuy, fvgOkBuy, validBuy);
const double arrowPrice = lowArr[i] - arrowOffset;
Signal_DrawReasonLabel(ch, timeArr[i], arrowPrice, true, validBuy, reason);
if(validBuy)
{
const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]);
if(ObjectFind(ch, arrowName) < 0)
{
buf_Signal[i] = 1.0;
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], arrowPrice);
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 233);
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP);
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowUpColor);
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "BUY | " + reason);
}
}
}
else
if(crossDown)
{
buf_Signal[i] = -1.0;
const double price = highArr[i] + arrowOffset;
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], price);
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 234);
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowDownColor);
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "SELL (EA)");
const bool validSell = trendDown && ema9PersistAbove && ema9SlopeDown && rsiOkSell && structOkSell && fvgOkSell;
const string reason = Signal_ReasonSell(trendDown, ema9PersistAbove, ema9SlopeDown, rsiOkSell,
structOkSell, fvgOkSell, validSell);
const double arrowPrice = highArr[i] + arrowOffset;
Signal_DrawReasonLabel(ch, timeArr[i], arrowPrice, false, validSell, reason);
if(validSell)
{
const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]);
if(ObjectFind(ch, arrowName) < 0)
{
buf_Signal[i] = -1.0;
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], arrowPrice);
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 234);
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowDownColor);
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "SELL | " + reason);
}
}
}
}
}
@@ -512,7 +936,10 @@ int OnInit()
g_lastAlertSellBar = 0;
g_firstCalc = true;
g_prevCalculated = 0;
g_tradeBarAnchor = iTime(_Symbol, _Period, 0);
g_tradeBarAnchor = iTime(_Symbol, _Period, 0);
g_pendingPlacedBarTime = 0;
g_deferSignalBarTime = 0;
g_deferIsBuy = false;
g_statExitDeals = 0;
g_statSL = 0;
@@ -538,6 +965,7 @@ int OnInit()
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Pending_CancelMine();
Handles_ReleaseAll();
ObjectsDeleteAll(ActChart(), OBJ_PREFIX);
ObjectsDeleteAll(ActChart(), STAT_PREFIX);
@@ -547,6 +975,8 @@ void OnDeinit(const int reason)
//+------------------------------------------------------------------+
void OnTick()
{
Pending_ManageExpiry();
const int rates_total = Bars(_Symbol, _Period);
const int ret = RsiMomentum_OnCalculate(rates_total, g_prevCalculated);
if (ret != 0)
@@ -560,6 +990,113 @@ void OnTick()
}
}
//+------------------------------------------------------------------+
void TradeExecuteOrder(const bool isBuy)
{
if (isBuy && !Struct_AllowsBuy(1))
{
Print("[RsiMomEA] Trade skip BUY: 4 swing = LH-LL (ngược tín hiệu mua)");
return;
}
if (!isBuy && !Struct_AllowsSell(1))
{
Print("[RsiMomEA] Trade skip SELL: 4 swing = HH-HL (ngược tín hiệu bán)");
return;
}
if (InpOnePositionFlat && HasMyMagicPositionOrPending())
return;
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const int fvgWin = Fvg_TradeWindowStart();
double entryPx = 0.0;
bool useLimit = InpFvgFilterEnabled;
if (useLimit)
{
if (isBuy)
{
if (!Fvg_SelectBestBuyLimit(fvgWin, InpFvgScanBars, entryPx))
{
Print("[RsiMomEA] Trade skip BUY: không có FVG bull trong ", InpFvgScanBars,
" nến (từ shift ", fvgWin, ")");
return;
}
}
else
{
if (!Fvg_SelectBestSellLimit(fvgWin, InpFvgScanBars, entryPx))
{
Print("[RsiMomEA] Trade skip SELL: không có FVG bear trong ", InpFvgScanBars,
" nến (từ shift ", fvgWin, ")");
return;
}
}
}
else
{
MqlTick tk;
if (!SymbolInfoTick(_Symbol, tk))
return;
entryPx = isBuy ? tk.ask : tk.bid;
useLimit = false;
}
Pending_CancelMine();
double sl = 0.0, tp = 0.0;
if (!NearestSwingSlTp(isBuy, entryPx, dig, sl, tp))
{
Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ");
return;
}
if (!StopsValid(isBuy, entryPx, sl, tp))
{
Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE");
return;
}
double vol = VolumeForRiskPercent(isBuy, entryPx, sl);
vol = NormalizeLots(vol);
if (vol <= 0.0)
{
Print("[RsiMomEA] Trade skip: volume=0");
return;
}
bool ok = false;
if (useLimit)
{
ok = isBuy
? g_trade.BuyLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom BUY FVG")
: g_trade.SellLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom SELL FVG");
if (!ok)
Print("[RsiMomEA] Limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
else
{
g_pendingPlacedBarTime = iTime(_Symbol, _Period, 0);
Print("[RsiMomEA] Limit OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL",
" @ ", DoubleToString(entryPx, dig),
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig),
" expireBars=", InpFvgLimitExpireBars);
}
}
else
{
MqlTick tk;
if (!SymbolInfoTick(_Symbol, tk))
return;
ok = isBuy
? g_trade.Buy(vol, _Symbol, tk.ask, sl, tp, "RsiMom BUY")
: g_trade.Sell(vol, _Symbol, tk.bid, sl, tp, "RsiMom SELL");
if (!ok)
Print("[RsiMomEA] Market fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
else
Print("[RsiMomEA] Market OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL",
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig));
}
}
//+------------------------------------------------------------------+
void TradeTryOnBarOpen(const int calcRet)
{
@@ -572,62 +1109,181 @@ void TradeTryOnBarOpen(const int calcRet)
if (ArraySize(buf_Signal) < 2)
return;
const int waitBars = MathMax(0, InpFvgWaitBarsAfterSignal);
if (g_deferSignalBarTime != 0 && InpFvgFilterEnabled && waitBars > 0)
{
const int sh = iBarShift(_Symbol, _Period, g_deferSignalBarTime, true);
if (sh < 0)
{
g_deferSignalBarTime = 0;
return;
}
if (sh < 1 + waitBars)
return;
const bool isBuy = g_deferIsBuy;
g_deferSignalBarTime = 0;
Print("[RsiMomEA] Đã đợi ", waitBars, " nến sau tín hiệu — quét FVG và đặt lệnh");
TradeExecuteOrder(isBuy);
return;
}
const double s = buf_Signal[1];
if (s > -0.5 && s < 0.5)
return;
const bool isBuy = (s > 0.5);
if (InpOnePositionFlat && CountMyMagicPositions() > 0)
return;
MqlTick tk;
if (!SymbolInfoTick(_Symbol, tk))
return;
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double entry = isBuy ? tk.ask : tk.bid;
double sl = 0.0, tp = 0.0;
if (!NearestSwingSlTp(isBuy, entry, dig, sl, tp))
if (isBuy && !Struct_AllowsBuy(1))
{
Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ");
Print("[RsiMomEA] Trade skip BUY: 4 swing = LH-LL (ngược tín hiệu mua)");
return;
}
if (!StopsValid(isBuy, entry, sl, tp))
if (!isBuy && !Struct_AllowsSell(1))
{
Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE");
Print("[RsiMomEA] Trade skip SELL: 4 swing = HH-HL (ngược tín hiệu bán)");
return;
}
const double balance = AccountInfoDouble(ACCOUNT_BALANCE);
const double riskMoney = balance * (InpRiskPercent / 100.0);
double vol = VolumeForRiskPercent(isBuy, entry, sl);
vol = NormalizeLots(vol);
if (vol <= 0.0)
if (InpFvgFilterEnabled && waitBars > 0)
{
Print("[RsiMomEA] Trade skip: volume=0");
const datetime tSig = iTime(_Symbol, _Period, 1);
if (tSig == 0)
return;
if (tSig == g_deferSignalBarTime)
return;
g_deferSignalBarTime = tSig;
g_deferIsBuy = isBuy;
Print("[RsiMomEA] Tín hiệu ", isBuy ? "BUY" : "SELL",
" — đợi ", waitBars, " nến đóng rồi quét FVG (bar ", TimeToString(tSig, TIME_DATE | TIME_MINUTES), ")");
return;
}
const bool ok = isBuy
? g_trade.Buy(vol, _Symbol, tk.ask, sl, tp, "RsiMom BUY")
: g_trade.Sell(vol, _Symbol, tk.bid, sl, tp, "RsiMom SELL");
TradeExecuteOrder(isBuy);
}
if (!ok)
Print("[RsiMomEA] Order fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
else
Print("[RsiMomEA] Order OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL",
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig));
//+------------------------------------------------------------------+
int CountMyMagicPendingOrders()
{
int n = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
const ulong ticket = OrderGetTicket(i);
if (ticket == 0)
continue;
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
continue;
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
continue;
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if (t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT)
n++;
}
return n;
}
bool HasMyMagicPositionOrPending()
{
return (CountMyMagicPositions() > 0 || CountMyMagicPendingOrders() > 0);
}
bool Pending_CancelMine()
{
bool any = false;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
const ulong ticket = OrderGetTicket(i);
if (ticket == 0)
continue;
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
continue;
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
continue;
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if (t != ORDER_TYPE_BUY_LIMIT && t != ORDER_TYPE_SELL_LIMIT)
continue;
if (g_trade.OrderDelete(ticket))
any = true;
}
if (any)
g_pendingPlacedBarTime = 0;
return any;
}
void Pending_ManageExpiry()
{
if (InpFvgLimitExpireBars <= 0)
return;
if (g_pendingPlacedBarTime == 0)
return;
int pendingCount = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
const ulong ticket = OrderGetTicket(i);
if (ticket == 0)
continue;
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
continue;
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
continue;
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if (t != ORDER_TYPE_BUY_LIMIT && t != ORDER_TYPE_SELL_LIMIT)
continue;
pendingCount++;
}
if (pendingCount == 0)
{
g_pendingPlacedBarTime = 0;
return;
}
const int shift = iBarShift(_Symbol, _Period, g_pendingPlacedBarTime, true);
if (shift < 0)
return;
if (shift >= InpFvgLimitExpireBars)
{
Print("[RsiMomEA] Hủy Limit sau ", shift, " nến (max ", InpFvgLimitExpireBars, ")");
Pending_CancelMine();
}
}
//+------------------------------------------------------------------+
//| Khoảng cách đẩy SL ra xa pivot: spread (+) ATR×mult nếu bật |
//+------------------------------------------------------------------+
double Sl_GetBufferDistance(const int atrShift = 1)
{
double dist = 0.0;
if (InpSlAtrAddSpread)
{
const int spr = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
dist += spr * _Point;
}
if (InpSlAtrBufferEnabled && h_ATR != INVALID_HANDLE)
{
double atrBuf[];
ArraySetAsSeries(atrBuf, true);
if (CopyBuffer(h_ATR, 0, atrShift, 1, atrBuf) > 0 && atrBuf[0] > 0.0)
dist += atrBuf[0] * MathMax(0.0, InpSlAtrMultiplier);
}
return dist;
}
//+------------------------------------------------------------------+
bool NearestSwingSlTp(const bool isBuy, const double entry, const int dig, double &sl, double &tp)
{
const double rr = MathMax(0.01, InpRewardRiskRatio);
const int mx = MathMax(5, InpSwingMaxBars);
const int spr = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
const double buf = spr * _Point;
const double rr = MathMax(0.01, InpRewardRiskRatio);
const int mx = MathMax(5, InpSwingMaxBars);
const double buf = Sl_GetBufferDistance(1);
if (isBuy)
{
@@ -773,11 +1429,17 @@ void SetTradeFillingFromSymbol()
}
//+------------------------------------------------------------------+
int Stats_LineStepPx()
{
return MathMax(12, InpStatFontSize + MathMax(8, InpStatLinePad));
}
void Stats_CreateObjects()
{
const long ch = ActChart();
const int fs = MathMax(7, InpStatFontSize);
const int step = fs + STAT_LINE_PAD;
const int step = Stats_LineStepPx();
const int y0 = MathMax(10, InpStatBottomMargin);
for (int k = 0; k < 3; k++)
{
@@ -787,7 +1449,7 @@ void Stats_CreateObjects()
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, 8);
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, STAT_Y_ANCHOR + k * step);
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y0 + k * step);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, fs);
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_COLOR, InpStatColor);
@@ -816,19 +1478,15 @@ void Stats_UpdateDisplay()
}
const int fs = MathMax(7, InpStatFontSize);
const int step = fs + STAT_LINE_PAD;
const int step = Stats_LineStepPx();
const int y0 = MathMax(10, InpStatBottomMargin);
for (int k = 0; k < 3; k++)
{
const string nm = (k == 0) ? STAT_L1 : ((k == 1) ? STAT_L2 : STAT_L3);
ObjectSetInteger(ch, nm, OBJPROP_FONTSIZE, fs);
ObjectSetInteger(ch, nm, OBJPROP_YDISTANCE, STAT_Y_ANCHOR + k * step);
ObjectSetInteger(ch, nm, OBJPROP_YDISTANCE, y0 + k * step);
}
string line1 = StringFormat("Total: %I64d | SL %I64d | TP %I64d",
g_statExitDeals, g_statSL, g_statTP);
if (g_statOther > 0)
line1 += StringFormat(" | Other %I64d", g_statOther);
double winrate = 0.0;
if (g_statExitDeals > 0)
winrate = 100.0 * (double)g_statWins / (double)g_statExitDeals;
@@ -838,16 +1496,19 @@ void Stats_UpdateDisplay()
if (g_statExitDeals > 0)
avg = g_statSumProfit / (double)g_statExitDeals;
string line1 = StringFormat("Average Profit / trade: %s %s", DoubleToString(avg, 2), cur);
const string line2 = StringFormat("Winrate: %.1f%%", winrate);
const string line3 = StringFormat("Average Profit / trade: %s %s",
DoubleToString(avg, 2), cur);
string line3 = StringFormat("Total: %I64d | SL %I64d | TP %I64d",
g_statExitDeals, g_statSL, g_statTP);
if (g_statOther > 0)
line3 += StringFormat(" | Other %I64d", g_statOther);
ObjectSetString (ch, STAT_L1, OBJPROP_TEXT, line1);
ObjectSetInteger(ch, STAT_L1, OBJPROP_COLOR, InpStatColor);
ObjectSetString (ch, STAT_L2, OBJPROP_TEXT, line2);
ObjectSetInteger(ch, STAT_L2, OBJPROP_COLOR, InpStatColor);
ObjectSetString (ch, STAT_L3, OBJPROP_TEXT, line3);
ObjectSetInteger(ch, STAT_L3, OBJPROP_COLOR, InpStatColor);
ObjectSetString (ch, STAT_L2, OBJPROP_TEXT, line2);
ObjectSetInteger(ch, STAT_L2, OBJPROP_COLOR, InpStatColor);
ObjectSetString (ch, STAT_L1, OBJPROP_TEXT, line1);
ObjectSetInteger(ch, STAT_L1, OBJPROP_COLOR, InpStatColor);
ChartRedraw(ch);
}