Files
Bell-PriceActionWithEma-EA/Experts/RsiMomentumEA.mq5
T
2026-05-25 02:01:26 +07:00

2483 lines
87 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| RsiMomentumEA.mq5 |
//| EA tự động — logic độc lập (không đọc RsiMomentumIndicator). |
//| RSI×WMA45 + 5phase | ATR↑ ADX trend EMA200 phiên | Limit 50% body |
//+------------------------------------------------------------------+
#property copyright "RsiMomentumEA"
#property version "4.31"
#include <Trade/Trade.mqh>
#include <RsiMom/TradeJournal.mqh>
#include <RsiMom/PhaseEntry.mqh>
#include <RsiMom/AdxFilter.mqh>
#include <RsiMom/SwingStructure.mqh>
#include <RsiMom/SignalDebug.mqh>
//--- 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
};
//--- 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;
input group "Bộ lọc tín hiệu (RSI + ATR + trend EMA200)"
input bool InpTrendFilterEnabled = true; // BUY: close > EMA200 | SELL: close < EMA200
input int InpTrendConfirmBars = 1; // BUY: N close > EMA200 | SELL: N close < EMA200
input bool InpAtrExpFilterEnabled = false; // tắt tạm — test riêng ADX
input int InpAtrExpPeriod = 14;
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)
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)
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
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
input double InpPhaseMinExpandSpread = 10.0; // P1: min max(WMA45-RSI) pt RSI [tối ưu ~618, step 1]
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)
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)"
input int InpSignalBarShift = 1; // nến tín hiệu (1 = nến vừa đóng)
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
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
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;
input group "Panel trạng thái (góc trên-trái)"
input bool InpShowPanel = false; // tắt = tester nhanh hơn
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;
input group "Cảnh báo / Notification (khi có entry mới)"
input bool InpAlertPush = false;
input bool InpAlertPopup = false;
input bool InpAlertSound = false;
input string InpSoundBuy = "alert.wav";
input string InpSoundSell = "alert2.wav";
input bool InpAlertEmail = false;
input bool InpAlertOnBar0 = false;
input group "Giao dịch tự động"
input bool InpTradeEnabled = true;
input ulong InpMagic = 202602;
input double InpRiskPercent = 1; // % balance mất nếu SL khớp (theo lot tính từ SL)
input double InpRewardRiskRatio = 1.05; // R:R — TP = tỷ lệ × khoảng SL (2.0 = 1:2)
input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-max
input int InpSlippagePoints = 30;
input bool InpOnePositionFlat = true;
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)
input bool InpTesterCalcOnNewBarOnly = true; // Tester: OnCalculate chỉ khi nến mới (tránh chậm dần)
input group "Quản lý lệnh mở @ 1R"
input bool InpManageAt1R = false; // @1R: chốt một phần + dời SL về entry
input double InpPartialCloseRatio = 0.5; // tỷ lệ volume chốt khi đạt 1R (0.5 = 50%)
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
input group "Xuất CSV thống kê (FILE_COMMON)"
input bool InpExportTradeJournal = false; // bật lại khi cần phân tích CSV (chậm hơn một chút)
input bool InpJournalResetOnInit = true; // Tester: xóa CSV cũ mỗi lần chạy backtest mới
input group "Thống kê (góc dưới-trái chart)"
input bool InpShowStats = true; // tắt = tester nhanh hơn
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;
//--- 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;
int h_ATR = INVALID_HANDLE;
int h_ATR_Regime = INVALID_HANDLE;
int h_ADX = INVALID_HANDLE;
const string OBJ_PREFIX = "RsiMomEA_";
const string DBG_PREFIX = OBJ_PREFIX + "DBG_";
#define PANEL_LINE_COUNT 25
#define PANEL_IDX_MARKET 19 // dòng 19+ = RSI / signal (sau block trạng thái)
const string PNL_PREFIX = OBJ_PREFIX + "pnl_";
const string EA_VERSION_STR = "4.31";
datetime g_dbgLogBarTime = 0; // chống spam Experts: 1 dòng / (nến, BUY|SELL)
int g_dbgLogSide = 0; // 1=BUY, -1=SELL
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;
datetime g_lastAlertBuyBar = 0;
datetime g_lastAlertSellBar = 0;
bool g_firstCalc = true;
static int g_prevCalculated = 0;
datetime g_tradeBarAnchor = 0;
datetime g_pendingPlacedBarTime = 0;
ulong g_pmTicket = 0;
double g_pmInitialRisk = 0.0;
bool g_pmAt1RDone = false;
CTrade g_trade;
long ActChart() { return ChartID(); }
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();
}
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 TradeExecuteLimitOrder(const bool isBuy);
void TradeExecuteMarketOrder(const bool isBuy);
bool EntryModeIsLimit();
bool EntryModeIsMarket();
string EntryModeLabel();
bool Signal_BodyMidPrice(const int shift, double &midOut);
bool LimitPriceValid(const bool isBuy, const double limitPx);
void Pending_ManageExpiry();
void Pending_EnvCancelIfBad();
bool Pending_CancelMine();
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();
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);
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);
void Stats_CreateObjects();
void Stats_UpdateDisplay();
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result);
//+------------------------------------------------------------------+
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)
{
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;
}
h_ADX = iADX(_Symbol, _Period, MathMax(2, InpAdxPeriod));
if (h_ADX == INVALID_HANDLE)
{
Print("[RsiMomEA] Không tạo được handle ADX");
return false;
}
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);
if (h_ATR != INVALID_HANDLE) IndicatorRelease(h_ATR);
if (h_ATR_Regime != INVALID_HANDLE) IndicatorRelease(h_ATR_Regime);
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;
}
//+------------------------------------------------------------------+
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)
{
const long ch = ActChart();
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;
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelLeftMargin);
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpPanelFontSize));
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(ch, name, OBJPROP_COLOR, clrSilver);
ObjectSetString (ch, name, OBJPROP_TEXT, "");
}
string Panel_FmtOnOff(const bool on)
{
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;
}
}
//+------------------------------------------------------------------+
void Panel_CreateAll()
{
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));
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) : ""),
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)",
Panel_FmtOnOff(InpPhaseFilterEnabled),
InpPhaseMinExpandSpread,
InpPhaseExpandLookback,
Panel_FmtOnOff(InpTrendFilterEnabled),
MathMax(1, InpTrendConfirmBars)),
InpPhaseFilterEnabled ? clrWhite : clrDimGray);
Panel_SetLine(ln++, StringFormat("ATR expand: %s | ADX: %s >=%.0f DI: %s",
Panel_FmtOnOff(InpAtrExpFilterEnabled),
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)",
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);
}
//+------------------------------------------------------------------+
void Panel_Update(const double &closeArr[], const double &ema200Arr[],
const int trendN, const int rates_total)
{
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;
bool panelTrendUp = true;
bool panelTrendDown = true;
for(int k = 0; k < trendN; k++)
{
const int idx = 1 + k;
if(ema200Arr[idx] <= 0.0)
{
panelTrendUp = false;
panelTrendDown = false;
break;
}
if(closeArr[idx] <= ema200Arr[idx])
panelTrendUp = false;
if(closeArr[idx] >= ema200Arr[idx])
panelTrendDown = false;
}
string trendTxt = "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;
}
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);
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);
}
Panel_SetLine(ln++, StringFormat("EMA200 close[1] %s %.5f",
panelTrendUp ? ">" : (panelTrendDown ? "<" : "~"),
ema200Arr[1]), trendClr);
while(ln < PANEL_LINE_COUNT)
Panel_SetLine(ln++, "", clrSilver);
}
//+------------------------------------------------------------------+
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;
}
}
//+------------------------------------------------------------------+
//| Regime: ATR mở rộng tại bar tín hiệu (ATR tăng vs N bar + liên tiếp) |
//+------------------------------------------------------------------+
bool AtrExp_GetAt(const int shift, double &atr)
{
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++)
{
double atrA = 0.0, atrB = 0.0;
if(!AtrExp_GetAt(shift + k, atrA) || !AtrExp_GetAt(shift + k + 1, atrB))
return false;
if(atrA <= atrB + 1e-8)
return false;
}
return true;
}
bool AtrExp_AllowsAt(const int shift)
{
return AtrExp_IsExpandingAt(shift);
}
bool AtrExp_AllowsNow()
{
return AtrExp_AllowsAt(1);
}
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
//| 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))
{
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;
if(closeArr[idx] <= ema200Arr[idx])
return false;
}
return true;
}
bool Trend_IsDownAt(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;
if(closeArr[idx] >= ema200Arr[idx])
return false;
}
return true;
}
//+------------------------------------------------------------------+
bool Signal_Ema9CoreBuyOkAt(const int shift)
{
return (buf_EMA9[shift] < buf_WMA45[shift]);
}
bool Signal_Ema9CoreSellOkAt(const int shift)
{
return (buf_EMA9[shift] > buf_WMA45[shift]);
}
PhaseEntryConfig GetPhaseEntryConfig()
{
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;
}
//+------------------------------------------------------------------+
bool Signal_RsiOkBuyAt(const int shift)
{
if(!InpRsiObOsFilterEnabled)
return true;
return (buf_RSI[shift] < InpRSIOverbought);
}
bool Signal_RsiOkSellAt(const int shift)
{
if(!InpRsiObOsFilterEnabled)
return true;
return (buf_RSI[shift] > InpRSIOversold);
}
//+------------------------------------------------------------------+
string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
const bool atrExpOk, const bool adxOk, const bool swingOk,
const bool rsiObOsOk, const bool envOk, const bool valid)
{
if(valid)
{
string s = InpPhaseFilterEnabled
? "RSI↑WMA45 | 5phase OK | EMA200 UP"
: "RSI↑WMA45 EMA9<WMA45 | EMA200 UP";
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
if(InpAtrExpFilterEnabled)
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
if(InpAdxFilterEnabled)
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
if(InpSwingStructFilterEnabled)
s += " | 2đáy↑";
return s;
}
string f = "";
if(!InpPhaseFilterEnabled && !ema9CoreOk)
f += "EMA9>=WMA45 ";
if(InpPhaseFilterEnabled && !phaseOk)
f += phaseFail;
if(InpTrendFilterEnabled && !trendUp)
f += "ngược EMA200 ";
if(InpAtrExpFilterEnabled && !atrExpOk)
f += "ATR co ";
if(InpAdxFilterEnabled && !adxOk)
f += "ADX yếu ";
if(InpSwingStructFilterEnabled && !swingOk)
f += "2 đáy ";
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
f += "RSI quá mua ";
if(!envOk)
f += "phiên/spread ";
if(StringLen(f) == 0)
f = "no cross ";
return f;
}
string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
const bool atrExpOk, const bool adxOk, const bool swingOk,
const bool rsiObOsOk, const bool envOk, const bool valid)
{
if(valid)
{
string s = InpPhaseFilterEnabled
? "RSI↓WMA45 | 5phase OK | EMA200 DOWN"
: "RSI↓WMA45 EMA9>WMA45 | EMA200 DOWN";
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
if(InpAtrExpFilterEnabled)
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
if(InpAdxFilterEnabled)
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
if(InpSwingStructFilterEnabled)
s += " | 2đỉnh↓";
return s;
}
string f = "";
if(!InpPhaseFilterEnabled && !ema9CoreOk)
f += "EMA9<=WMA45 ";
if(InpPhaseFilterEnabled && !phaseOk)
f += phaseFail;
if(InpTrendFilterEnabled && !trendDown)
f += "ngược EMA200 ";
if(InpAtrExpFilterEnabled && !atrExpOk)
f += "ATR co ";
if(InpAdxFilterEnabled && !adxOk)
f += "ADX yếu ";
if(InpSwingStructFilterEnabled && !swingOk)
f += "2 đỉnh ";
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
f += "RSI quá bán ";
if(!envOk)
f += "phiên/spread ";
if(StringLen(f) == 0)
f = "no cross ";
return f;
}
//+------------------------------------------------------------------+
void Signal_DebugApplyTradeLayer(SignalEvalResult &ev, const int shift)
{
const int sigShift = MathMax(1, InpSignalBarShift);
if(shift != sigShift || !ev.signalOk)
return;
if(!InpTradeEnabled)
{
ev.tradeOk = false;
ev.failTag = "Trade-OFF";
SignalEval_Append(ev.detail, "Đặt lệnh:OFF (InpTradeEnabled=false)");
ev.summary = "SIG → Trade tắt";
return;
}
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");
}
}
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
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)
{
if(DebugMarksEffective() && Signal_DebugMarkExists(ch, DBG_PREFIX, barTime, isBuy))
return;
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);
SignalEvalResult ev;
if(isBuy)
ev = Signal_EvaluateBuyAt(shift, rates_total, trendN,
buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr,
phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled,
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
adxVal, plusDi, minusDi, adxWhy,
InpSwingStructFilterEnabled, swingOkBar,
swingOld, swingNew, swingWhy,
InpRsiObOsFilterEnabled,
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,
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
adxVal, plusDi, minusDi, adxWhy,
InpSwingStructFilterEnabled, swingOkBar,
swingOld, swingNew, swingWhy,
InpRsiObOsFilterEnabled,
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);
if(InpDebugLogExperts && shift == sigShift
&& Signal_DebugShouldLogExperts(barTime, isBuy))
{
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");
}
}
//+------------------------------------------------------------------+
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;
const int dbgMax = MathMax(50, InpDebugMarkMaxBars);
const bool dbgMarks = DebugMarksEffective();
if(dbgMarks)
{
Signal_DebugConfigureChart(ch);
Signal_DebugPruneOlderThan(ch, DBG_PREFIX, MathMax(50, InpDebugMarkMaxBars) + 5);
}
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;
if (InpTrendFilterEnabled && i + trendN >= need) continue;
const int phaseLb = InpPhaseFilterEnabled
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3
: 0;
if (i + phaseLb >= rates_total) continue;
if (ema200Arr[i] <= 0.0) continue;
const bool trendUp = !InpTrendFilterEnabled || Trend_IsUpAt(i, trendN, closeArr, ema200Arr);
const bool trendDown = !InpTrendFilterEnabled || Trend_IsDownAt(i, trendN, closeArr, ema200Arr);
buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0);
// 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;
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);
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);
const bool rsiOkBuy = Signal_RsiOkBuyAt(i);
const bool rsiOkSell = Signal_RsiOkSellAt(i);
string envWhy = "";
const bool envOkBar = Env_AllowsSessionAt(timeArr[i], envWhy);
if(crossUpWma45)
{
const bool validBuy = coreBuyOk && trendUp && atrExpOkBar && adxOkBuy
&& swingOkBuy && rsiOkBuy && envOkBar;
if(dbgMarks && i <= dbgMax)
{
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);
}
if(validBuy)
{
const string reason = Signal_ReasonBuy(trendUp, ema9BuyOk, phaseBuyOk, phaseFailBuy,
atrExpOkBar, adxOkBuy, swingOkBuy,
rsiOkBuy, envOkBar, true);
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);
}
}
}
if(crossDownWma45)
{
const bool validSell = coreSellOk && trendDown && atrExpOkBar && adxOkSell
&& swingOkSell && rsiOkSell && envOkBar;
if(dbgMarks && i <= dbgMax)
{
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)
{
const string reason = Signal_ReasonSell(trendDown, ema9SellOk, phaseSellOk, phaseFailSell,
atrExpOkBar, adxOkSell, swingOkSell,
rsiOkSell, envOkBar, true);
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);
}
}
}
}
}
//+------------------------------------------------------------------+
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);
{
int crossUp = 0, crossDn = 0, passTrendUp = 0, passTrendDn = 0, passAtrUp = 0, passAtrDn = 0;
int passAdxUp = 0, passAdxDn = 0;
int passSwingUp = 0, passSwingDn = 0;
int passEnvUp = 0, passEnvDn = 0;
int passEma9Up = 0, passEma9Dn = 0;
int passPhaseUp = 0, passPhaseDn = 0;
int validBuy = 0, validSell = 0;
const int needSh = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars)
+ (InpAdxFilterEnabled ? MathMax(0, InpAdxRiseBars) : 0);
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++;
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++;
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++;
}
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",
n, crossUp, crossDn, passEma9Up, passEma9Dn, passPhaseUp, passPhaseDn,
passTrendUp, passTrendDn, passAtrUp, passAtrDn, passAdxUp, passAdxDn,
passSwingUp, passSwingDn, passEnvUp, passEnvDn, validBuy, validSell);
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");
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");
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");
}
}
//+------------------------------------------------------------------+
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)
{
const int atrNeed = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars) + 5;
const int adxNeed = InpAdxFilterEnabled
? MathMax(InpAdxPeriod, 5) + MathMax(0, InpAdxRiseBars) + 3
: 0;
const int phaseNeed = InpPhaseFilterEnabled
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 10
: 0;
const int minBars = MathMax(InpWMA45Period + InpRSIPeriod + 5 + phaseNeed,
MathMax(MathMax(InpAtrExpPeriod, InpSlAtrPeriod) + atrNeed, adxNeed));
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);
const int atrRegBars = BarsCalculated(h_ATR_Regime);
const int adxBars = InpAdxFilterEnabled ? BarsCalculated(h_ADX) : 1;
if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0 || atrRegBars <= 0
|| adxBars <= 0)
{
static datetime lastWarn = 0;
if (TimeCurrent() - lastWarn > 30)
{
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);
lastWarn = TimeCurrent();
}
return 0;
}
int srcMin = MathMin(MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars), atrRegBars);
if(InpAdxFilterEnabled)
srcMin = MathMin(srcMin, adxBars);
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);
const int trendN = InpTrendFilterEnabled ? MathMax(1, InpTrendConfirmBars) : 0;
const int need = MathMin(barsToScan + 2 + (InpTrendFilterEnabled ? trendN : 0), copyN);
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);
if(ChartRedrawEffective())
ChartRedraw(ActChart());
Diagnostics_FirstPass(rates_total, copyN, trendN, need, closeArr);
return copyN;
}
//+------------------------------------------------------------------+
int OnInit()
{
g_lastAlertBuyBar = 0;
g_lastAlertSellBar = 0;
g_dbgLogBarTime = 0;
g_dbgLogSide = 0;
g_firstCalc = true;
g_prevCalculated = 0;
g_tradeBarAnchor = iTime(_Symbol, _Period, 0);
g_pendingPlacedBarTime = 0;
Position_ResetPmState();
g_statExitDeals = 0;
g_statSL = 0;
g_statTP = 0;
g_statOther = 0;
g_statWins = 0;
g_statSumProfit = 0.0;
if(InpExportTradeJournal)
{
RsiMomJournal_ResetMonths();
if(InpJournalResetOnInit && MQLInfoInteger(MQL_TESTER))
RsiMomJournal_ResetFiles(_Symbol, _Period);
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
SetTradeFillingFromSymbol();
if (!Handles_CreateAll())
return INIT_FAILED;
const long chInit = ActChart();
ObjectsDeleteAll(chInit, OBJ_PREFIX + "RSN_");
ObjectsDeleteAll(chInit, DBG_PREFIX);
if(DebugMarksEffective())
Signal_DebugConfigureChart(chInit);
if(InpShowPanel)
Panel_CreateAll();
if(InpShowStats)
{
Stats_CreateObjects();
Stats_UpdateDisplay();
}
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)");
Print("[RsiMomEA] Init OK v", EA_VERSION_STR, " — entry=", EntryModeLabel(),
" | RSI×WMA45 + ", InpPhaseFilterEnabled ? "5phase" : "core",
" | 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",
" ADX=", InpAdxFilterEnabled
? StringFormat(">=%.0f DI=%s rise%d", InpAdxMinLevel,
InpAdxRequireDiDirection ? "on" : "off", InpAdxRiseBars) : "OFF",
" 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.");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(InpExportTradeJournal)
RsiMomJournal_OnDeinit(_Symbol, _Period);
Pending_CancelMine();
Handles_ReleaseAll();
ObjectsDeleteAll(ActChart(), OBJ_PREFIX);
ObjectsDeleteAll(ActChart(), STAT_PREFIX);
ChartRedraw(ActChart());
}
//+------------------------------------------------------------------+
void OnTick()
{
if(EntryModeIsLimit())
{
Pending_EnvCancelIfBad();
Pending_ManageExpiry();
}
Position_ManageAt1R();
const datetime t0 = iTime(_Symbol, _Period, 0);
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)
{
g_tradeBarAnchor = t0;
TradeTryOnBarOpen(calcRet);
}
if(InpShowPanel)
Panel_UpdateStatus();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(!DebugMarksEffective() || !InpDebugHoverHint)
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);
}
//+------------------------------------------------------------------+
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)
{
if(EntryModeIsMarket())
TradeExecuteMarketOrder(isBuy);
else
TradeExecuteLimitOrder(isBuy);
}
//+------------------------------------------------------------------+
void TradeExecuteLimitOrder(const bool isBuy)
{
if (InpOnePositionFlat && HasMyMagicPositionOrPending())
return;
string envWhy = "";
if(!Env_AllowsTradeNow(envWhy))
{
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy,
"(spread=", Env_CurrentSpreadPts(), " pts)");
return;
}
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const int sigSh = MathMax(1, InpSignalBarShift);
double bodyMid = 0.0;
if(!Signal_BodyMidPrice(sigSh, bodyMid))
{
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;
}
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
{
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);
}
}
//+------------------------------------------------------------------+
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));
}
//+------------------------------------------------------------------+
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;
TradeExecuteOrder(s > 0.5);
}
//+------------------------------------------------------------------+
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);
}
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()
{
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;
if (shift >= InpLimitExpireBars)
{
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;
}
//+------------------------------------------------------------------+
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);
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));
}
void Stats_CreateObjects()
{
const long ch = ActChart();
const int fs = MathMax(7, InpStatFontSize);
const int step = Stats_LineStepPx();
const int y0 = MathMax(10, InpStatBottomMargin);
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);
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, "");
if(ChartRedrawEffective())
ChartRedraw(ch);
return;
}
const int fs = MathMax(7, InpStatFontSize);
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, y0 + k * step);
}
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);
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);
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);
}
//+------------------------------------------------------------------+
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++;
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);
}
Stats_UpdateDisplay();
}
//+------------------------------------------------------------------+