Files
Bell-PriceActionWithEma-EA/Experts/RsiMomentumEA.mq5
T

2483 lines
87 KiB
Plaintext
Raw Normal View History

2026-05-14 00:54:40 +07:00
//+------------------------------------------------------------------+
//| RsiMomentumEA.mq5 |
//| EA tự động — logic độc lập (không đọc RsiMomentumIndicator). |
2026-05-25 02:01:26 +07:00
//| RSI×WMA45 + 5phase | ATR↑ ADX trend EMA200 phiên | Limit 50% body |
2026-05-14 00:54:40 +07:00
//+------------------------------------------------------------------+
#property copyright "RsiMomentumEA"
2026-05-25 02:01:26 +07:00
#property version "4.31"
2026-05-15 19:35:48 +07:00
#include <Trade/Trade.mqh>
2026-05-23 22:26:55 +07:00
#include <RsiMom/TradeJournal.mqh>
#include <RsiMom/PhaseEntry.mqh>
2026-05-25 02:01:26 +07:00
#include <RsiMom/AdxFilter.mqh>
#include <RsiMom/SwingStructure.mqh>
2026-05-23 22:26:55 +07:00
#include <RsiMom/SignalDebug.mqh>
2026-05-14 00:54:40 +07:00
2026-05-25 02:01:26 +07:00
//--- Cơ chế vào lệnh (switch test — logic Limit giữ nguyên trong TradeExecuteLimitOrder)
enum ENUM_RSI_MOM_ENTRY_MODE
{
RSI_MOM_ENTRY_LIMIT_BODY50 = 0, // Limit @ 50% thân nến tín hiệu (mặc định)
RSI_MOM_ENTRY_MARKET = 1 // Market ngay khi nến mới sau tín hiệu
};
2026-05-14 00:54:40 +07:00
//--- Input (khớp Indicators/RsiMomentumIndicator/Lib/Inputs.mqh)
input group "Chỉ báo"
input int InpRSIPeriod = 14;
input int InpEMA9Period = 9;
input int InpWMA45Period = 45;
input int InpEMATrendPeriod = 200;
2026-05-23 22:26:55 +07:00
input group "Bộ lọc tín hiệu (RSI + ATR + trend EMA200)"
2026-05-25 02:01:26 +07:00
input bool InpTrendFilterEnabled = true; // BUY: close > EMA200 | SELL: close < EMA200
2026-05-23 22:26:55 +07:00
input int InpTrendConfirmBars = 1; // BUY: N close > EMA200 | SELL: N close < EMA200
2026-05-25 02:01:26 +07:00
input bool InpAtrExpFilterEnabled = false; // tắt tạm — test riêng ADX
2026-05-23 22:26:55 +07:00
input int InpAtrExpPeriod = 14;
2026-05-24 01:11:43 +07:00
input int InpAtrExpCompareBars = 3; // so ATR[shift] vs ATR[shift+N]
input double InpAtrExpMinRatio = 1.005; // ≥1.005 = +0.5% (tối ưu 1.0031.02)
input int InpAtrExpRiseBars = 2; // ATR tăng liên tiếp N nến (1 = lỏng hơn)
2026-05-14 00:54:40 +07:00
2026-05-25 02:01:26 +07:00
input group "ADX — trend mạnh (pullback hiệu quả)"
input bool InpAdxFilterEnabled = true; // ADX + hướng +DI/-DI tại nến tín hiệu
input int InpAdxPeriod = 14;
input double InpAdxMinLevel = 22.0; // ADX >= ngưỡng (sideway ~<20, trend 2235+)
input double InpAdxMaxLevel = 0.0; // 0=tắt; ví dụ 45 tránh trend quá già
input bool InpAdxRequireDiDirection = true; // BUY +DI>-DI | SELL -DI>+DI
input double InpAdxMinDiSpread = 0.0; // |+DI(-DI)| tối thiểu (510 = chặt hơn)
input int InpAdxRiseBars = 0; // ADX tăng vs N nến trước (0=tắt, 12 bật)
input group "2 swing — đáy tăng / đỉnh giảm (thân nến)"
input bool InpSwingStructFilterEnabled = true;
input int InpSwingStructRange = 2; // pivot: N nến mỗi bên mỗi đáy/đỉnh
input int InpSwingStructLookback = 120; // quét tối đa N nến trước tín hiệu
input double InpSwingStructTolPts = 0.0; // cho phép 2 đáy/đỉnh bằng nhau (points)
2026-05-23 22:26:55 +07:00
input group "Lọc RSI quá mua / quá bán (nến tín hiệu)"
input bool InpRsiObOsFilterEnabled = true; // BUY khi RSI<70 | SELL khi RSI>30
input double InpRSIOverbought = 70.0; // RSI ≥ ngưỡng → bỏ BUY
input double InpRSIOversold = 30.0; // RSI ≤ ngưỡng → bỏ SELL
2026-05-14 00:54:40 +07:00
2026-05-23 22:26:55 +07:00
input group "Entry 5 phase (mở rộng → cuộn EMA9 → EMA9 hướng → WMA45 phẳng → cắt gần)"
input bool InpPhaseFilterEnabled = true;
input int InpPhaseExpandLookback = 25; // P1: quét mở rộng 3 đường
2026-05-24 01:11:43 +07:00
input double InpPhaseMinExpandSpread = 10.0; // P1: min max(WMA45-RSI) pt RSI [tối ưu ~618, step 1]
2026-05-23 22:26:55 +07:00
input int InpPhaseCoilLookback = 12; // P2: quét cuộn trước nến tín hiệu
input int InpPhaseMinRsiEma9Cross = 2; // P2: RSI cắt EMA9 ≥ N lần (chống xuyên 1 lần)
input double InpPhaseCoilBand = 6.0; // P2: |RSI-EMA9| ≤ band = quanh EMA9
input int InpPhaseEma9SlopeBars = 2; // P3: cửa sổ so sánh EMA9 (nhỏ hơn = lỏng hơn)
input double InpPhaseEma9SlopeTol = 1.5; // P3: cho phép EMA9 lệch ngược tối đa (pt RSI)
input int InpPhaseWmaFlatBars = 4; // P4: WMA45 phẳng — cửa sổ slope
input double InpPhaseWmaWasSlopeMin = 0.08; // P4: dốc tối thiểu quá khứ (khi RelaxPrior=false)
input double InpPhaseWmaFlatMaxSlope = 0.55; // P4: |slope| WMA45 gần 0 tại signal (lớn hơn = lỏng)
input bool InpPhaseWmaRelaxPrior = true; // P4: chỉ cần WMA45 từng đi đúng hướng, không cần dốc mạnh
input double InpPhaseMaxEma9WmaGap = 16.0; // P5: EMA9WMA45 tối đa khi cắt (lớn hơn = lỏng)
2026-05-14 00:54:40 +07:00
2026-05-25 02:01:26 +07:00
input group "Cơ chế vào lệnh (switch test)"
input ENUM_RSI_MOM_ENTRY_MODE InpEntryMode = RSI_MOM_ENTRY_MARKET;
input group "Entry — Limit 50% thân nến (chỉ InpEntryMode=Limit)"
2026-05-23 22:26:55 +07:00
input int InpSignalBarShift = 1; // nến tín hiệu (1 = nến vừa đóng)
2026-05-25 02:01:26 +07:00
input group "Quản lý Limit pending (chỉ InpEntryMode=Limit)"
input int InpLimitExpireBars = 40; // hủy Limit nếu không khớp sau N nến
2026-05-23 22:26:55 +07:00
2026-05-25 02:01:26 +07:00
input group "Debug — đánh dấu RSI×WMA45 (tắt = backtest nhanh)"
input bool InpDebugMarkSignals = false; // mọi cross: OK xanh | SIG vàng | SKIP đỏ
input int InpDebugMarkMaxBars = 400; // chỉ tạo mới trong N nến gần nhất
input bool InpDebugLogExperts = false; // 1 dòng Experts / nến tín hiệu
input bool InpDebugHoverHint = false; // rê chuột lên dấu X
2026-05-14 00:54:40 +07:00
input group "Mũi tên giao cắt"
input color InpArrowUpColor = clrLime;
input color InpArrowDownColor = clrTomato;
input int InpArrowOffsetPts = 30;
input int InpArrowSize = 1;
2026-05-23 22:26:55 +07:00
input group "Panel trạng thái (góc trên-trái)"
2026-05-25 02:01:26 +07:00
input bool InpShowPanel = false; // tắt = tester nhanh hơn
2026-05-23 22:26:55 +07:00
input int InpPanelFontSize = 8;
input int InpPanelLinePad = 14; // khoảng cách dọc giữa các dòng
input int InpPanelLeftMargin = 8;
input int InpPanelTopMargin = 10;
input color InpPanelColorEMA9 = clrGold;
input color InpPanelColorWMA45 = clrDodgerBlue;
2026-05-14 00:54:40 +07:00
input group "Cảnh báo / Notification (khi có entry mới)"
2026-05-25 02:01:26 +07:00
input bool InpAlertPush = false;
input bool InpAlertPopup = false;
input bool InpAlertSound = false;
2026-05-14 00:54:40 +07:00
input string InpSoundBuy = "alert.wav";
input string InpSoundSell = "alert2.wav";
input bool InpAlertEmail = false;
input bool InpAlertOnBar0 = false;
2026-05-15 19:35:48 +07:00
input group "Giao dịch tự động"
input bool InpTradeEnabled = true;
input ulong InpMagic = 202602;
2026-05-23 22:26:55 +07:00
input double InpRiskPercent = 1; // % balance mất nếu SL khớp (theo lot tính từ SL)
2026-05-25 02:01:26 +07:00
input double InpRewardRiskRatio = 1.05; // R:R — TP = tỷ lệ × khoảng SL (2.0 = 1:2)
2026-05-15 19:35:48 +07:00
input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-max
input int InpSlippagePoints = 30;
input bool InpOnePositionFlat = true;
2026-05-23 22:26:55 +07:00
input group "Phiên & spread (chỉ trade khi pass)"
input bool InpSessionFilterEnabled = true; // London/NY + chặn giao phiên
input bool InpEnvUseUtc = false; // false = giờ server/broker
input int InpLondonStartHour = 8;
input int InpLondonEndHour = 17;
input int InpNYStartHour = 13;
input int InpNYEndHour = 22;
input int InpSessionAvoidLastMin = 15; // bỏ N phút cuối mỗi phiên
input bool InpTransitionBlockEnabled = true; // chặn giao phiên / thanh khoản thấp
input int InpTransition1StartHour = 7; // ví dụ trước London
input int InpTransition1EndHour = 8;
input int InpTransition2StartHour = 21; // ví dụ đóng NY / rollover
input int InpTransition2EndHour = 22;
input bool InpSpreadFilterEnabled = true;
input int InpMaxSpreadPoints = 60; // SYMBOL_SPREAD (points) tối đa
input bool InpSpreadSkipInTester = true; // Tester: bỏ lọc spread (spread cố định thường quá cao)
2026-05-24 01:11:43 +07:00
input bool InpTesterCalcOnNewBarOnly = true; // Tester: OnCalculate chỉ khi nến mới (tránh chậm dần)
2026-05-23 22:26:55 +07:00
input group "Quản lý lệnh mở @ 1R"
2026-05-25 02:01:26 +07:00
input bool InpManageAt1R = false; // @1R: chốt một phần + dời SL về entry
2026-05-24 01:11:43 +07:00
input double InpPartialCloseRatio = 0.5; // tỷ lệ volume chốt khi đạt 1R (0.5 = 50%)
2026-05-23 22:26:55 +07:00
input int InpBreakevenOffsetPts = 0; // SL tại entry ± point (0 = đúng entry)
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
2026-05-23 22:26:55 +07:00
input group "Xuất CSV thống kê (FILE_COMMON)"
2026-05-25 02:01:26 +07:00
input bool InpExportTradeJournal = false; // bật lại khi cần phân tích CSV (chậm hơn một chút)
2026-05-23 22:26:55 +07:00
input bool InpJournalResetOnInit = true; // Tester: xóa CSV cũ mỗi lần chạy backtest mới
2026-05-15 19:35:48 +07:00
input group "Thống kê (góc dưới-trái chart)"
2026-05-25 02:01:26 +07:00
input bool InpShowStats = true; // tắt = tester nhanh hơn
2026-05-15 19:35:48 +07:00
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ê
2026-05-15 19:35:48 +07:00
input color InpStatColor = clrSilver;
2026-05-14 00:54:40 +07:00
//--- Buffers & state (trùng State.mqh)
double buf_RSI[];
double buf_EMA9[];
double buf_WMA45[];
double buf_Signal[];
double buf_EMA200[];
double buf_Trend[];
int h_RSI = INVALID_HANDLE;
int h_EMA9 = INVALID_HANDLE;
int h_WMA45 = INVALID_HANDLE;
int h_EMA200 = INVALID_HANDLE;
2026-05-23 22:26:55 +07:00
int h_ATR = INVALID_HANDLE;
int h_ATR_Regime = INVALID_HANDLE;
2026-05-25 02:01:26 +07:00
int h_ADX = INVALID_HANDLE;
2026-05-14 00:54:40 +07:00
const string OBJ_PREFIX = "RsiMomEA_";
2026-05-23 22:26:55 +07:00
const string DBG_PREFIX = OBJ_PREFIX + "DBG_";
2026-05-25 02:01:26 +07:00
#define PANEL_LINE_COUNT 25
#define PANEL_IDX_MARKET 19 // dòng 19+ = RSI / signal (sau block trạng thái)
2026-05-23 22:26:55 +07:00
const string PNL_PREFIX = OBJ_PREFIX + "pnl_";
2026-05-25 02:01:26 +07:00
const string EA_VERSION_STR = "4.31";
2026-05-23 23:55:55 +07:00
datetime g_dbgLogBarTime = 0; // chống spam Experts: 1 dòng / (nến, BUY|SELL)
int g_dbgLogSide = 0; // 1=BUY, -1=SELL
2026-05-14 00:54:40 +07:00
2026-05-15 19:35:48 +07:00
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";
long g_statExitDeals = 0;
long g_statSL = 0;
long g_statTP = 0;
long g_statOther = 0;
long g_statWins = 0;
double g_statSumProfit = 0.0;
2026-05-14 00:54:40 +07:00
datetime g_lastAlertBuyBar = 0;
datetime g_lastAlertSellBar = 0;
bool g_firstCalc = true;
static int g_prevCalculated = 0;
2026-05-15 19:35:48 +07:00
datetime g_tradeBarAnchor = 0;
datetime g_pendingPlacedBarTime = 0;
2026-05-23 22:26:55 +07:00
ulong g_pmTicket = 0;
double g_pmInitialRisk = 0.0;
bool g_pmAt1RDone = false;
2026-05-15 19:35:48 +07:00
CTrade g_trade;
2026-05-14 00:54:40 +07:00
long ActChart() { return ChartID(); }
2026-05-24 01:11:43 +07:00
bool IsStrategyTester()
{
return (bool)MQLInfoInteger(MQL_TESTER);
}
bool IsTesterVisualMode()
{
return IsStrategyTester() && (bool)MQLInfoInteger(MQL_VISUAL_MODE);
}
bool DebugMarksEffective()
{
if(!InpDebugMarkSignals)
return false;
// Tester không visual: không vẽ object (tránh hàng nghìn object làm chậm)
if(IsStrategyTester() && !IsTesterVisualMode())
return false;
return true;
}
bool ChartRedrawEffective()
{
return !IsStrategyTester() || IsTesterVisualMode();
}
2026-05-15 19:35:48 +07:00
void SetTradeFillingFromSymbol();
double Sl_GetBufferDistance(const int atrShift = 1);
2026-05-15 19:35:48 +07:00
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();
2026-05-15 19:35:48 +07:00
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);
2026-05-25 02:01:26 +07:00
void TradeExecuteLimitOrder(const bool isBuy);
void TradeExecuteMarketOrder(const bool isBuy);
bool EntryModeIsLimit();
bool EntryModeIsMarket();
string EntryModeLabel();
2026-05-23 22:26:55 +07:00
bool Signal_BodyMidPrice(const int shift, double &midOut);
bool LimitPriceValid(const bool isBuy, const double limitPx);
void Pending_ManageExpiry();
2026-05-23 22:26:55 +07:00
void Pending_EnvCancelIfBad();
bool Pending_CancelMine();
2026-05-23 22:26:55 +07:00
void Position_ResetPmState();
ulong Position_FindMyTicket();
void Position_ManageAt1R();
bool AtrExp_GetAt(const int shift, double &atr);
bool AtrExp_IsExpandingAt(const int shift);
bool AtrExp_AllowsAt(const int shift);
bool AtrExp_AllowsNow();
2026-05-25 02:01:26 +07:00
bool Adx_AllowsBuyAt(const int shift, string &why);
bool Adx_AllowsSellAt(const int shift, string &why);
void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi);
bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew);
bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew);
2026-05-23 22:26:55 +07:00
int Env_CurrentSpreadPts();
bool Env_SpreadAllows();
bool Env_AllowsSessionAt(const datetime t, string &why);
bool Env_AllowsTradeNow(string &why);
bool Env_AllowsTradeAtBar(const int sigShift, string &why);
bool Trend_IsUpAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[]);
bool Trend_IsDownAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[]);
bool Signal_Ema9CoreBuyOkAt(const int shift);
bool Signal_Ema9CoreSellOkAt(const int shift);
string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
const bool atrExpOk, const bool envOk, const bool valid);
string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
const bool atrExpOk, const bool envOk, const bool valid);
2026-05-15 19:35:48 +07:00
void Stats_CreateObjects();
void Stats_UpdateDisplay();
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result);
2026-05-14 00:54:40 +07:00
//+------------------------------------------------------------------+
bool Handles_CreateAll()
{
h_RSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
if (h_RSI == INVALID_HANDLE)
{
Print("[RsiMomEA] Không tạo được handle RSI");
return false;
}
h_EMA9 = iMA(_Symbol, _Period, InpEMA9Period, 0, MODE_EMA, h_RSI);
if (h_EMA9 == INVALID_HANDLE)
{
Print("[RsiMomEA] 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("[RsiMomEA] 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("[RsiMomEA] Không tạo được handle EMA200");
return false;
}
h_ATR = iATR(_Symbol, _Period, MathMax(1, InpSlAtrPeriod));
if (h_ATR == INVALID_HANDLE)
{
2026-05-23 22:26:55 +07:00
Print("[RsiMomEA] Không tạo được handle ATR (SL)");
return false;
}
h_ATR_Regime = iATR(_Symbol, _Period, MathMax(1, InpAtrExpPeriod));
if (h_ATR_Regime == INVALID_HANDLE)
{
Print("[RsiMomEA] Không tạo được handle ATR (regime)");
return false;
}
2026-05-25 02:01:26 +07:00
h_ADX = iADX(_Symbol, _Period, MathMax(2, InpAdxPeriod));
if (h_ADX == INVALID_HANDLE)
{
Print("[RsiMomEA] Không tạo được handle ADX");
return false;
}
2026-05-14 00:54:40 +07:00
return true;
}
//+------------------------------------------------------------------+
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);
2026-05-23 22:26:55 +07:00
if (h_ATR != INVALID_HANDLE) IndicatorRelease(h_ATR);
if (h_ATR_Regime != INVALID_HANDLE) IndicatorRelease(h_ATR_Regime);
2026-05-25 02:01:26 +07:00
if (h_ADX != INVALID_HANDLE) IndicatorRelease(h_ADX);
h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = h_ATR = h_ATR_Regime = h_ADX = INVALID_HANDLE;
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+
2026-05-23 22:26:55 +07:00
string Panel_LineName(const int idx)
{
return PNL_PREFIX + IntegerToString(idx);
}
int Panel_LineStepPx()
{
return MathMax(11, InpPanelFontSize + MathMax(6, InpPanelLinePad));
}
void Panel_SetLine(const int idx, const string text, const color clr)
2026-05-14 00:54:40 +07:00
{
const long ch = ActChart();
2026-05-23 22:26:55 +07:00
const string name = Panel_LineName(idx);
if(ObjectFind(ch, name) < 0)
return;
ObjectSetString(ch, name, OBJPROP_TEXT, text);
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
}
void Panel_CreateLine(const int idx)
{
const long ch = ActChart();
const string name = Panel_LineName(idx);
if(ObjectFind(ch, name) >= 0)
return;
const int step = Panel_LineStepPx();
const int y = InpPanelTopMargin + idx * step;
2026-05-14 00:54:40 +07:00
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
2026-05-23 22:26:55 +07:00
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelLeftMargin);
2026-05-14 00:54:40 +07:00
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y);
2026-05-23 22:26:55 +07:00
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpPanelFontSize));
2026-05-14 00:54:40 +07:00
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
2026-05-23 22:26:55 +07:00
ObjectSetInteger(ch, name, OBJPROP_COLOR, clrSilver);
ObjectSetString (ch, name, OBJPROP_TEXT, "");
2026-05-14 00:54:40 +07:00
}
2026-05-23 22:26:55 +07:00
string Panel_FmtOnOff(const bool on)
2026-05-14 00:54:40 +07:00
{
2026-05-23 22:26:55 +07:00
return on ? "ON " : "OFF";
}
color Panel_ClrOnOff(const bool on)
{
return on ? clrLime : clrDimGray;
}
void Panel_GetSessionLive(string &status, color &clr)
{
status = "";
clr = clrSilver;
if(!InpSessionFilterEnabled)
{
status = "Phiên: filter OFF (mọi giờ)";
clr = clrSilver;
return;
}
const int mins = Env_MinutesFromTime(TimeCurrent());
if(Env_IsTransitionAt(mins))
{
status = "Phiên: GIAO PHIÊN (chặn)";
clr = clrOrange;
return;
}
const int avoid = MathMax(0, InpSessionAvoidLastMin);
const bool inLondon = Env_IsWithinWindow(mins, InpLondonStartHour, InpLondonEndHour, avoid);
const bool inNY = Env_IsWithinWindow(mins, InpNYStartHour, InpNYEndHour, avoid);
if(inLondon && inNY)
{
status = "Phiên: London + NY";
clr = clrLime;
}
else if(inLondon)
{
status = "Phiên: London";
clr = clrLime;
}
else if(inNY)
{
status = "Phiên: New York";
clr = clrLime;
}
else
{
status = "Phiên: NGOÀI PHIÊN";
clr = clrTomato;
}
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+
void Panel_CreateAll()
{
2026-05-23 22:26:55 +07:00
if(!InpShowPanel)
return;
for(int i = 0; i < PANEL_LINE_COUNT; i++)
Panel_CreateLine(i);
}
//+------------------------------------------------------------------+
void Panel_UpdateStatus()
{
if(!InpShowPanel)
return;
int ln = 0;
const string tfTxt = StringSubstr(EnumToString((ENUM_TIMEFRAMES)_Period), 7);
Panel_SetLine(ln++, StringFormat("RSI MOMENTUM EA v%s | %s %s",
EA_VERSION_STR, _Symbol, tfTxt), clrWhite);
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
Panel_SetLine(ln++, StringFormat("Trade: %s Magic %I64u SignalBar[%d]",
Panel_FmtOnOff(InpTradeEnabled), InpMagic,
MathMax(1, InpSignalBarShift)),
Panel_ClrOnOff(InpTradeEnabled));
2026-05-25 02:01:26 +07:00
Panel_SetLine(ln++, StringFormat("Entry: %s", EntryModeLabel()),
InpEntryMode == RSI_MOM_ENTRY_MARKET ? clrGold : clrSilver);
Panel_SetLine(ln++, StringFormat("Risk %.2f%% | R:R 1:%.2f%s",
InpRiskPercent, InpRewardRiskRatio,
EntryModeIsLimit()
? StringFormat(" | Limit exp %d bar", InpLimitExpireBars) : ""),
2026-05-23 22:26:55 +07:00
clrSilver);
Panel_SetLine(ln++, StringFormat("Manage @1R: %s (partial %.0f%%)",
Panel_FmtOnOff(InpManageAt1R),
InpPartialCloseRatio * 100.0),
Panel_ClrOnOff(InpManageAt1R));
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
Panel_SetLine(ln++, StringFormat("5 phase: %s P1>=%.1f LB%d | EMA200: %s (%d bar)",
2026-05-23 22:26:55 +07:00
Panel_FmtOnOff(InpPhaseFilterEnabled),
InpPhaseMinExpandSpread,
InpPhaseExpandLookback,
2026-05-23 22:26:55 +07:00
Panel_FmtOnOff(InpTrendFilterEnabled),
MathMax(1, InpTrendConfirmBars)),
InpPhaseFilterEnabled ? clrWhite : clrDimGray);
2026-05-25 02:01:26 +07:00
Panel_SetLine(ln++, StringFormat("ATR expand: %s | ADX: %s >=%.0f DI: %s",
2026-05-23 22:26:55 +07:00
Panel_FmtOnOff(InpAtrExpFilterEnabled),
2026-05-25 02:01:26 +07:00
Panel_FmtOnOff(InpAdxFilterEnabled),
InpAdxMinLevel,
Panel_FmtOnOff(InpAdxRequireDiDirection)),
clrSilver);
Panel_SetLine(ln++, StringFormat("Swing2 body: %s range=%d LB=%d",
Panel_FmtOnOff(InpSwingStructFilterEnabled),
InpSwingStructRange, InpSwingStructLookback),
clrSilver);
Panel_SetLine(ln++, StringFormat("RSI OB/OS: %s (%.0f / %.0f)",
2026-05-23 22:26:55 +07:00
Panel_FmtOnOff(InpRsiObOsFilterEnabled),
InpRSIOverbought, InpRSIOversold),
clrSilver);
const bool spreadEffective = InpSpreadFilterEnabled &&
!(MQLInfoInteger(MQL_TESTER) && InpSpreadSkipInTester);
Panel_SetLine(ln++, StringFormat("Session: %s L%d-%d NY%d-%d | Spread: %s%s",
Panel_FmtOnOff(InpSessionFilterEnabled),
InpLondonStartHour, InpLondonEndHour,
InpNYStartHour, InpNYEndHour,
Panel_FmtOnOff(spreadEffective),
(InpSpreadFilterEnabled && InpSpreadSkipInTester && MQLInfoInteger(MQL_TESTER))
? " (skip tester)" : ""),
Panel_ClrOnOff(InpSessionFilterEnabled));
Panel_SetLine(ln++, StringFormat("Giao phiên: %s | Debug marks: %s | Journal: %s",
Panel_FmtOnOff(InpTransitionBlockEnabled && InpSessionFilterEnabled),
Panel_FmtOnOff(InpDebugMarkSignals),
Panel_FmtOnOff(InpExportTradeJournal)),
clrSilver);
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
string sessTxt = "";
color sessClr = clrSilver;
Panel_GetSessionLive(sessTxt, sessClr);
Panel_SetLine(ln++, sessTxt, sessClr);
string envWhy = "";
const bool tradeEnvOk = Env_AllowsTradeNow(envWhy);
Panel_SetLine(ln++, StringFormat("Trade env: %s%s",
tradeEnvOk ? "OK" : "BLOCK",
(StringLen(envWhy) > 0 ? " — " + envWhy : "")),
tradeEnvOk ? clrLime : clrOrangeRed);
const int spr = Env_CurrentSpreadPts();
const bool sprOk = Env_SpreadAllows();
Panel_SetLine(ln++, StringFormat("Spread: %d pts (max %d) %s",
spr, InpMaxSpreadPoints, sprOk ? "OK" : "HIGH"),
sprOk ? clrSilver : clrOrange);
Panel_SetLine(ln++, StringFormat("Giờ lọc: %s | %s",
InpEnvUseUtc ? "UTC" : "SERVER",
TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES)),
clrSilver);
while(ln < PANEL_IDX_MARKET)
Panel_SetLine(ln++, "", clrSilver);
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+
void Panel_Update(const double &closeArr[], const double &ema200Arr[],
const int trendN, const int rates_total)
{
2026-05-23 22:26:55 +07:00
Panel_UpdateStatus();
if(!InpShowPanel)
return;
if(rates_total <= trendN + 1)
return;
if(ema200Arr[1] <= 0.0)
return;
if(ArraySize(buf_RSI) < 2)
return;
int ln = PANEL_IDX_MARKET;
2026-05-14 00:54:40 +07:00
bool panelTrendUp = true;
bool panelTrendDown = true;
2026-05-23 22:26:55 +07:00
for(int k = 0; k < trendN; k++)
2026-05-14 00:54:40 +07:00
{
const int idx = 1 + k;
2026-05-23 22:26:55 +07:00
if(ema200Arr[idx] <= 0.0)
{
panelTrendUp = false;
panelTrendDown = false;
break;
}
if(closeArr[idx] <= ema200Arr[idx])
panelTrendUp = false;
if(closeArr[idx] >= ema200Arr[idx])
panelTrendDown = false;
2026-05-14 00:54:40 +07:00
}
2026-05-23 22:26:55 +07:00
string trendTxt = "RANGE";
color trendClr = clrSilver;
if(panelTrendUp)
{
trendTxt = StringFormat("UPTREND (close>%d>EMA200)", trendN);
trendClr = InpArrowUpColor;
}
else if(panelTrendDown)
{
trendTxt = StringFormat("DOWNTREND (close<%d<EMA200)", trendN);
trendClr = InpArrowDownColor;
}
2026-05-14 00:54:40 +07:00
2026-05-23 22:26:55 +07:00
string sigTxt = "none";
color sigClr = clrSilver;
if(buf_Signal[1] > 0.5)
{
sigTxt = "BUY";
sigClr = InpArrowUpColor;
}
else if(buf_Signal[1] < -0.5)
{
sigTxt = "SELL";
sigClr = InpArrowDownColor;
}
Panel_SetLine(ln++, StringFormat("Bar[1] Signal: %s | %s", sigTxt, trendTxt), sigClr);
Panel_SetLine(ln++, StringFormat("RSI %6.2f", buf_RSI[1]), clrMediumOrchid);
Panel_SetLine(ln++, StringFormat("EMA9 %6.2f", buf_EMA9[1]), InpPanelColorEMA9);
Panel_SetLine(ln++, StringFormat("WMA45 %6.2f", buf_WMA45[1]), InpPanelColorWMA45);
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled)
{
double adx = 0.0, pdi = 0.0, mdi = 0.0;
Adx_GetAtBar(1, adx, pdi, mdi);
string adxWhy = "";
const bool adxOk = panelTrendUp ? Adx_AllowsBuyAt(1, adxWhy)
: (panelTrendDown ? Adx_AllowsSellAt(1, adxWhy) : true);
Panel_SetLine(ln++, StringFormat("ADX %5.1f +DI=%.1f -DI=%.1f %s",
adx, pdi, mdi, adxOk ? "OK" : "FAIL"),
adxOk ? clrSilver : clrOrangeRed);
}
2026-05-23 22:26:55 +07:00
Panel_SetLine(ln++, StringFormat("EMA200 close[1] %s %.5f",
panelTrendUp ? ">" : (panelTrendDown ? "<" : "~"),
ema200Arr[1]), trendClr);
while(ln < PANEL_LINE_COUNT)
Panel_SetLine(ln++, "", clrSilver);
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+
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);
const string tfTxt = StringSubstr(tf, 7);
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const string pushMsg = StringFormat("[RsiMomEA] %s %s %s @ %s | RSI=%.1f EMA9=%.1f WMA45=%.1f",
dir, _Symbol, tfTxt, DoubleToString(price, dig), rsiVal, ema9Val, wma45Val);
const string fullMsg = StringFormat("RsiMomEA %s signal\n%s %s @ %s\nRSI=%.2f EMA9=%.2f WMA45=%.2f\nEMA200=%s\nBar: %s",
dir, _Symbol, tfTxt, DoubleToString(price, dig),
rsiVal, ema9Val, wma45Val, DoubleToString(ema200Val, dig),
TimeToString(barTime, TIME_DATE|TIME_MINUTES));
if (InpAlertPush)
{
if (!SendNotification(pushMsg))
PrintFormat("[RsiMomEA] SendNotification FAILED err=%d", GetLastError());
}
if (InpAlertPopup)
Alert(pushMsg);
if (InpAlertSound)
{
const string snd = isBuy ? InpSoundBuy : InpSoundSell;
if (StringLen(snd) > 0)
PlaySound(snd);
}
if (InpAlertEmail)
SendMail(StringFormat("RsiMomEA %s %s %s", dir, _Symbol, tfTxt), fullMsg);
Print("[RsiMomEA] >>> ", pushMsg);
}
//+------------------------------------------------------------------+
void Alerts_CheckAndFire(const datetime &timeArr[], const double &closeArr[],
const double &ema200Arr[], const int need, const int rates_total)
{
if (g_firstCalc)
{
if (need > 1)
{
g_lastAlertBuyBar = timeArr[1];
g_lastAlertSellBar = timeArr[1];
}
g_firstCalc = false;
return;
}
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;
}
}
//+------------------------------------------------------------------+
2026-05-23 22:26:55 +07:00
//| Regime: ATR mở rộng tại bar tín hiệu (ATR tăng vs N bar + liên tiếp) |
//+------------------------------------------------------------------+
2026-05-23 22:26:55 +07:00
bool AtrExp_GetAt(const int shift, double &atr)
{
2026-05-23 22:26:55 +07:00
atr = 0.0;
if(h_ATR_Regime == INVALID_HANDLE || shift < 0)
return false;
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(h_ATR_Regime, 0, shift, 1, buf) < 1)
return false;
atr = buf[0];
return (atr > 0.0);
}
bool AtrExp_IsExpandingAt(const int shift)
{
if(!InpAtrExpFilterEnabled)
return true;
const int cmpBars = MathMax(1, InpAtrExpCompareBars);
const int riseBars = MathMax(1, InpAtrExpRiseBars);
const int needShift = shift + cmpBars + riseBars;
if(needShift >= Bars(_Symbol, _Period))
return false;
double atrNow = 0.0, atrRef = 0.0;
if(!AtrExp_GetAt(shift, atrNow) || !AtrExp_GetAt(shift + cmpBars, atrRef))
return false;
if(atrRef <= 0.0)
return false;
const double minRatio = MathMax(1.0, InpAtrExpMinRatio);
if(atrNow / atrRef < minRatio - 1e-8)
return false;
for(int k = 0; k < riseBars; k++)
{
2026-05-23 22:26:55 +07:00
double atrA = 0.0, atrB = 0.0;
if(!AtrExp_GetAt(shift + k, atrA) || !AtrExp_GetAt(shift + k + 1, atrB))
return false;
2026-05-23 22:26:55 +07:00
if(atrA <= atrB + 1e-8)
return false;
}
return true;
}
2026-05-23 22:26:55 +07:00
bool AtrExp_AllowsAt(const int shift)
{
2026-05-23 22:26:55 +07:00
return AtrExp_IsExpandingAt(shift);
}
bool AtrExp_AllowsNow()
{
return AtrExp_AllowsAt(1);
}
2026-05-25 02:01:26 +07:00
//+------------------------------------------------------------------+
AdxFilterConfig GetAdxFilterConfig()
{
AdxFilterConfig c;
c.enabled = InpAdxFilterEnabled;
c.period = MathMax(2, InpAdxPeriod);
c.minLevel = MathMax(0.0, InpAdxMinLevel);
c.maxLevel = MathMax(0.0, InpAdxMaxLevel);
c.requireDiDir = InpAdxRequireDiDirection;
c.minDiSpread = MathMax(0.0, InpAdxMinDiSpread);
c.riseBars = MathMax(0, InpAdxRiseBars);
return c;
}
void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi)
{
adx = plusDi = minusDi = 0.0;
if(!InpAdxFilterEnabled || h_ADX == INVALID_HANDLE)
return;
AdxFilter_GetAt(h_ADX, shift, adx, plusDi, minusDi);
}
bool Adx_AllowsBuyAt(const int shift, string &why)
{
why = "";
if(!InpAdxFilterEnabled)
return true;
return AdxFilter_PassesBuy(h_ADX, shift, GetAdxFilterConfig(), why);
}
bool Adx_AllowsSellAt(const int shift, string &why)
{
why = "";
if(!InpAdxFilterEnabled)
return true;
return AdxFilter_PassesSell(h_ADX, shift, GetAdxFilterConfig(), why);
}
SwingStructConfig SwingStruct_BuildConfig()
{
SwingStructConfig c;
c.enabled = InpSwingStructFilterEnabled;
c.pivotRange = MathMax(1, InpSwingStructRange);
c.lookback = MathMax(20, InpSwingStructLookback);
c.tolPts = MathMax(0.0, InpSwingStructTolPts);
return c;
}
bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew)
{
why = "";
bodyOld = bodyNew = 0.0;
if(!InpSwingStructFilterEnabled)
return true;
SwingStructPoint older, newer;
const SwingStructConfig cfg = SwingStruct_BuildConfig();
const bool ok = SwingStruct_PassesBuyAt(_Symbol, _Period, shift, cfg, why, older, newer);
bodyOld = older.bodyPrice;
bodyNew = newer.bodyPrice;
return ok;
}
bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew)
{
why = "";
bodyOld = bodyNew = 0.0;
if(!InpSwingStructFilterEnabled)
return true;
SwingStructPoint older, newer;
const SwingStructConfig cfg = SwingStruct_BuildConfig();
const bool ok = SwingStruct_PassesSellAt(_Symbol, _Period, shift, cfg, why, older, newer);
bodyOld = older.bodyPrice;
bodyNew = newer.bodyPrice;
return ok;
}
2026-05-23 22:26:55 +07:00
//+------------------------------------------------------------------+
//| Phiên London/NY + chặn giao phiên + spread tối đa (trade env) |
//+------------------------------------------------------------------+
int Env_GmtOffsetSec()
{
return (int)(TimeGMT() - TimeCurrent());
}
datetime Env_ToFilterTime(const datetime t)
{
if(!InpEnvUseUtc)
return t;
return t + Env_GmtOffsetSec();
}
int Env_ClampHour(const int h)
{
return MathMax(0, MathMin(23, h));
}
int Env_MinutesFromTime(const datetime t)
{
MqlDateTime tm;
TimeToStruct(Env_ToFilterTime(t), tm);
return tm.hour * 60 + tm.min;
}
bool Env_IsWithinWindow(const int currentMinutes,
const int startHour, const int endHour,
const int avoidLastMin)
{
const int startMin = Env_ClampHour(startHour) * 60;
int endMin = Env_ClampHour(endHour) * 60;
if(endMin <= startMin)
endMin += 24 * 60;
int cur = currentMinutes;
if(endMin > 24 * 60 && cur < startMin)
cur += 24 * 60;
if(cur < startMin || cur >= endMin)
return false;
const int avoid = MathMax(0, avoidLastMin);
if(avoid > 0 && cur >= endMin - avoid)
return false;
return true;
}
bool Env_IsTransitionAt(const int mins)
{
if(!InpTransitionBlockEnabled)
return false;
return (Env_IsWithinWindow(mins, InpTransition1StartHour, InpTransition1EndHour, 0) ||
Env_IsWithinWindow(mins, InpTransition2StartHour, InpTransition2EndHour, 0));
}
bool Env_InActiveSessionAt(const int mins)
{
if(!InpSessionFilterEnabled)
return true;
const int avoid = MathMax(0, InpSessionAvoidLastMin);
return (Env_IsWithinWindow(mins, InpLondonStartHour, InpLondonEndHour, avoid) ||
Env_IsWithinWindow(mins, InpNYStartHour, InpNYEndHour, avoid));
}
int Env_CurrentSpreadPts()
{
return (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
}
bool Env_SpreadAllows()
{
if(!InpSpreadFilterEnabled)
return true;
if(MQLInfoInteger(MQL_TESTER) && InpSpreadSkipInTester)
return true;
const int maxSp = MathMax(1, InpMaxSpreadPoints);
return (Env_CurrentSpreadPts() <= maxSp);
}
bool Env_AllowsSessionAt(const datetime t, string &why)
{
why = "";
if(!InpSessionFilterEnabled)
return true;
const int mins = Env_MinutesFromTime(t);
if(Env_IsTransitionAt(mins))
{
2026-05-23 22:26:55 +07:00
why = "giao phiên ";
return false;
}
if(!Env_InActiveSessionAt(mins))
{
why = "ngoài phiên ";
return false;
}
return true;
}
bool Env_AllowsTradeNow(string &why)
{
return Env_AllowsTradeAtBar(MathMax(1, InpSignalBarShift), why);
}
bool Env_AllowsTradeAtBar(const int sigShift, string &why)
{
why = "";
const datetime barT = iTime(_Symbol, _Period, sigShift);
if(barT == 0)
{
why = "no bar time ";
return false;
}
if(!Env_AllowsSessionAt(barT, why))
return false;
if(!Env_SpreadAllows())
{
why = StringFormat("spread %d>%d ", Env_CurrentSpreadPts(), InpMaxSpreadPoints);
return false;
}
return true;
}
bool Trend_IsUpAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[])
{
const int n = MathMax(1, trendN);
for(int k = 0; k < n; k++)
{
const int idx = shift + k;
if(ema200Arr[idx] <= 0.0)
return false;
2026-05-23 22:26:55 +07:00
if(closeArr[idx] <= ema200Arr[idx])
return false;
}
return true;
}
2026-05-23 22:26:55 +07:00
bool Trend_IsDownAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[])
{
2026-05-23 22:26:55 +07:00
const int n = MathMax(1, trendN);
for(int k = 0; k < n; k++)
{
2026-05-23 22:26:55 +07:00
const int idx = shift + k;
if(ema200Arr[idx] <= 0.0)
return false;
if(closeArr[idx] >= ema200Arr[idx])
return false;
}
return true;
}
2026-05-23 22:26:55 +07:00
//+------------------------------------------------------------------+
bool Signal_Ema9CoreBuyOkAt(const int shift)
{
2026-05-23 22:26:55 +07:00
return (buf_EMA9[shift] < buf_WMA45[shift]);
}
2026-05-23 22:26:55 +07:00
bool Signal_Ema9CoreSellOkAt(const int shift)
{
2026-05-23 22:26:55 +07:00
return (buf_EMA9[shift] > buf_WMA45[shift]);
}
2026-05-23 22:26:55 +07:00
PhaseEntryConfig GetPhaseEntryConfig()
{
2026-05-23 22:26:55 +07:00
PhaseEntryConfig c;
c.enabled = InpPhaseFilterEnabled;
c.expandLookback = MathMax(5, InpPhaseExpandLookback);
c.minExpandSpread = MathMax(1.0, InpPhaseMinExpandSpread);
c.coilLookback = MathMax(3, InpPhaseCoilLookback);
c.minRsiEma9Crosses = MathMax(1, InpPhaseMinRsiEma9Cross);
c.coilBand = MathMax(0.5, InpPhaseCoilBand);
c.ema9SlopeBars = MathMax(1, InpPhaseEma9SlopeBars);
c.ema9SlopeTol = MathMax(0.0, InpPhaseEma9SlopeTol);
c.wmaFlatBars = MathMax(2, InpPhaseWmaFlatBars);
c.wmaWasSlopeMin = MathMax(0.0, InpPhaseWmaWasSlopeMin);
c.wmaFlatMaxSlope = MathMax(0.05, InpPhaseWmaFlatMaxSlope);
c.wmaRelaxPrior = InpPhaseWmaRelaxPrior;
c.maxEma9WmaGap = MathMax(0.5, InpPhaseMaxEma9WmaGap);
return c;
}
//+------------------------------------------------------------------+
2026-05-23 22:26:55 +07:00
bool Signal_RsiOkBuyAt(const int shift)
{
2026-05-23 22:26:55 +07:00
if(!InpRsiObOsFilterEnabled)
return true;
2026-05-23 22:26:55 +07:00
return (buf_RSI[shift] < InpRSIOverbought);
}
2026-05-23 22:26:55 +07:00
bool Signal_RsiOkSellAt(const int shift)
{
2026-05-23 22:26:55 +07:00
if(!InpRsiObOsFilterEnabled)
return true;
2026-05-23 22:26:55 +07:00
return (buf_RSI[shift] > InpRSIOversold);
}
//+------------------------------------------------------------------+
2026-05-23 22:26:55 +07:00
string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
2026-05-25 02:01:26 +07:00
const bool atrExpOk, const bool adxOk, const bool swingOk,
const bool rsiObOsOk, const bool envOk, const bool valid)
{
if(valid)
{
2026-05-23 22:26:55 +07:00
string s = InpPhaseFilterEnabled
? "RSI↑WMA45 | 5phase OK | EMA200 UP"
: "RSI↑WMA45 EMA9<WMA45 | EMA200 UP";
2026-05-25 02:01:26 +07:00
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
2026-05-23 22:26:55 +07:00
if(InpAtrExpFilterEnabled)
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled)
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
if(InpSwingStructFilterEnabled)
s += " | 2đáy↑";
return s;
}
string f = "";
2026-05-23 22:26:55 +07:00
if(!InpPhaseFilterEnabled && !ema9CoreOk)
f += "EMA9>=WMA45 ";
if(InpPhaseFilterEnabled && !phaseOk)
f += phaseFail;
if(InpTrendFilterEnabled && !trendUp)
f += "ngược EMA200 ";
if(InpAtrExpFilterEnabled && !atrExpOk)
f += "ATR co ";
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled && !adxOk)
f += "ADX yếu ";
if(InpSwingStructFilterEnabled && !swingOk)
f += "2 đáy ";
2026-05-23 22:26:55 +07:00
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
f += "RSI quá mua ";
if(!envOk)
f += "phiên/spread ";
if(StringLen(f) == 0)
f = "no cross ";
return f;
}
2026-05-23 22:26:55 +07:00
string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
2026-05-25 02:01:26 +07:00
const bool atrExpOk, const bool adxOk, const bool swingOk,
const bool rsiObOsOk, const bool envOk, const bool valid)
{
if(valid)
{
2026-05-23 22:26:55 +07:00
string s = InpPhaseFilterEnabled
? "RSI↓WMA45 | 5phase OK | EMA200 DOWN"
: "RSI↓WMA45 EMA9>WMA45 | EMA200 DOWN";
2026-05-25 02:01:26 +07:00
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
2026-05-23 22:26:55 +07:00
if(InpAtrExpFilterEnabled)
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled)
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
if(InpSwingStructFilterEnabled)
s += " | 2đỉnh↓";
return s;
}
string f = "";
2026-05-23 22:26:55 +07:00
if(!InpPhaseFilterEnabled && !ema9CoreOk)
f += "EMA9<=WMA45 ";
if(InpPhaseFilterEnabled && !phaseOk)
f += phaseFail;
if(InpTrendFilterEnabled && !trendDown)
f += "ngược EMA200 ";
if(InpAtrExpFilterEnabled && !atrExpOk)
f += "ATR co ";
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled && !adxOk)
f += "ADX yếu ";
if(InpSwingStructFilterEnabled && !swingOk)
f += "2 đỉnh ";
2026-05-23 22:26:55 +07:00
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
f += "RSI quá bán ";
if(!envOk)
f += "phiên/spread ";
if(StringLen(f) == 0)
f = "no cross ";
return f;
}
2026-05-23 22:26:55 +07:00
//+------------------------------------------------------------------+
void Signal_DebugApplyTradeLayer(SignalEvalResult &ev, const int shift)
{
2026-05-23 22:26:55 +07:00
const int sigShift = MathMax(1, InpSignalBarShift);
if(shift != sigShift || !ev.signalOk)
return;
2026-05-23 22:26:55 +07:00
if(!InpTradeEnabled)
{
2026-05-23 22:26:55 +07:00
ev.tradeOk = false;
ev.failTag = "Trade-OFF";
SignalEval_Append(ev.detail, "Đặt lệnh:OFF (InpTradeEnabled=false)");
ev.summary = "SIG → Trade tắt";
return;
}
2026-05-23 22:26:55 +07:00
string tradeWhy = "";
ev.tradeOk = Env_AllowsTradeAtBar(shift, tradeWhy);
if(ev.tradeOk)
{
ev.summary = "HỢP LỆ → vào lệnh";
SignalEval_Append(ev.detail, "Đặt lệnh:OK");
}
else
{
ev.failTag = "Đặt lệnh";
ev.summary = "SIG → " + tradeWhy;
SignalEval_Append(ev.detail, StringLen(tradeWhy) > 0
? "Đặt lệnh:FAIL " + tradeWhy
: "Đặt lệnh:FAIL");
}
}
2026-05-23 23:55:55 +07:00
//+------------------------------------------------------------------+
bool Signal_DebugShouldLogExperts(const datetime barTime, const bool isBuy)
{
const int side = isBuy ? 1 : -1;
if(barTime == g_dbgLogBarTime && side == g_dbgLogSide)
return false;
g_dbgLogBarTime = barTime;
g_dbgLogSide = side;
return true;
}
2026-05-23 22:26:55 +07:00
//+------------------------------------------------------------------+
void Signal_DebugMarkCross(const long ch, const int shift, const datetime barTime,
const double barHigh, const double barLow, const double markPrice,
const bool isBuy, const int rates_total, const int trendN,
const double &closeArr[], const double &ema200Arr[],
const PhaseEntryConfig &phaseCfg,
const bool atrExpOkBar, const bool sessionAtBar,
const string sessionFailWhy)
{
2026-05-24 01:11:43 +07:00
if(DebugMarksEffective() && Signal_DebugMarkExists(ch, DBG_PREFIX, barTime, isBuy))
return;
2026-05-25 02:01:26 +07:00
double adxVal = 0.0, plusDi = 0.0, minusDi = 0.0;
string adxWhy = "";
Adx_GetAtBar(shift, adxVal, plusDi, minusDi);
const bool adxOkBar = isBuy ? Adx_AllowsBuyAt(shift, adxWhy) : Adx_AllowsSellAt(shift, adxWhy);
string swingWhy = "";
double swingOld = 0.0, swingNew = 0.0;
const bool swingOkBar = isBuy
? SwingStruct_AllowsBuyAt(shift, swingWhy, swingOld, swingNew)
: SwingStruct_AllowsSellAt(shift, swingWhy, swingOld, swingNew);
2026-05-23 22:26:55 +07:00
SignalEvalResult ev;
if(isBuy)
ev = Signal_EvaluateBuyAt(shift, rates_total, trendN,
buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr,
phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled,
2026-05-25 02:01:26 +07:00
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
adxVal, plusDi, minusDi, adxWhy,
InpSwingStructFilterEnabled, swingOkBar,
swingOld, swingNew, swingWhy,
InpRsiObOsFilterEnabled,
2026-05-23 22:26:55 +07:00
InpRSIOverbought, InpRSIOversold,
InpSessionFilterEnabled,
atrExpOkBar, sessionAtBar, sessionFailWhy);
else
ev = Signal_EvaluateSellAt(shift, rates_total, trendN,
buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr,
phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled,
2026-05-25 02:01:26 +07:00
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
adxVal, plusDi, minusDi, adxWhy,
InpSwingStructFilterEnabled, swingOkBar,
swingOld, swingNew, swingWhy,
InpRsiObOsFilterEnabled,
2026-05-23 22:26:55 +07:00
InpRSIOverbought, InpRSIOversold,
InpSessionFilterEnabled,
atrExpOkBar, sessionAtBar, sessionFailWhy);
Signal_DebugApplyTradeLayer(ev, shift);
Signal_DebugDrawMark(ch, DBG_PREFIX, barTime, barHigh, barLow, markPrice, isBuy, ev);
const int sigShift = MathMax(1, InpSignalBarShift);
2026-05-23 23:55:55 +07:00
if(InpDebugLogExperts && shift == sigShift
&& Signal_DebugShouldLogExperts(barTime, isBuy))
2026-05-23 22:26:55 +07:00
{
PrintFormat("[RsiMomEA DBG] %s %s %s fail=[%s] | %s",
TimeToString(barTime, TIME_DATE | TIME_MINUTES),
isBuy ? "BUY" : "SELL",
ev.summary,
ev.failTag,
ev.detail);
if(ev.signalOk && MathAbs(buf_Signal[shift]) < 0.5)
Print("[RsiMomEA DBG] → buf_Signal=0 (mũi tên CR không vẽ) nhưng debug vẫn pass signal — kiểm tra ObjectFind trùng tên");
}
}
2026-05-14 00:54:40 +07:00
//+------------------------------------------------------------------+
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 long ch = ActChart();
const double arrowOffset = InpArrowOffsetPts * _Point;
2026-05-23 22:26:55 +07:00
const int dbgMax = MathMax(50, InpDebugMarkMaxBars);
2026-05-24 01:11:43 +07:00
const bool dbgMarks = DebugMarksEffective();
if(dbgMarks)
{
2026-05-23 22:26:55 +07:00
Signal_DebugConfigureChart(ch);
2026-05-24 01:11:43 +07:00
Signal_DebugPruneOlderThan(ch, DBG_PREFIX, MathMax(50, InpDebugMarkMaxBars) + 5);
}
2026-05-14 00:54:40 +07:00
buf_Signal[0] = 0.0;
buf_Trend[0] = 0.0;
buf_EMA200[0] = (need > 0) ? ema200Arr[0] : 0.0;
for (int i = barsToScan; i >= 1; i--)
{
buf_Signal[i] = 0.0;
buf_Trend[i] = 0.0;
buf_EMA200[i] = (i < need) ? ema200Arr[i] : 0.0;
if (i + 1 >= rates_total) continue;
2026-05-23 22:26:55 +07:00
if (InpTrendFilterEnabled && i + trendN >= need) continue;
const int phaseLb = InpPhaseFilterEnabled
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3
: 0;
if (i + phaseLb >= rates_total) continue;
2026-05-14 00:54:40 +07:00
if (ema200Arr[i] <= 0.0) continue;
2026-05-23 22:26:55 +07:00
const bool trendUp = !InpTrendFilterEnabled || Trend_IsUpAt(i, trendN, closeArr, ema200Arr);
const bool trendDown = !InpTrendFilterEnabled || Trend_IsDownAt(i, trendN, closeArr, ema200Arr);
2026-05-14 00:54:40 +07:00
buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0);
2026-05-23 22:26:55 +07:00
// BUY: RSI↑WMA45 + EMA9<WMA45 | SELL: RSI↓WMA45 + EMA9>WMA45
const bool crossUpWma45 = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]);
const bool crossDownWma45 = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]);
if (!crossUpWma45 && !crossDownWma45) continue;
2026-05-14 00:54:40 +07:00
2026-05-23 22:26:55 +07:00
const bool ema9BuyOk = Signal_Ema9CoreBuyOkAt(i);
const bool ema9SellOk = Signal_Ema9CoreSellOkAt(i);
const PhaseEntryConfig phaseCfg = GetPhaseEntryConfig();
string phaseFailBuy = "";
string phaseFailSell = "";
const bool phaseBuyOk = Phase_BuyPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, phaseFailBuy);
const bool phaseSellOk = Phase_SellPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, phaseFailSell);
const bool coreBuyOk = InpPhaseFilterEnabled ? phaseBuyOk : ema9BuyOk;
const bool coreSellOk = InpPhaseFilterEnabled ? phaseSellOk : ema9SellOk;
const bool atrExpOkBar = AtrExp_AllowsAt(i);
2026-05-25 02:01:26 +07:00
string adxWhyB = "", adxWhyS = "";
const bool adxOkBuy = Adx_AllowsBuyAt(i, adxWhyB);
const bool adxOkSell = Adx_AllowsSellAt(i, adxWhyS);
string swingWhyB = "", swingWhyS = "";
double swOld = 0.0, swNew = 0.0;
const bool swingOkBuy = SwingStruct_AllowsBuyAt(i, swingWhyB, swOld, swNew);
const bool swingOkSell = SwingStruct_AllowsSellAt(i, swingWhyS, swOld, swNew);
2026-05-23 22:26:55 +07:00
const bool rsiOkBuy = Signal_RsiOkBuyAt(i);
const bool rsiOkSell = Signal_RsiOkSellAt(i);
string envWhy = "";
const bool envOkBar = Env_AllowsSessionAt(timeArr[i], envWhy);
2026-05-14 00:54:40 +07:00
2026-05-23 22:26:55 +07:00
if(crossUpWma45)
2026-05-14 00:54:40 +07:00
{
2026-05-25 02:01:26 +07:00
const bool validBuy = coreBuyOk && trendUp && atrExpOkBar && adxOkBuy
&& swingOkBuy && rsiOkBuy && envOkBar;
2026-05-23 22:26:55 +07:00
2026-05-24 01:11:43 +07:00
if(dbgMarks && i <= dbgMax)
2026-05-14 00:54:40 +07:00
{
2026-05-23 22:26:55 +07:00
string envDbg = envWhy;
Signal_DebugMarkCross(ch, i, timeArr[i], highArr[i], lowArr[i],
lowArr[i] - arrowOffset, true,
rates_total, trendN, closeArr, ema200Arr, phaseCfg,
atrExpOkBar, envOkBar, envDbg);
2026-05-14 00:54:40 +07:00
}
if(validBuy)
{
2026-05-23 22:26:55 +07:00
const string reason = Signal_ReasonBuy(trendUp, ema9BuyOk, phaseBuyOk, phaseFailBuy,
2026-05-25 02:01:26 +07:00
atrExpOkBar, adxOkBuy, swingOkBuy,
rsiOkBuy, envOkBar, true);
2026-05-23 22:26:55 +07:00
const double arrowPrice = lowArr[i] - arrowOffset;
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);
}
}
2026-05-14 00:54:40 +07:00
}
2026-05-23 22:26:55 +07:00
if(crossDownWma45)
2026-05-14 00:54:40 +07:00
{
2026-05-25 02:01:26 +07:00
const bool validSell = coreSellOk && trendDown && atrExpOkBar && adxOkSell
&& swingOkSell && rsiOkSell && envOkBar;
2026-05-23 22:26:55 +07:00
2026-05-24 01:11:43 +07:00
if(dbgMarks && i <= dbgMax)
2026-05-23 22:26:55 +07:00
{
string envDbg = envWhy;
Signal_DebugMarkCross(ch, i, timeArr[i], highArr[i], lowArr[i],
highArr[i] + arrowOffset, false,
rates_total, trendN, closeArr, ema200Arr, phaseCfg,
atrExpOkBar, envOkBar, envDbg);
}
if(validSell)
{
2026-05-23 22:26:55 +07:00
const string reason = Signal_ReasonSell(trendDown, ema9SellOk, phaseSellOk, phaseFailSell,
2026-05-25 02:01:26 +07:00
atrExpOkBar, adxOkSell, swingOkSell,
rsiOkSell, envOkBar, true);
2026-05-23 22:26:55 +07:00
const double arrowPrice = highArr[i] + arrowOffset;
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);
}
}
2026-05-14 00:54:40 +07:00
}
}
}
//+------------------------------------------------------------------+
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("[RsiMomEA] First-pass OK rates_total=%d copyN=%d trendN=%d need=%d", rates_total, copyN, trendN, need);
PrintFormat("[RsiMomEA] 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("[RsiMomEA] last %d bars trend dist: UP=%d DOWN=%d RANGE=%d (zeroEma200=%d)",
n, upCount, downCount, rangeCount, zeroEma);
2026-05-23 22:26:55 +07:00
{
int crossUp = 0, crossDn = 0, passTrendUp = 0, passTrendDn = 0, passAtrUp = 0, passAtrDn = 0;
2026-05-25 02:01:26 +07:00
int passAdxUp = 0, passAdxDn = 0;
int passSwingUp = 0, passSwingDn = 0;
2026-05-23 22:26:55 +07:00
int passEnvUp = 0, passEnvDn = 0;
int passEma9Up = 0, passEma9Dn = 0;
int passPhaseUp = 0, passPhaseDn = 0;
int validBuy = 0, validSell = 0;
2026-05-25 02:01:26 +07:00
const int needSh = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars)
+ (InpAdxFilterEnabled ? MathMax(0, InpAdxRiseBars) : 0);
2026-05-23 22:26:55 +07:00
const int phaseLb = InpPhaseFilterEnabled
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3
: 0;
const PhaseEntryConfig phaseCfg = GetPhaseEntryConfig();
for(int i = 1; i <= n; i++)
{
if(i + 1 >= rates_total || i + needSh >= rates_total
|| (InpTrendFilterEnabled && i + trendN >= need)
|| i + phaseLb >= rates_total)
continue;
const bool up = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]);
const bool dn = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]);
if(up) crossUp++;
if(dn) crossDn++;
if(up && (!InpTrendFilterEnabled || Trend_IsUpAt(i, MathMax(1, InpTrendConfirmBars), closeArr, buf_EMA200)))
passTrendUp++;
if(dn && (!InpTrendFilterEnabled || Trend_IsDownAt(i, MathMax(1, InpTrendConfirmBars), closeArr, buf_EMA200)))
passTrendDn++;
if(up && AtrExp_AllowsAt(i)) passAtrUp++;
if(dn && AtrExp_AllowsAt(i)) passAtrDn++;
2026-05-25 02:01:26 +07:00
string adxW = "";
if(up && Adx_AllowsBuyAt(i, adxW)) passAdxUp++;
if(dn && Adx_AllowsSellAt(i, adxW)) passAdxDn++;
double swO = 0.0, swN = 0.0;
if(up && SwingStruct_AllowsBuyAt(i, adxW, swO, swN)) passSwingUp++;
if(dn && SwingStruct_AllowsSellAt(i, adxW, swO, swN)) passSwingDn++;
2026-05-23 22:26:55 +07:00
if(up && Signal_Ema9CoreBuyOkAt(i)) passEma9Up++;
if(dn && Signal_Ema9CoreSellOkAt(i)) passEma9Dn++;
string pfB = "", pfS = "";
if(up && Phase_BuyPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, pfB)) passPhaseUp++;
if(dn && Phase_SellPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, pfS)) passPhaseDn++;
const datetime tBar = iTime(_Symbol, _Period, i);
string w = "";
if(up && Env_AllowsSessionAt(tBar, w)) passEnvUp++;
if(dn && Env_AllowsSessionAt(tBar, w)) passEnvDn++;
if(buf_Signal[i] > 0.5) validBuy++;
if(buf_Signal[i] < -0.5) validSell++;
}
2026-05-25 02:01:26 +07:00
PrintFormat("[RsiMomEA] last %d bars: cross UP=%d DN=%d | EMA9 UP=%d DN=%d | 5phase UP=%d DN=%d | EMA200 UP=%d DOWN=%d | ATR↑ UP=%d DN=%d | ADX UP=%d DN=%d | Swing2 UP=%d DN=%d | phiên UP=%d DN=%d | signal BUY=%d SELL=%d",
2026-05-23 22:26:55 +07:00
n, crossUp, crossDn, passEma9Up, passEma9Dn, passPhaseUp, passPhaseDn,
2026-05-25 02:01:26 +07:00
passTrendUp, passTrendDn, passAtrUp, passAtrDn, passAdxUp, passAdxDn,
passSwingUp, passSwingDn, passEnvUp, passEnvDn, validBuy, validSell);
2026-05-23 22:26:55 +07:00
if(crossUp > 0 && passPhaseUp == 0 && InpPhaseFilterEnabled)
Print("[RsiMomEA] Gợi ý: cross UP bị 5phase — xem P1-P5 hoặc hạ InpPhaseMinExpandSpread / InpPhaseMinRsiEma9Cross");
else if(crossUp > 0 && passEma9Up == 0)
Print("[RsiMomEA] Gợi ý: cross UP nhưng EMA9>=WMA45 — không đủ điều kiện lõi BUY");
if(InpAtrExpFilterEnabled && crossUp > 0 && passAtrUp == 0)
Print("[RsiMomEA] Gợi ý: cross bị chặn ATR — hạ InpAtrExpMinRatio / InpAtrExpRiseBars hoặc tắt InpAtrExpFilterEnabled");
2026-05-25 02:01:26 +07:00
if(InpAdxFilterEnabled && crossUp > 0 && passAdxUp == 0)
Print("[RsiMomEA] Gợi ý: cross bị chặn ADX — hạ InpAdxMinLevel hoặc tắt InpAdxRequireDiDirection");
if(InpSwingStructFilterEnabled && crossUp > 0 && passSwingUp == 0)
Print("[RsiMomEA] Gợi ý: cross bị chặn 2 đáy — tăng lookback hoặc tắt InpSwingStructFilterEnabled");
2026-05-23 22:26:55 +07:00
if(InpSessionFilterEnabled && crossUp > 0 && passEnvUp == 0)
Print("[RsiMomEA] Gợi ý: cross bị chặn PHIÊN — chỉnh giờ London/NY (server) hoặc tắt InpSessionFilterEnabled");
}
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+
void EnsureBuffers(const int rates_total)
{
if (rates_total <= 0) return;
ArrayResize(buf_RSI, rates_total);
ArrayResize(buf_EMA9, rates_total);
ArrayResize(buf_WMA45,rates_total);
ArrayResize(buf_Signal,rates_total);
ArrayResize(buf_EMA200,rates_total);
ArrayResize(buf_Trend, rates_total);
ArraySetAsSeries(buf_RSI, true);
ArraySetAsSeries(buf_EMA9, true);
ArraySetAsSeries(buf_WMA45, true);
ArraySetAsSeries(buf_Signal, true);
ArraySetAsSeries(buf_EMA200, true);
ArraySetAsSeries(buf_Trend, true);
}
//+------------------------------------------------------------------+
int RsiMomentum_OnCalculate(const int rates_total, const int prev_calculated)
{
2026-05-23 22:26:55 +07:00
const int atrNeed = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars) + 5;
2026-05-25 02:01:26 +07:00
const int adxNeed = InpAdxFilterEnabled
? MathMax(InpAdxPeriod, 5) + MathMax(0, InpAdxRiseBars) + 3
: 0;
2026-05-23 22:26:55 +07:00
const int phaseNeed = InpPhaseFilterEnabled
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 10
: 0;
const int minBars = MathMax(InpWMA45Period + InpRSIPeriod + 5 + phaseNeed,
2026-05-25 02:01:26 +07:00
MathMax(MathMax(InpAtrExpPeriod, InpSlAtrPeriod) + atrNeed, adxNeed));
2026-05-14 00:54:40 +07:00
if (rates_total < minBars) return 0;
EnsureBuffers(rates_total);
const int rsiBars = BarsCalculated(h_RSI);
const int ema9Bars = BarsCalculated(h_EMA9);
const int wmaBars = BarsCalculated(h_WMA45);
const int ema200Bars = BarsCalculated(h_EMA200);
2026-05-23 22:26:55 +07:00
const int atrRegBars = BarsCalculated(h_ATR_Regime);
2026-05-25 02:01:26 +07:00
const int adxBars = InpAdxFilterEnabled ? BarsCalculated(h_ADX) : 1;
if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0 || atrRegBars <= 0
|| adxBars <= 0)
2026-05-14 00:54:40 +07:00
{
static datetime lastWarn = 0;
if (TimeCurrent() - lastWarn > 30)
{
2026-05-25 02:01:26 +07:00
PrintFormat("[RsiMomEA] Source not ready: RSI=%d EMA9=%d WMA45=%d EMA200=%d ATRreg=%d ADX=%d (rates=%d)",
rsiBars, ema9Bars, wmaBars, ema200Bars, atrRegBars, adxBars, rates_total);
2026-05-14 00:54:40 +07:00
lastWarn = TimeCurrent();
}
return 0;
}
2026-05-25 02:01:26 +07:00
int srcMin = MathMin(MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars), atrRegBars);
if(InpAdxFilterEnabled)
srcMin = MathMin(srcMin, adxBars);
2026-05-14 00:54:40 +07:00
const int copyN = MathMin(srcMin, rates_total);
if (copyN < minBars) return 0;
if (CopyBuffer(h_RSI, 0, 0, copyN, buf_RSI) <= 0) return 0;
if (CopyBuffer(h_EMA9, 0, 0, copyN, buf_EMA9) <= 0) return 0;
if (CopyBuffer(h_WMA45, 0, 0, copyN, buf_WMA45) <= 0) return 0;
int barsToScan = (prev_calculated == 0)
? rates_total - 2
: (rates_total - prev_calculated + 2);
barsToScan = MathMin(barsToScan, rates_total - 2);
2026-05-23 22:26:55 +07:00
const int trendN = InpTrendFilterEnabled ? MathMax(1, InpTrendConfirmBars) : 0;
const int need = MathMin(barsToScan + 2 + (InpTrendFilterEnabled ? trendN : 0), copyN);
2026-05-14 00:54:40 +07:00
datetime timeArr[];
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, 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;
SignalScan_Run(barsToScan, rates_total, need, trendN, timeArr, highArr, lowArr, closeArr, ema200Arr);
Alerts_CheckAndFire(timeArr, closeArr, ema200Arr, need, rates_total);
Panel_Update(closeArr, ema200Arr, trendN, rates_total);
2026-05-24 01:11:43 +07:00
if(ChartRedrawEffective())
ChartRedraw(ActChart());
2026-05-14 00:54:40 +07:00
Diagnostics_FirstPass(rates_total, copyN, trendN, need, closeArr);
return copyN;
}
//+------------------------------------------------------------------+
int OnInit()
{
g_lastAlertBuyBar = 0;
g_lastAlertSellBar = 0;
2026-05-23 23:55:55 +07:00
g_dbgLogBarTime = 0;
g_dbgLogSide = 0;
2026-05-14 00:54:40 +07:00
g_firstCalc = true;
g_prevCalculated = 0;
g_tradeBarAnchor = iTime(_Symbol, _Period, 0);
g_pendingPlacedBarTime = 0;
2026-05-23 22:26:55 +07:00
Position_ResetPmState();
2026-05-15 19:35:48 +07:00
g_statExitDeals = 0;
g_statSL = 0;
g_statTP = 0;
g_statOther = 0;
g_statWins = 0;
g_statSumProfit = 0.0;
2026-05-23 22:26:55 +07:00
if(InpExportTradeJournal)
{
RsiMomJournal_ResetMonths();
if(InpJournalResetOnInit && MQLInfoInteger(MQL_TESTER))
RsiMomJournal_ResetFiles(_Symbol, _Period);
}
2026-05-15 19:35:48 +07:00
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
SetTradeFillingFromSymbol();
2026-05-14 00:54:40 +07:00
if (!Handles_CreateAll())
return INIT_FAILED;
2026-05-23 22:26:55 +07:00
const long chInit = ActChart();
ObjectsDeleteAll(chInit, OBJ_PREFIX + "RSN_");
2026-05-25 02:01:26 +07:00
ObjectsDeleteAll(chInit, DBG_PREFIX);
if(DebugMarksEffective())
Signal_DebugConfigureChart(chInit);
2026-05-23 22:26:55 +07:00
2026-05-25 02:01:26 +07:00
if(InpShowPanel)
Panel_CreateAll();
if(InpShowStats)
{
Stats_CreateObjects();
Stats_UpdateDisplay();
}
2026-05-23 22:26:55 +07:00
const int spr = Env_CurrentSpreadPts();
string envWhy = "";
const bool envNow = Env_AllowsTradeAtBar(MathMax(1, InpSignalBarShift), envWhy);
if(InpExportTradeJournal)
Print("[RsiMomEA] Journal CSV: ", RsiMomJournal_TradesPath(_Symbol, _Period),
" | summary: ", RsiMomJournal_SummaryPath(_Symbol, _Period), " (FILE_COMMON)");
2026-05-25 02:01:26 +07:00
Print("[RsiMomEA] Init OK v", EA_VERSION_STR, " — entry=", EntryModeLabel(),
" | RSI×WMA45 + ", InpPhaseFilterEnabled ? "5phase" : "core",
2026-05-23 22:26:55 +07:00
" | EMA200=", InpTrendFilterEnabled ? "on" : "OFF",
" | session=", InpSessionFilterEnabled ? "on" : "OFF",
" | debugMarks=", InpDebugMarkSignals ? "on" : "off",
" | ATR+EMA200+phiên | trade=", InpTradeEnabled ? "on" : "off", " risk%=", InpRiskPercent,
" ATRexp=", InpAtrExpFilterEnabled
? StringFormat("ratio>=%.2f rise%d cmp%d", InpAtrExpMinRatio, InpAtrExpRiseBars, InpAtrExpCompareBars) : "OFF",
2026-05-25 02:01:26 +07:00
" ADX=", InpAdxFilterEnabled
? StringFormat(">=%.0f DI=%s rise%d", InpAdxMinLevel,
InpAdxRequireDiDirection ? "on" : "off", InpAdxRiseBars) : "OFF",
2026-05-23 22:26:55 +07:00
" session=", InpSessionFilterEnabled
? StringFormat("L%d-%d NY%d-%d avoid%d%s", InpLondonStartHour, InpLondonEndHour,
InpNYStartHour, InpNYEndHour, InpSessionAvoidLastMin,
InpEnvUseUtc ? " UTC" : " srv") : "off",
" spread=", InpSpreadFilterEnabled
? (InpSpreadSkipInTester && MQLInfoInteger(MQL_TESTER)
? "off-in-tester" : StringFormat("<=%d", InpMaxSpreadPoints)) : "off");
PrintFormat("[RsiMomEA] SYMBOL_SPREAD=%d pts | env@signalBar1=%s %s",
spr, envNow ? "OK" : "BLOCK", envWhy);
if(InpSpreadFilterEnabled && !InpSpreadSkipInTester && spr > InpMaxSpreadPoints)
PrintFormat("[RsiMomEA] CẢNH BÁO: spread tester %d > max %d → không vào lệnh. Tăng InpMaxSpreadPoints hoặc bật InpSpreadSkipInTester.",
spr, InpMaxSpreadPoints);
if(InpSessionFilterEnabled && !envNow &&
(StringFind(envWhy, "ngoài") >= 0 || StringFind(envWhy, "giao") >= 0))
Print("[RsiMomEA] Gợi ý phiên: chỉnh London/NY theo giờ SERVER (xem bar time trong tester), hoặc tắt InpSessionFilterEnabled để test.");
2026-05-14 00:54:40 +07:00
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
2026-05-23 22:26:55 +07:00
if(InpExportTradeJournal)
RsiMomJournal_OnDeinit(_Symbol, _Period);
Pending_CancelMine();
2026-05-14 00:54:40 +07:00
Handles_ReleaseAll();
ObjectsDeleteAll(ActChart(), OBJ_PREFIX);
2026-05-15 19:35:48 +07:00
ObjectsDeleteAll(ActChart(), STAT_PREFIX);
2026-05-14 00:54:40 +07:00
ChartRedraw(ActChart());
}
//+------------------------------------------------------------------+
void OnTick()
{
2026-05-25 02:01:26 +07:00
if(EntryModeIsLimit())
{
Pending_EnvCancelIfBad();
Pending_ManageExpiry();
}
2026-05-23 22:26:55 +07:00
Position_ManageAt1R();
2026-05-15 19:35:48 +07:00
const datetime t0 = iTime(_Symbol, _Period, 0);
2026-05-24 01:11:43 +07:00
const bool newBar = (t0 != 0 && t0 != g_tradeBarAnchor);
const bool runCalc = (g_prevCalculated == 0)
|| newBar
|| !IsStrategyTester()
|| !InpTesterCalcOnNewBarOnly;
int calcRet = g_prevCalculated;
if(runCalc)
{
const int rates_total = Bars(_Symbol, _Period);
calcRet = RsiMomentum_OnCalculate(rates_total, g_prevCalculated);
if(calcRet != 0)
g_prevCalculated = calcRet;
}
if(newBar)
2026-05-15 19:35:48 +07:00
{
g_tradeBarAnchor = t0;
2026-05-24 01:11:43 +07:00
TradeTryOnBarOpen(calcRet);
2026-05-15 19:35:48 +07:00
}
2026-05-23 22:26:55 +07:00
if(InpShowPanel)
Panel_UpdateStatus();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
2026-05-24 01:11:43 +07:00
if(!DebugMarksEffective() || !InpDebugHoverHint)
2026-05-23 22:26:55 +07:00
return;
const long ch = ActChart();
if(id == CHARTEVENT_MOUSE_MOVE)
{
Signal_DebugOnMouseMove(ch, DBG_PREFIX, (int)lparam, (int)dparam);
return;
}
if(id == CHARTEVENT_CHART_CHANGE || id == CHARTEVENT_CLICK)
{
Signal_DebugHideHoverHint(ch);
}
}
//+------------------------------------------------------------------+
void Position_ResetPmState()
{
g_pmTicket = 0;
g_pmInitialRisk = 0.0;
g_pmAt1RDone = false;
}
ulong Position_FindMyTicket()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
return ticket;
}
return 0;
}
double Position_VolumeStepDown(const double vol)
{
const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(step <= 0.0)
return vol;
double v = MathFloor(vol / step) * step;
if(v < vmin)
return 0.0;
return NormalizeDouble(v, 8);
}
void Position_ManageAt1R()
{
if(!InpManageAt1R)
return;
const ulong ticket = Position_FindMyTicket();
if(ticket == 0)
{
if(g_pmTicket != 0)
Position_ResetPmState();
return;
}
if(g_pmTicket != ticket)
{
g_pmTicket = ticket;
g_pmAt1RDone = false;
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double sl = PositionGetDouble(POSITION_SL);
g_pmInitialRisk = MathAbs(entry - sl);
if(g_pmInitialRisk < _Point * 2.0)
g_pmInitialRisk = 0.0;
}
if(g_pmAt1RDone || g_pmInitialRisk <= 0.0)
return;
if(!PositionSelectByTicket(ticket))
return;
MqlTick tk;
if(!SymbolInfoTick(_Symbol, tk))
return;
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const bool isBuy = (ptype == POSITION_TYPE_BUY);
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double cur = isBuy ? tk.bid : tk.ask;
const double profitDist = isBuy ? (cur - entry) : (entry - cur);
if(profitDist + _Point < g_pmInitialRisk)
return;
const double vol = PositionGetDouble(POSITION_VOLUME);
const double ratio = MathMax(0.01, MathMin(1.0, InpPartialCloseRatio));
double closeVol = Position_VolumeStepDown(vol * ratio);
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
const double remain = vol - closeVol;
if(closeVol >= vmin && remain >= vmin)
{
if(g_trade.PositionClosePartial(ticket, closeVol))
Print("[RsiMomEA] Chốt ", DoubleToString(closeVol, 2), " lot (",
DoubleToString(ratio * 100.0, 0), "%) @ 1R — ticket #", ticket);
else
Print("[RsiMomEA] Partial close fail ", g_trade.ResultRetcode(), " ",
g_trade.ResultComment());
}
const double off = MathMax(0, InpBreakevenOffsetPts) * _Point;
double beSl = isBuy ? (entry - off) : (entry + off);
beSl = NormalizeDouble(beSl, dig);
const double tp = PositionGetDouble(POSITION_TP);
const double curSl = PositionGetDouble(POSITION_SL);
const bool needBe = isBuy ? (curSl < beSl - _Point) : (curSl > beSl + _Point);
if(needBe && StopsValid(isBuy, cur, beSl, tp))
{
if(g_trade.PositionModify(ticket, beSl, tp))
Print("[RsiMomEA] SL → entry (BE) @ ", DoubleToString(beSl, dig),
" sau khi đạt 1R — ticket #", ticket);
else
Print("[RsiMomEA] BE modify fail ", g_trade.ResultRetcode(), " ",
g_trade.ResultComment());
}
g_pmAt1RDone = true;
}
//+------------------------------------------------------------------+
bool Signal_BodyMidPrice(const int shift, double &midOut)
{
const double o = iOpen(_Symbol, _Period, shift);
const double c = iClose(_Symbol, _Period, shift);
midOut = (o + c) * 0.5;
return (midOut > 0.0);
}
bool LimitPriceValid(const bool isBuy, const double limitPx)
{
MqlTick tk;
if(!SymbolInfoTick(_Symbol, tk))
return false;
const int stops = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
const int freeze = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
const double md = (stops > freeze ? stops : freeze) * _Point;
if(isBuy)
return (limitPx < tk.ask - md);
return (limitPx > tk.bid + md);
2026-05-15 19:35:48 +07:00
}
2026-05-25 02:01:26 +07:00
//+------------------------------------------------------------------+
bool EntryModeIsLimit()
{
return (InpEntryMode == RSI_MOM_ENTRY_LIMIT_BODY50);
}
bool EntryModeIsMarket()
{
return (InpEntryMode == RSI_MOM_ENTRY_MARKET);
}
string EntryModeLabel()
{
return EntryModeIsMarket() ? "Market" : "Limit 50% body";
}
//+------------------------------------------------------------------+
void TradeExecuteOrder(const bool isBuy)
2026-05-25 02:01:26 +07:00
{
if(EntryModeIsMarket())
TradeExecuteMarketOrder(isBuy);
else
TradeExecuteLimitOrder(isBuy);
}
//+------------------------------------------------------------------+
void TradeExecuteLimitOrder(const bool isBuy)
{
if (InpOnePositionFlat && HasMyMagicPositionOrPending())
return;
2026-05-23 22:26:55 +07:00
string envWhy = "";
if(!Env_AllowsTradeNow(envWhy))
{
2026-05-23 22:26:55 +07:00
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy,
"(spread=", Env_CurrentSpreadPts(), " pts)");
return;
}
2026-05-23 22:26:55 +07:00
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const int sigSh = MathMax(1, InpSignalBarShift);
double bodyMid = 0.0;
if(!Signal_BodyMidPrice(sigSh, bodyMid))
{
2026-05-23 22:26:55 +07:00
Print("[RsiMomEA] Trade skip: không lấy được 50% thân nến tín hiệu (shift ", sigSh, ")");
return;
}
const double entryPx = NormalizeDouble(bodyMid, dig);
if(!LimitPriceValid(isBuy, entryPx))
{
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL",
" Limit @ ", DoubleToString(entryPx, dig),
" không hợp lệ (BUY Limit < Ask, SELL Limit > Bid)");
return;
}
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;
}
2026-05-23 22:26:55 +07:00
const bool ok = isBuy
? g_trade.BuyLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom BUY body50")
: g_trade.SellLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom SELL body50");
if (!ok)
Print("[RsiMomEA] Limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
else
{
2026-05-23 22:26:55 +07:00
g_pendingPlacedBarTime = iTime(_Symbol, _Period, 0);
Print("[RsiMomEA] Limit OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL",
" @ ", DoubleToString(entryPx, dig), " (50% body bar ", sigSh, ")",
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig),
" expireBars=", InpLimitExpireBars);
}
}
2026-05-25 02:01:26 +07:00
//+------------------------------------------------------------------+
void TradeExecuteMarketOrder(const bool isBuy)
{
if(InpOnePositionFlat && CountMyMagicPositions() > 0)
return;
string envWhy = "";
if(!Env_AllowsTradeNow(envWhy))
{
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy,
"(spread=", Env_CurrentSpreadPts(), " pts)");
return;
}
MqlTick tk;
if(!SymbolInfoTick(_Symbol, tk))
{
Print("[RsiMomEA] Trade skip: không lấy được tick");
return;
}
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double entryPx = NormalizeDouble(isBuy ? tk.ask : tk.bid, dig);
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;
}
const bool ok = isBuy
? g_trade.Buy(vol, _Symbol, 0.0, sl, tp, "RsiMom BUY mkt")
: g_trade.Sell(vol, _Symbol, 0.0, sl, tp, "RsiMom SELL mkt");
if(!ok)
Print("[RsiMomEA] Market fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
else
Print("[RsiMomEA] Market OK #", g_trade.ResultDeal(), " ", isBuy ? "BUY" : "SELL",
" @~", DoubleToString(entryPx, dig),
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig));
}
2026-05-15 19:35:48 +07:00
//+------------------------------------------------------------------+
void TradeTryOnBarOpen(const int calcRet)
{
if (!InpTradeEnabled)
return;
if (!MQLInfoInteger(MQL_TESTER) && !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
return;
if (calcRet <= 0)
return;
if (ArraySize(buf_Signal) < 2)
return;
const double s = buf_Signal[1];
if (s > -0.5 && s < 0.5)
return;
2026-05-23 22:26:55 +07:00
TradeExecuteOrder(s > 0.5);
}
2026-05-15 19:35:48 +07:00
//+------------------------------------------------------------------+
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);
}
2026-05-23 22:26:55 +07:00
void Pending_EnvCancelIfBad()
{
if(!InpSessionFilterEnabled && !InpSpreadFilterEnabled && !InpTransitionBlockEnabled)
return;
if(CountMyMagicPendingOrders() == 0)
return;
string why = "";
if(Env_AllowsTradeNow(why))
return;
Print("[RsiMomEA] Hủy Limit — môi trường trade: ", why,
" spread=", Env_CurrentSpreadPts(), " pts");
Pending_CancelMine();
}
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()
{
2026-05-23 22:26:55 +07:00
if (InpLimitExpireBars <= 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;
2026-05-23 22:26:55 +07:00
if (shift >= InpLimitExpireBars)
{
2026-05-23 22:26:55 +07:00
Print("[RsiMomEA] Hủy Limit sau ", shift, " nến (max ", InpLimitExpireBars, ")");
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;
2026-05-15 19:35:48 +07:00
}
//+------------------------------------------------------------------+
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 double buf = Sl_GetBufferDistance(1);
2026-05-15 19:35:48 +07:00
if (isBuy)
{
double pivotLow = 0.0;
bool found = false;
for (int i = 2; i <= mx; i++)
{
const double L = iLow(_Symbol, _Period, i);
if (L < iLow(_Symbol, _Period, i - 1) && L < iLow(_Symbol, _Period, i + 1))
{
pivotLow = L;
found = true;
break;
}
}
if (!found)
{
pivotLow = iLow(_Symbol, _Period, 2);
for (int j = 3; j <= mx; j++)
pivotLow = MathMin(pivotLow, iLow(_Symbol, _Period, j));
}
sl = NormalizeDouble(pivotLow - buf, dig);
const double risk = entry - sl;
if (risk <= _Point * 2)
return false;
tp = NormalizeDouble(entry + risk * rr, dig);
}
else
{
double pivotHigh = 0.0;
bool found = false;
for (int i = 2; i <= mx; i++)
{
const double H = iHigh(_Symbol, _Period, i);
if (H > iHigh(_Symbol, _Period, i - 1) && H > iHigh(_Symbol, _Period, i + 1))
{
pivotHigh = H;
found = true;
break;
}
}
if (!found)
{
pivotHigh = iHigh(_Symbol, _Period, 2);
for (int j = 3; j <= mx; j++)
pivotHigh = MathMax(pivotHigh, iHigh(_Symbol, _Period, j));
}
sl = NormalizeDouble(pivotHigh + buf, dig);
const double risk = sl - entry;
if (risk <= _Point * 2)
return false;
tp = NormalizeDouble(entry - risk * rr, dig);
}
return true;
}
//+------------------------------------------------------------------+
bool StopsValid(const bool isBuy, const double price, const double sl, const double tp)
{
const int stops = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
const int freeze = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
const double md = (stops > freeze ? stops : freeze) * _Point;
if (md <= 0.0)
return true;
if (isBuy)
{
if (price - sl < md - _Point) return false;
if (tp - price < md - _Point) return false;
}
else
{
if (sl - price < md - _Point) return false;
if (price - tp < md - _Point) return false;
}
return true;
}
//+------------------------------------------------------------------+
int CountMyMagicPositions()
{
int n = 0;
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
if (!PositionGetTicket(i))
continue;
if (PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if ((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
n++;
}
return n;
}
//+------------------------------------------------------------------+
double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice)
{
if (MathAbs(entryRef - slPrice) < _Point)
return 0.0;
const double balance = AccountInfoDouble(ACCOUNT_BALANCE);
const double riskMoney = balance * (InpRiskPercent / 100.0);
double profit = 0.0;
if (!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,
_Symbol, 1.0, entryRef, slPrice, profit))
return 0.0;
const double lossPerLot = MathAbs(profit);
if (lossPerLot < DBL_EPSILON)
return 0.0;
return riskMoney / lossPerLot;
}
//+------------------------------------------------------------------+
double NormalizeLots(double v)
{
const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
const double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if (step <= 0.0)
return 0.0;
v = MathFloor(v / step) * step;
if (v < vmin - 1e-12)
return 0.0;
if (v > vmax)
v = vmax;
return NormalizeDouble(v, 8);
}
//+------------------------------------------------------------------+
void SetTradeFillingFromSymbol()
{
const long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if ((fm & SYMBOL_FILLING_IOC) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
else if ((fm & SYMBOL_FILLING_FOK) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
}
//+------------------------------------------------------------------+
int Stats_LineStepPx()
{
return MathMax(12, InpStatFontSize + MathMax(8, InpStatLinePad));
}
2026-05-15 19:35:48 +07:00
void Stats_CreateObjects()
{
const long ch = ActChart();
const int fs = MathMax(7, InpStatFontSize);
const int step = Stats_LineStepPx();
const int y0 = MathMax(10, InpStatBottomMargin);
2026-05-15 19:35:48 +07:00
for (int k = 0; k < 3; k++)
{
const string name = (k == 0) ? STAT_L1 : ((k == 1) ? STAT_L2 : STAT_L3);
if (ObjectFind(ch, name) >= 0)
continue;
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, y0 + k * step);
2026-05-15 19:35:48 +07:00
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, fs);
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_COLOR, InpStatColor);
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, false);
ObjectSetString (ch, name, OBJPROP_TEXT, "");
}
}
//+------------------------------------------------------------------+
void Stats_UpdateDisplay()
{
const long ch = ActChart();
if (ObjectFind(ch, STAT_L1) < 0)
return;
if (!InpShowStats)
{
ObjectSetString(ch, STAT_L1, OBJPROP_TEXT, "");
ObjectSetString(ch, STAT_L2, OBJPROP_TEXT, "");
ObjectSetString(ch, STAT_L3, OBJPROP_TEXT, "");
2026-05-25 02:01:26 +07:00
if(ChartRedrawEffective())
ChartRedraw(ch);
2026-05-15 19:35:48 +07:00
return;
}
const int fs = MathMax(7, InpStatFontSize);
const int step = Stats_LineStepPx();
const int y0 = MathMax(10, InpStatBottomMargin);
2026-05-15 19:35:48 +07:00
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, y0 + k * step);
2026-05-15 19:35:48 +07:00
}
double winrate = 0.0;
if (g_statExitDeals > 0)
winrate = 100.0 * (double)g_statWins / (double)g_statExitDeals;
const string cur = AccountInfoString(ACCOUNT_CURRENCY);
double avg = 0.0;
if (g_statExitDeals > 0)
avg = g_statSumProfit / (double)g_statExitDeals;
string line1 = StringFormat("Average Profit / trade: %s %s", DoubleToString(avg, 2), cur);
2026-05-15 19:35:48 +07:00
const string line2 = StringFormat("Winrate: %.1f%%", winrate);
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);
2026-05-15 19:35:48 +07:00
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);
2026-05-15 19:35:48 +07:00
ChartRedraw(ch);
}
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if (trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
const ulong dealTicket = trans.deal;
if (dealTicket == 0)
return;
if (!HistoryDealSelect(dealTicket))
return;
if (HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol)
return;
if ((ulong)HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != InpMagic)
return;
const long entry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if (entry != DEAL_ENTRY_OUT)
return;
const double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP)
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
const ENUM_DEAL_REASON reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
g_statExitDeals++;
g_statSumProfit += profit;
if (profit > 0.0)
g_statWins++;
if (reason == DEAL_REASON_SL)
g_statSL++;
else if (reason == DEAL_REASON_TP)
g_statTP++;
else
g_statOther++;
2026-05-23 22:26:55 +07:00
if(InpExportTradeJournal)
{
const ulong posId = (ulong)HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
ulong entryDeal = 0;
datetime entryTime = 0;
long posType = 0;
double entryPrice = 0.0, sl = 0.0, tp = 0.0, vol = 0.0;
int sessionOk = 0;
if(RsiMomJournal_FindEntryDeal(posId, entryDeal, entryTime, posType,
entryPrice, sl, tp, vol))
{
string envWhy = "";
sessionOk = Env_AllowsSessionAt(entryTime, envWhy) ? 1 : 0;
}
RsiMomJournal_RecordClosedDeal(dealTicket, _Symbol, _Period, InpMagic,
h_RSI, h_WMA45, h_EMA200, h_ATR_Regime,
InpAtrExpCompareBars, InpAtrExpMinRatio,
sessionOk);
}
2026-05-15 19:35:48 +07:00
Stats_UpdateDisplay();
2026-05-14 00:54:40 +07:00
}
//+------------------------------------------------------------------+