From cf8fb6c26616076e159e2fd1aefad395a0d4b92b Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Sun, 25 Jan 2026 12:30:39 +0700 Subject: [PATCH] feat: Implement Base EMA Reversal V2 MQL5 indicator with multi-stage signals and trade management. --- EMA Indi/Base_EMA_Reversal_V2.mq5 | 405 ++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 EMA Indi/Base_EMA_Reversal_V2.mq5 diff --git a/EMA Indi/Base_EMA_Reversal_V2.mq5 b/EMA Indi/Base_EMA_Reversal_V2.mq5 new file mode 100644 index 0000000..4cef587 --- /dev/null +++ b/EMA Indi/Base_EMA_Reversal_V2.mq5 @@ -0,0 +1,405 @@ +//+------------------------------------------------------------------+ +//| Base_EMA_Reversal_V2.mq5 | +//| Base-EMA Multi-Stage Signal Framework | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025" +#property link "" +#property version "3.07" // Fix Invisible Dots +#property indicator_chart_window +#property indicator_buffers 12 +#property indicator_plots 8 + +// --- Plot 1: Buy Signal --- +#property indicator_label1 "BUY_SIGNAL" +#property indicator_type1 DRAW_ARROW +#property indicator_color1 clrLightBlue +#property indicator_width1 2 + +// --- Plot 2: Sell Signal --- +#property indicator_label2 "SELL_SIGNAL" +#property indicator_type2 DRAW_ARROW +#property indicator_color2 clrRed +#property indicator_width2 2 + +// --- Plot 3: EMA 50 --- +#property indicator_label3 "EMA 50" +#property indicator_type3 DRAW_LINE +#property indicator_color3 clrRed +#property indicator_style3 STYLE_SOLID +#property indicator_width3 1 + +// --- Plot 4: EMA 100 --- +#property indicator_label4 "EMA 100" +#property indicator_type4 DRAW_LINE +#property indicator_color4 clrOrange +#property indicator_style4 STYLE_SOLID +#property indicator_width4 1 + +// --- Plot 5: EMA 200 --- +#property indicator_label5 "EMA 200" +#property indicator_type5 DRAW_LINE +#property indicator_color5 clrGreen +#property indicator_style5 STYLE_SOLID +#property indicator_width5 1 + +// --- Plot 6: EMA 600 --- +#property indicator_label6 "EMA 600" +#property indicator_type6 DRAW_LINE +#property indicator_color6 clrMagenta +#property indicator_style6 STYLE_SOLID +#property indicator_width6 1 + +// --- Plot 7: Buy SL --- +#property indicator_label7 "BUY_SL" +#property indicator_type7 DRAW_ARROW +#property indicator_color7 clrGold // Gold is more visible than Yellow +#property indicator_width7 3 // Max Thickness + +// --- Plot 8: Sell SL --- +#property indicator_label8 "SELL_SL" +#property indicator_type8 DRAW_ARROW +#property indicator_color8 clrGold // Gold is more visible than Yellow +#property indicator_width8 3 // Max Thickness + +// Input Parameters +input group "=== EMA Settings ===" +input int InpEMA50 = 50; +input int InpEMA100 = 100; +input int InpEMA200 = 200; +input int InpEMA600 = 600; + +input group "=== Setup Settings ===" +input int InpLookback = 30; // Lookback for Base Detection +input double InpBaseThreshold = 50; // Pullback threshold (points) +input int InpStateReset = 100; // Reset state if too long + +input group "=== Debug Settings ===" +input bool InpDebugLogs = false; + +// Indicator Buffers +double BufferBuySignal[]; +double BufferSellSignal[]; +// EMA Display Buffers +double BufferEMA50[]; +double BufferEMA100[]; +double BufferEMA200[]; +double BufferEMA600[]; +// SL Buffers +double BufferBuySL[]; +double BufferSellSL[]; + +double BufferBuyTP[]; +double BufferSellTP[]; +double BufferBuyState[]; +double BufferSellState[]; + + +// State Variables +int buy_state = 0; +int sell_state = 0; +double current_base_low = 0; +double current_base_high = 0; + +// Trade Tracking Struct +struct Trade { + bool active; + int type; // 1=Buy, -1=Sell + double entry; + double sl; + double tp; + datetime time; +}; + +Trade active_trade; +int win_count = 0; +int loss_count = 0; + +// Handles +int h_ema50, h_ema100, h_ema200, h_ema600; + +//+------------------------------------------------------------------+ +//| OnInit | +//+------------------------------------------------------------------+ +int OnInit() +{ + SetIndexBuffer(0, BufferBuySignal, INDICATOR_DATA); + SetIndexBuffer(1, BufferSellSignal, INDICATOR_DATA); + + SetIndexBuffer(2, BufferEMA50, INDICATOR_DATA); + SetIndexBuffer(3, BufferEMA100, INDICATOR_DATA); + SetIndexBuffer(4, BufferEMA200, INDICATOR_DATA); + SetIndexBuffer(5, BufferEMA600, INDICATOR_DATA); + + SetIndexBuffer(6, BufferBuySL, INDICATOR_DATA); + SetIndexBuffer(7, BufferSellSL, INDICATOR_DATA); + + SetIndexBuffer(8, BufferBuyTP, INDICATOR_CALCULATIONS); + SetIndexBuffer(9, BufferSellTP, INDICATOR_CALCULATIONS); + SetIndexBuffer(10, BufferBuyState, INDICATOR_CALCULATIONS); + SetIndexBuffer(11, BufferSellState, INDICATOR_CALCULATIONS); + + // Force Arrow Types + PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_ARROW); + PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW); + PlotIndexSetInteger(6, PLOT_DRAW_TYPE, DRAW_ARROW); + PlotIndexSetInteger(7, PLOT_DRAW_TYPE, DRAW_ARROW); + + // Arrow Codes + PlotIndexSetInteger(0, PLOT_ARROW, 233); // Arrow Up + PlotIndexSetInteger(1, PLOT_ARROW, 234); // Arrow Down + + // Use Code 108 (Big Circle) for maximum visibility + PlotIndexSetInteger(6, PLOT_ARROW, 108); + PlotIndexSetInteger(7, PLOT_ARROW, 108); + + // Empty Values + for(int i=0; i<8; i++) PlotIndexSetDouble(i, PLOT_EMPTY_VALUE, EMPTY_VALUE); + + h_ema50 = iMA(_Symbol, _Period, InpEMA50, 0, MODE_EMA, PRICE_CLOSE); + h_ema100 = iMA(_Symbol, _Period, InpEMA100, 0, MODE_EMA, PRICE_CLOSE); + h_ema200 = iMA(_Symbol, _Period, InpEMA200, 0, MODE_EMA, PRICE_CLOSE); + h_ema600 = iMA(_Symbol, _Period, InpEMA600, 0, MODE_EMA, PRICE_CLOSE); + + if(h_ema50 == INVALID_HANDLE || h_ema600 == INVALID_HANDLE) return(INIT_FAILED); + + active_trade.active = false; + active_trade.type = 0; + + // Setup Dashboard + ObjectCreate(0, "Stat_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0); + ObjectSetInteger(0, "Stat_BG", OBJPROP_XDISTANCE, 10); + ObjectSetInteger(0, "Stat_BG", OBJPROP_YDISTANCE, 20); + ObjectSetInteger(0, "Stat_BG", OBJPROP_XSIZE, 150); + ObjectSetInteger(0, "Stat_BG", OBJPROP_YSIZE, 80); + ObjectSetInteger(0, "Stat_BG", OBJPROP_BGCOLOR, clrBlack); + ObjectSetInteger(0, "Stat_BG", OBJPROP_BORDER_TYPE, BORDER_FLAT); + + return(INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| OnCalculate | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const datetime &time[], + const double &open[], + const double &high[], + const double &low[], + const double &close[], + const long &tick_volume[], + const long &volume[], + const int &spread[]) +{ + ArraySetAsSeries(time, true); + ArraySetAsSeries(open, true); + ArraySetAsSeries(high, true); + ArraySetAsSeries(low, true); + ArraySetAsSeries(close, true); + + ArraySetAsSeries(BufferBuySignal, true); + ArraySetAsSeries(BufferSellSignal, true); + ArraySetAsSeries(BufferEMA50, true); + ArraySetAsSeries(BufferEMA100, true); + ArraySetAsSeries(BufferEMA200, true); + ArraySetAsSeries(BufferEMA600, true); + ArraySetAsSeries(BufferBuySL, true); + ArraySetAsSeries(BufferSellSL, true); + ArraySetAsSeries(BufferBuyTP, true); + ArraySetAsSeries(BufferSellTP, true); + ArraySetAsSeries(BufferBuyState, true); + ArraySetAsSeries(BufferSellState, true); + + if(rates_total < InpEMA600 + InpLookback) return(0); + + double ema50[], ema100[], ema200[], ema600[]; + ArraySetAsSeries(ema50, true); + ArraySetAsSeries(ema100, true); + ArraySetAsSeries(ema200, true); + ArraySetAsSeries(ema600, true); + + CopyBuffer(h_ema50, 0, 0, rates_total, ema50); + CopyBuffer(h_ema100, 0, 0, rates_total, ema100); + CopyBuffer(h_ema200, 0, 0, rates_total, ema200); + CopyBuffer(h_ema600, 0, 0, rates_total, ema600); + + int limit; + if(prev_calculated == 0) { + limit = rates_total - InpEMA600 - 1; + buy_state = 0; + sell_state = 0; + win_count = 0; + loss_count = 0; + active_trade.active = false; + + ArrayInitialize(BufferBuySignal, EMPTY_VALUE); + ArrayInitialize(BufferSellSignal, EMPTY_VALUE); + ArrayInitialize(BufferBuySL, EMPTY_VALUE); + ArrayInitialize(BufferSellSL, EMPTY_VALUE); + } else { + limit = rates_total - prev_calculated; + } + + for(int i = limit; i >= 0; i--) { + BufferBuySignal[i] = EMPTY_VALUE; + BufferSellSignal[i] = EMPTY_VALUE; + BufferBuySL[i] = EMPTY_VALUE; + BufferSellSL[i] = EMPTY_VALUE; + + BufferEMA50[i] = ema50[i]; + BufferEMA100[i] = ema100[i]; + BufferEMA200[i] = ema200[i]; + BufferEMA600[i] = ema600[i]; + + BufferBuyTP[i] = 0.0; + BufferSellTP[i] = 0.0; + + // --- ACTIVE TRADE MANAGEMENT --- + if(active_trade.active) { + bool closed = false; + + if(active_trade.type == 1) { // Buy + if(low[i] <= active_trade.sl) { loss_count++; closed = true; } + else if(high[i] >= active_trade.tp) { win_count++; closed = true; } + } + else if(active_trade.type == -1) { // Sell + if(high[i] >= active_trade.sl) { loss_count++; closed = true; } + else if(low[i] <= active_trade.tp) { win_count++; closed = true; } + } + + if(closed) { active_trade.active = false; active_trade.type = 0; } + } + + // --- LOGIC BUY SIDE --- + bool allow_buy = (!active_trade.active || active_trade.type != 1); + + int lowest_idx = -1; + double min_val = DBL_MAX; + for(int k=0; k<=InpLookback; k++) { + if(i+k < rates_total && low[i+k] < min_val) min_val = low[i+k]; + } + if(low[i] == min_val) { + buy_state = 1; + current_base_low = low[i]; + } + + if(buy_state == 1) { + if(high[i] >= ema50[i] || high[i] >= ema100[i] || high[i] >= ema200[i]) buy_state = 2; + } + + if(buy_state == 2) { + // Strict Logic: Low < Base + Threshold AND Low > Base + // Wait, if Low == Base, it touches SL immediately. Do we allow entry at base? + // Usually entry is slightly above base. + // Let's assume Low > Base for valid entry. + if(low[i] <= current_base_low + (InpBaseThreshold * _Point) && low[i] > current_base_low) { + if(allow_buy) { + if(active_trade.active && active_trade.type == -1) { + if(low[i] < active_trade.entry) win_count++; else loss_count++; + active_trade.active = false; + } + + BufferBuySignal[i] = low[i]; + BufferBuySL[i] = current_base_low; // Visible Dot + BufferBuyTP[i] = ema600[i]; + + if(InpDebugLogs) Print("Buy Signal @ ", i, " Price:", low[i], " SL:", current_base_low); + + active_trade.active = true; + active_trade.type = 1; + active_trade.entry = low[i]; + active_trade.sl = current_base_low; + active_trade.tp = ema600[i]; + active_trade.time = time[i]; + } + buy_state = 0; + } + } + + if(buy_state > 0 && low[i] < current_base_low) { + buy_state = 1; + current_base_low = low[i]; + } + + // --- LOGIC SELL SIDE --- + bool allow_sell = (!active_trade.active || active_trade.type != -1); + + double max_val = -DBL_MAX; + for(int k=0; k<=InpLookback; k++) { + if(i+k < rates_total && high[i+k] > max_val) max_val = high[i+k]; + } + if(high[i] == max_val) { + sell_state = 1; + current_base_high = high[i]; + } + + if(sell_state == 1) { + if(low[i] <= ema50[i] || low[i] <= ema100[i] || low[i] <= ema200[i]) sell_state = 2; + } + + if(sell_state == 2) { + if(high[i] >= current_base_high - (InpBaseThreshold * _Point) && high[i] < current_base_high) { + if(allow_sell) { + if(active_trade.active && active_trade.type == 1) { + if(high[i] > active_trade.entry) win_count++; else loss_count++; + active_trade.active = false; + } + + BufferSellSignal[i] = high[i]; + BufferSellSL[i] = current_base_high; // Visible Dot + BufferSellTP[i] = ema600[i]; + + if(InpDebugLogs) Print("Sell Signal @ ", i, " Price:", high[i], " SL:", current_base_high); + + active_trade.active = true; + active_trade.type = -1; + active_trade.entry = high[i]; + active_trade.sl = current_base_high; + active_trade.tp = ema600[i]; + active_trade.time = time[i]; + } + sell_state = 0; + } + } + + if(sell_state > 0 && high[i] > current_base_high) { + sell_state = 1; + current_base_high = high[i]; + } + + BufferBuyState[i] = buy_state; + BufferSellState[i] = sell_state; + } + + UpdateDashboard(); + ChartRedraw(); + return(rates_total); +} + +void UpdateDashboard() { + CreateStatLabel("Stat_Win", 30, "Win: " + IntegerToString(win_count)); + CreateStatLabel("Stat_Loss", 45, "Loss: " + IntegerToString(loss_count)); + + double rate = 0; + if(win_count + loss_count > 0) rate = (double)win_count * 100.0 / (win_count + loss_count); + CreateStatLabel("Stat_Rate", 60, "Rate: " + DoubleToString(rate, 1) + "%"); +} + +void CreateStatLabel(string name, int y, string text) { + if(ObjectFind(0, name) < 0) { + ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 20); + ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite); + ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9); + ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + } + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); + ObjectSetString(0, name, OBJPROP_TEXT, text); +} + +void OnDeinit(const int reason) { + ObjectDelete(0, "Stat_BG"); + ObjectDelete(0, "Stat_Win"); + ObjectDelete(0, "Stat_Loss"); + ObjectDelete(0, "Stat_Rate"); +}