900 lines
31 KiB
Plaintext
900 lines
31 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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 <Trade/Trade.mqh>
|
||
|
||
//--- 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();
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|