//+------------------------------------------------------------------+ //| RsiMomentumEA.mq5 | //| EA tự động — logic độc lập (không đọc RsiMomentumIndicator). | //| RSI + EMA9/WMA45 trên RSI + EMA200(close), signal, mũi tên, panel | //+------------------------------------------------------------------+ #property copyright "RsiMomentumEA" #property version "3.13" #include //--- 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 trend" input int InpTrendConfirmBars = 1; input group "Bộ lọc RSI" input double InpRSIOverbought = 70.0; input double InpRSIOversold = 30.0; input group "Bộ lọc EMA9 vs WMA45 (chống nhiễu)" input int InpEma9PersistBars = 3; 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 thông tin" input bool InpShowPanel = true; input color InpPanelColorEMA9 = clrGold; // chữ giá trị EMA9 (vàng) input color InpPanelColorWMA45 = clrDodgerBlue; // chữ giá trị WMA45 (xanh dương) input group "Cảnh báo / Notification (khi có entry mới)" input bool InpAlertPush = true; input bool InpAlertPopup = true; input bool InpAlertSound = true; 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.0; // % balance mất nếu SL khớp (theo lot tính từ SL) input double InpRewardRiskRatio = 1.1; // R:R — khoảng TP = tỷ lệ này × khoảng SL (ví dụ 1.5 = 1:1.5) input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-max input int InpSlippagePoints = 30; input bool InpOnePositionFlat = true; input group "Thống kê (góc dưới-trái chart)" input bool InpShowStats = true; input int InpStatFontSize = 9; 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; const string OBJ_PREFIX = "RsiMomEA_"; const string LBL_TITLE = OBJ_PREFIX + "title"; const string LBL_TREND = OBJ_PREFIX + "trend"; const string LBL_RSI_VAL = OBJ_PREFIX + "rsi"; const string LBL_EMA9VAL = OBJ_PREFIX + "ema9"; const string LBL_WMA45VAL = OBJ_PREFIX + "wma45"; const string STAT_PREFIX = "RsiMomEA_ST_"; const string STAT_L1 = STAT_PREFIX + "line1"; const string STAT_L2 = STAT_PREFIX + "line2"; const string STAT_L3 = STAT_PREFIX + "line3"; // Khoảng cách dọc giữa các dòng thống kê (pixel): bước = fontSize + STAT_LINE_PAD const int STAT_Y_ANCHOR = 18; const int STAT_LINE_PAD = 16; 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; CTrade g_trade; long ActChart() { return ChartID(); } void SetTradeFillingFromSymbol(); 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(); double NormalizeLots(double v); double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice); void TradeTryOnBarOpen(const int calcRet); 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; } 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); h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = INVALID_HANDLE; } //+------------------------------------------------------------------+ void CreateLabel(const string name, const string text, const color clr, const int x, const int y, const int fontSize = 9) { const long ch = ActChart(); if (ObjectFind(ch, name) >= 0) return; ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, fontSize); 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, clr); ObjectSetString (ch, name, OBJPROP_TEXT, text); } //+------------------------------------------------------------------+ void UpdateLabel(const string name, const string text, const color clr) { const long ch = ActChart(); if (ObjectFind(ch, name) < 0) return; ObjectSetString (ch, name, OBJPROP_TEXT, text); ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); } //+------------------------------------------------------------------+ void Panel_CreateAll() { if (!InpShowPanel) return; CreateLabel(LBL_TITLE, "─ RSI MOMENTUM (EA) ─", clrWhite, 10, 14, 10); CreateLabel(LBL_TREND, "Trend : ---", clrSilver, 10, 34, 9); CreateLabel(LBL_RSI_VAL, "RSI : ---", clrMediumOrchid, 10, 51, 9); CreateLabel(LBL_EMA9VAL, "EMA9 : ---", InpPanelColorEMA9, 10, 68, 9); CreateLabel(LBL_WMA45VAL, "WMA45 : ---", InpPanelColorWMA45, 10, 85, 9); } //+------------------------------------------------------------------+ void Panel_Update(const double &closeArr[], const double &ema200Arr[], const int trendN, const int rates_total) { if (!InpShowPanel) return; if (rates_total <= trendN + 1) return; if (ema200Arr[1] <= 0.0) return; 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; color trendClr; if (panelTrendUp) { trendTxt = StringFormat("Trend : UPTREND (%d closes > EMA200)", trendN); trendClr = InpArrowUpColor; } else if (panelTrendDown) { trendTxt = StringFormat("Trend : DOWNTREND (%d closes < EMA200)", trendN); trendClr = InpArrowDownColor; } else { trendTxt = "Trend : RANGE / SWITCHING"; trendClr = clrSilver; } UpdateLabel(LBL_TREND, trendTxt, trendClr); UpdateLabel(LBL_RSI_VAL, StringFormat("RSI : %6.2f", buf_RSI[1]), clrMediumOrchid); UpdateLabel(LBL_EMA9VAL, StringFormat("EMA9 : %6.2f", buf_EMA9[1]), InpPanelColorEMA9); UpdateLabel(LBL_WMA45VAL, StringFormat("WMA45 : %6.2f", buf_WMA45[1]), InpPanelColorWMA45); } //+------------------------------------------------------------------+ 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; } } //+------------------------------------------------------------------+ 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; 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 (i + trendN >= need) continue; if (ema200Arr[i] <= 0.0) continue; bool trendUp = true; bool trendDown = true; for (int k = 0; k < trendN; k++) { const int idx = i + k; if (ema200Arr[idx] <= 0.0) { trendUp = false; trendDown = false; break; } if (closeArr[idx] <= ema200Arr[idx]) trendUp = false; if (closeArr[idx] >= ema200Arr[idx]) trendDown = false; if (!trendUp && !trendDown) break; } buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0); const bool crossUp = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]); const bool crossDown = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]); if (!crossUp && !crossDown) continue; const bool ema9BelowWma = (buf_EMA9[i] < buf_WMA45[i]); const bool ema9AboveWma = (buf_EMA9[i] > buf_WMA45[i]); const int persistN = MathMax(1, InpEma9PersistBars); bool ema9PersistBelow = ema9BelowWma; bool ema9PersistAbove = ema9AboveWma; if (persistN > 1) { if (i + persistN - 1 >= rates_total) { ema9PersistBelow = false; ema9PersistAbove = false; } else { for (int k = 1; k < persistN; k++) { if (buf_EMA9[i+k] >= buf_WMA45[i+k]) ema9PersistBelow = false; if (buf_EMA9[i+k] <= buf_WMA45[i+k]) ema9PersistAbove = false; if (!ema9PersistBelow && !ema9PersistAbove) break; } } } const bool ema9SlopeUp = (buf_EMA9[i] > buf_EMA9[i+1]); const bool ema9SlopeDown = (buf_EMA9[i] < buf_EMA9[i+1]); const bool rsiOkBuy = (buf_RSI[i] < InpRSIOverbought); const bool rsiOkSell = (buf_RSI[i] > InpRSIOversold); const bool validBuy = crossUp && trendUp && ema9PersistBelow && ema9SlopeUp && rsiOkBuy; const bool validSell = crossDown && trendDown && ema9PersistAbove && ema9SlopeDown && rsiOkSell; if (!validBuy && !validSell) continue; const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]); if (ObjectFind(ch, arrowName) >= 0) continue; if (validBuy) { buf_Signal[i] = 1.0; const double price = lowArr[i] - arrowOffset; ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], price); ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 233); ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP); ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowUpColor); ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize); ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true); ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "BUY (EA)"); } else { buf_Signal[i] = -1.0; const double price = highArr[i] + arrowOffset; ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], price); ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 234); ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowDownColor); ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize); ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false); ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true); ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "SELL (EA)"); } } } //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ 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 minBars = InpWMA45Period + InpRSIPeriod + 5; 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); if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0) { static datetime lastWarn = 0; if (TimeCurrent() - lastWarn > 30) { PrintFormat("[RsiMomEA] Source not ready: RSI=%d EMA9=%d WMA45=%d EMA200=%d (rates=%d)", rsiBars, ema9Bars, wmaBars, ema200Bars, rates_total); lastWarn = TimeCurrent(); } return 0; } const int srcMin = MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars); 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 = MathMax(1, InpTrendConfirmBars); const int need = MathMin(barsToScan + 2 + trendN, 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); ChartRedraw(ActChart()); Diagnostics_FirstPass(rates_total, copyN, trendN, need, closeArr); return copyN; } //+------------------------------------------------------------------+ int OnInit() { g_lastAlertBuyBar = 0; g_lastAlertSellBar = 0; g_firstCalc = true; g_prevCalculated = 0; g_tradeBarAnchor = iTime(_Symbol, _Period, 0); g_statExitDeals = 0; g_statSL = 0; g_statTP = 0; g_statOther = 0; g_statWins = 0; g_statSumProfit = 0.0; g_trade.SetExpertMagicNumber(InpMagic); g_trade.SetDeviationInPoints(InpSlippagePoints); SetTradeFillingFromSymbol(); if (!Handles_CreateAll()) return INIT_FAILED; Panel_CreateAll(); Stats_CreateObjects(); Stats_UpdateDisplay(); Print("[RsiMomEA] Init OK — trade=", InpTradeEnabled ? "on" : "off", " risk%=", InpRiskPercent); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Handles_ReleaseAll(); ObjectsDeleteAll(ActChart(), OBJ_PREFIX); ObjectsDeleteAll(ActChart(), STAT_PREFIX); ChartRedraw(ActChart()); } //+------------------------------------------------------------------+ void OnTick() { const int rates_total = Bars(_Symbol, _Period); const int ret = RsiMomentum_OnCalculate(rates_total, g_prevCalculated); if (ret != 0) g_prevCalculated = ret; const datetime t0 = iTime(_Symbol, _Period, 0); if (t0 != 0 && t0 != g_tradeBarAnchor) { g_tradeBarAnchor = t0; TradeTryOnBarOpen(ret); } } //+------------------------------------------------------------------+ 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; const bool isBuy = (s > 0.5); if (InpOnePositionFlat && CountMyMagicPositions() > 0) return; MqlTick tk; if (!SymbolInfoTick(_Symbol, tk)) return; const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); const double entry = isBuy ? tk.ask : tk.bid; double sl = 0.0, tp = 0.0; if (!NearestSwingSlTp(isBuy, entry, dig, sl, tp)) { Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ"); return; } if (!StopsValid(isBuy, entry, sl, tp)) { Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE"); return; } const double balance = AccountInfoDouble(ACCOUNT_BALANCE); const double riskMoney = balance * (InpRiskPercent / 100.0); double vol = VolumeForRiskPercent(isBuy, entry, sl); vol = NormalizeLots(vol); if (vol <= 0.0) { Print("[RsiMomEA] Trade skip: volume=0"); return; } const bool ok = isBuy ? g_trade.Buy(vol, _Symbol, tk.ask, sl, tp, "RsiMom BUY") : g_trade.Sell(vol, _Symbol, tk.bid, sl, tp, "RsiMom SELL"); if (!ok) Print("[RsiMomEA] Order fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment()); else Print("[RsiMomEA] Order OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL", " vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig)); } //+------------------------------------------------------------------+ bool NearestSwingSlTp(const bool isBuy, const double entry, const int dig, double &sl, double &tp) { const double rr = MathMax(0.01, InpRewardRiskRatio); const int mx = MathMax(5, InpSwingMaxBars); const int spr = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); const double buf = spr * _Point; 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); } //+------------------------------------------------------------------+ void Stats_CreateObjects() { const long ch = ActChart(); const int fs = MathMax(7, InpStatFontSize); const int step = fs + STAT_LINE_PAD; 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, STAT_Y_ANCHOR + 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, ""); ChartRedraw(ch); return; } const int fs = MathMax(7, InpStatFontSize); const int step = fs + STAT_LINE_PAD; for (int k = 0; k < 3; k++) { const string nm = (k == 0) ? STAT_L1 : ((k == 1) ? STAT_L2 : STAT_L3); ObjectSetInteger(ch, nm, OBJPROP_FONTSIZE, fs); ObjectSetInteger(ch, nm, OBJPROP_YDISTANCE, STAT_Y_ANCHOR + k * step); } string line1 = StringFormat("Total: %I64d | SL %I64d | TP %I64d", g_statExitDeals, g_statSL, g_statTP); if (g_statOther > 0) line1 += StringFormat(" | Other %I64d", g_statOther); double winrate = 0.0; if (g_statExitDeals > 0) winrate = 100.0 * (double)g_statWins / (double)g_statExitDeals; const string cur = AccountInfoString(ACCOUNT_CURRENCY); double avg = 0.0; if (g_statExitDeals > 0) avg = g_statSumProfit / (double)g_statExitDeals; const string line2 = StringFormat("Winrate: %.1f%%", winrate); const string line3 = StringFormat("Average Profit / trade: %s %s", DoubleToString(avg, 2), cur); ObjectSetString (ch, STAT_L1, OBJPROP_TEXT, line1); ObjectSetInteger(ch, STAT_L1, OBJPROP_COLOR, InpStatColor); ObjectSetString (ch, STAT_L2, OBJPROP_TEXT, line2); ObjectSetInteger(ch, STAT_L2, OBJPROP_COLOR, InpStatColor); ObjectSetString (ch, STAT_L3, OBJPROP_TEXT, line3); ObjectSetInteger(ch, STAT_L3, OBJPROP_COLOR, InpStatColor); 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++; Stats_UpdateDisplay(); } //+------------------------------------------------------------------+