//╔════════════════════════════════════════════════════════════════════════╗ //║ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ █████╗ █████╗ █████╗ ███╗ ██╗ ║ //║ ██╔═══╝╚██╗██╔╝██╔══██╗██╔══██╗██╔═══╝██╔══██╗██╔═══╝██╔══██╗████╗ ██║ ║ //║ █████╗ ╚███╔╝ ██║ ██║██████╔╝█████╗ ███████║██║ ██║ ██║██╔██╗██║ ║ //║ ██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║██║╚████║ ║ //║ ██████╗██╔╝ ██╗╚█████╔╝██████╔╝██████╗██║ ██║╚█████╗╚█████╔╝██║ ╚███║ ║ //║ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚════╝ ╚═╝ ╚══╝ ║ //╚═══════ Algorithms that observe ══════ Signals that speak ══════════════╝ //+------------------------------------------------------------------+ //| Faithful MQL5 conversion of "UT Bot Alerts" by QuantNomad | //| Original Pine Script: tradingview.com/script/n8ss8BID | //| Originally developed by Yo_adriiiiaan, idea by HPotter | //| Pine Script v4 alerts version by QuantNomad (Vadim Cissa) | //+------------------------------------------------------------------+ //| Algorithm: ATR-based adaptive trailing stop with crossover | //| detection for buy/sell signal generation. | //| | //| The trailing stop uses a 4-branch recursive logic: | //| 1. Uptrend continuation -> stop ratchets UP only | //| 2. Downtrend continuation -> stop ratchets DOWN only | //| 3. Bullish reversal -> reset stop = price - nLoss | //| 4. Bearish reversal -> reset stop = price + nLoss | //| | //| Signals fire on the bar where price crosses the trailing stop. | //| Non-repainting on confirmed (closed) bars. | //+------------------------------------------------------------------+ #property copyright "Exobeacon Labs" #property link "https://www.exobeacon.com" #property version "1.0" #property description "UT Bot Alerts - ATR Trailing Stop System" #property description "Converted from TradingView Pine Script by QuantNomad" #property description " " #property description "Generates Buy/Sell signals when price crosses an" #property description "adaptive ATR-based trailing stop line." #property description " " #property description "〰〰〰〰〰((👽))〰〰〰〰〰" #property description "🛸 mql5.com/en/users/ulisescalb" #property description "🛸 github.com/Exobeacon-Labs" #property strict #property indicator_chart_window //+------------------------------------------------------------------+ //| Buffer Layout (10 buffers, 4 plots) | //+------------------------------------------------------------------+ // Plot 0: DRAW_COLOR_LINE -> Buf[0]=data, Buf[1]=color | // Plot 1: DRAW_ARROW (Buy) -> Buf[2]=data | // Plot 2: DRAW_ARROW (Sell)-> Buf[3]=data | // Plot 3: DRAW_COLOR_CANDLES -> Buf[4..7]=OHLC, Buf[8]=color | // Calc: Buf[9] = Position state | //+------------------------------------------------------------------+ #property indicator_buffers 10 #property indicator_plots 4 //--- Plot 0: ATR Trailing Stop Line #property indicator_label1 "ATR Trailing Stop" #property indicator_type1 DRAW_COLOR_LINE #property indicator_color1 clrLime, clrRed, clrDodgerBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 2 //--- Plot 1: Buy Signal Arrow #property indicator_label2 "Buy Signal" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrLime #property indicator_style2 STYLE_SOLID #property indicator_width2 2 //--- Plot 2: Sell Signal Arrow #property indicator_label3 "Sell Signal" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrRed #property indicator_style3 STYLE_SOLID #property indicator_width3 2 //--- Plot 3: Colored Candles (Bar Coloring) #property indicator_label4 "Bar Color" #property indicator_type4 DRAW_COLOR_CANDLES #property indicator_color4 clrLime, clrRed, clrNONE #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ //| Pine Script mapping: | //| a = input(1, "Key Vaule...") -> InpKeyValue | //| c = input(10, "ATR Period") -> InpATRPeriod | //| h = input(false, "Signals from...") -> InpUseHeikinAshi | //+------------------------------------------------------------------+ input group "============ UT Bot Core ============" input double InpKeyValue = 1.0; // Key Value (ATR Multiplier / Sensitivity) input int InpATRPeriod = 10; // ATR Period input bool InpUseHeikinAshi = false; // Use Heikin Ashi Candles as Source input group "============ Visuals ============" input bool InpShowTrailLine = true; // Show Trailing Stop Line input bool InpShowSignals = true; // Show Buy/Sell Arrows input bool InpColorBars = true; // Color Price Bars input int InpArrowOffset = 10; // Arrow Distance from Bar (points) input group "============ Alerts ============" input bool InpAlertPopup = true; // Popup Alert input bool InpAlertSound = true; // Sound Alert input bool InpAlertPush = false; // Push Notification input bool InpAlertEmail = false; // Email Notification //+------------------------------------------------------------------+ //| Indicator Buffers | //+------------------------------------------------------------------+ double TrailLineBuffer[]; // [0] Trail stop values double TrailColorBuffer[]; // [1] Trail stop color index double BuyArrowBuffer[]; // [2] Buy arrow positions double SellArrowBuffer[]; // [3] Sell arrow positions double BarOpenBuffer[]; // [4] Candle Open for coloring double BarHighBuffer[]; // [5] Candle High for coloring double BarLowBuffer[]; // [6] Candle Low for coloring double BarCloseBuffer[]; // [7] Candle Close for coloring double BarColorBuffer[]; // [8] Candle color index double PosBuffer[]; // [9] Position state (calculation only) //+------------------------------------------------------------------+ //| Internal calculation arrays (not tied to plots) | //+------------------------------------------------------------------+ double g_trail[]; // Internal trailing stop (always calculated) double g_pos[]; // Internal position state (always calculated) double g_src[]; // Source price array //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ int g_atrHandle = INVALID_HANDLE; datetime g_lastAlertBar = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization | //+------------------------------------------------------------------+ int OnInit() { //--- Validate inputs if(InpATRPeriod < 1) { PrintFormat("[UTBot] ATR Period must be >= 1 (got %d)", InpATRPeriod); return INIT_PARAMETERS_INCORRECT; } if(InpKeyValue <= 0.0) { PrintFormat("[UTBot] Key Value must be > 0 (got %.4f)", InpKeyValue); return INIT_PARAMETERS_INCORRECT; } //--- Create ATR handle // MT5 iATR uses Wilder's smoothing (RMA) = matches TradingView's atr() g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, InpATRPeriod); if(g_atrHandle == INVALID_HANDLE) { Print("[UTBot] Failed to create ATR handle"); return INIT_FAILED; } //--- Bind buffers to plots // IMPORTANT: Never change PLOT_DRAW_TYPE after binding. // Changing to DRAW_NONE corrupts the buffer-to-plot mapping // in MT5. Visibility is controlled via EMPTY_VALUE in OnCalculate. // Plot 0: Trailing Stop (DRAW_COLOR_LINE = 2 buffers) SetIndexBuffer(0, TrailLineBuffer, INDICATOR_DATA); SetIndexBuffer(1, TrailColorBuffer, INDICATOR_COLOR_INDEX); PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); // Plot 1: Buy Arrow (DRAW_ARROW = 1 buffer) SetIndexBuffer(2, BuyArrowBuffer, INDICATOR_DATA); PlotIndexSetInteger(1, PLOT_ARROW, 233); // Wingdings up arrow PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); // Plot 2: Sell Arrow (DRAW_ARROW = 1 buffer) SetIndexBuffer(3, SellArrowBuffer, INDICATOR_DATA); PlotIndexSetInteger(2, PLOT_ARROW, 234); // Wingdings down arrow PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); // Plot 3: Colored Candles (DRAW_COLOR_CANDLES = 4 data + 1 color) SetIndexBuffer(4, BarOpenBuffer, INDICATOR_DATA); SetIndexBuffer(5, BarHighBuffer, INDICATOR_DATA); SetIndexBuffer(6, BarLowBuffer, INDICATOR_DATA); SetIndexBuffer(7, BarCloseBuffer, INDICATOR_DATA); SetIndexBuffer(8, BarColorBuffer, INDICATOR_COLOR_INDEX); PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); // Calculation-only buffer SetIndexBuffer(9, PosBuffer, INDICATOR_CALCULATIONS); //--- All buffers: left-to-right indexing (index 0 = oldest bar) ArraySetAsSeries(TrailLineBuffer, false); ArraySetAsSeries(TrailColorBuffer, false); ArraySetAsSeries(BuyArrowBuffer, false); ArraySetAsSeries(SellArrowBuffer, false); ArraySetAsSeries(BarOpenBuffer, false); ArraySetAsSeries(BarHighBuffer, false); ArraySetAsSeries(BarLowBuffer, false); ArraySetAsSeries(BarCloseBuffer, false); ArraySetAsSeries(BarColorBuffer, false); ArraySetAsSeries(PosBuffer, false); //--- Short name IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("UT Bot (%.1f, %d%s)", InpKeyValue, InpATRPeriod, InpUseHeikinAshi ? ", HA" : "")); IndicatorSetInteger(INDICATOR_DIGITS, _Digits); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(g_atrHandle != INVALID_HANDLE) { IndicatorRelease(g_atrHandle); g_atrHandle = INVALID_HANDLE; } ArrayFree(g_trail); ArrayFree(g_pos); ArrayFree(g_src); } //+------------------------------------------------------------------+ //| Calculate Heikin Ashi Close | //+------------------------------------------------------------------+ //| Pine: security(heikinashi(syminfo.tickerid), ..., close) | //| HA Close = (O + H + L + C) / 4 | //| HA Open = (prev_HA_Open + prev_HA_Close) / 2 | //| We only need HA Close as the source price for UT Bot. | //+------------------------------------------------------------------+ void CalcHeikinAshiClose(const double &open[], const double &high[], const double &low[], const double &close[], double &ha_close[], const int total) { if(total < 1) return; double ha_open_val = open[0]; ha_close[0] = (open[0] + high[0] + low[0] + close[0]) / 4.0; for(int i = 1; i < total; i++) { ha_open_val = (ha_open_val + ha_close[i - 1]) / 2.0; ha_close[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0; } } //+------------------------------------------------------------------+ //| Fire alert (once per bar) | //+------------------------------------------------------------------+ void FireAlert(const bool is_buy, const datetime bar_time) { if(bar_time == g_lastAlertBar) return; g_lastAlertBar = bar_time; string dir = is_buy ? "BUY" : "SELL"; string msg = StringFormat("[UT Bot] %s on %s %s | %s", dir, _Symbol, EnumToString(_Period), TimeToString(bar_time, TIME_DATE | TIME_MINUTES)); if(InpAlertPopup) Alert(msg); if(InpAlertSound) PlaySound(is_buy ? "alert.wav" : "alert2.wav"); if(InpAlertPush) SendNotification(msg); if(InpAlertEmail) SendMail("UT Bot: " + dir, msg); } //+------------------------------------------------------------------+ //| Main calculation | //+------------------------------------------------------------------+ 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[]) { //--- Minimum bars required int min_bars = InpATRPeriod + 2; if(rates_total < min_bars) return 0; //--- Set price arrays to left-to-right indexing ArraySetAsSeries(time, false); ArraySetAsSeries(open, false); ArraySetAsSeries(high, false); ArraySetAsSeries(low, false); ArraySetAsSeries(close, false); //--- Retrieve ATR values from built-in indicator double atr[]; ArrayResize(atr, rates_total); ArraySetAsSeries(atr, false); if(CopyBuffer(g_atrHandle, 0, 0, rates_total, atr) != rates_total) return 0; //--- Resize internal arrays ArrayResize(g_trail, rates_total); ArrayResize(g_pos, rates_total); ArrayResize(g_src, rates_total); //--- Build source price array if(InpUseHeikinAshi) CalcHeikinAshiClose(open, high, low, close, g_src, rates_total); else ArrayCopy(g_src, close, 0, 0, rates_total); //--- Determine starting bar int start; if(prev_calculated <= 0) { //--- First run: initialize all output buffers ArrayInitialize(TrailLineBuffer, EMPTY_VALUE); ArrayInitialize(TrailColorBuffer, 0.0); ArrayInitialize(BuyArrowBuffer, EMPTY_VALUE); ArrayInitialize(SellArrowBuffer, EMPTY_VALUE); ArrayInitialize(BarOpenBuffer, EMPTY_VALUE); ArrayInitialize(BarHighBuffer, EMPTY_VALUE); ArrayInitialize(BarLowBuffer, EMPTY_VALUE); ArrayInitialize(BarCloseBuffer, EMPTY_VALUE); ArrayInitialize(BarColorBuffer, 0.0); ArrayInitialize(PosBuffer, 0.0); //--- Initialize internal arrays ArrayInitialize(g_trail, 0.0); ArrayInitialize(g_pos, 0.0); //--- Seed bar 0 // Pine Script: nz(xATRTrailingStop[1], 0) = 0 on first bar // Since src > 0 always, branch 3 fires: trail = src - nLoss // We seed directly with src for stability. g_trail[0] = g_src[0]; g_pos[0] = 0.0; start = 1; } else { //--- Subsequent calls: recalculate from last bar start = prev_calculated - 1; } //================================================================= // MAIN LOOP: Bar-by-bar processing (left to right) // Mirrors Pine Script's execution model exactly. // // PHASE 1: Core algorithm on internal arrays (always runs) // PHASE 2: Map results to display buffers (respects visibility) //================================================================= for(int i = start; i < rates_total; i++) { double curr_src = g_src[i]; double prev_src = g_src[i - 1]; double prev_trail = g_trail[i - 1]; double nLoss = InpKeyValue * atr[i]; //============================================================ // PHASE 1a: ATR Trailing Stop — 4-Branch Recursive Logic //============================================================ // Pine Script: // xATRTrailingStop := iff( // src > nz(trail[1],0) and src[1] > nz(trail[1],0), // max(nz(trail[1]), src - nLoss), // iff( // src < nz(trail[1],0) and src[1] < nz(trail[1],0), // min(nz(trail[1]), src + nLoss), // iff(src > nz(trail[1],0), // src - nLoss, // src + nLoss))) //============================================================ double trail; if(curr_src > prev_trail && prev_src > prev_trail) { // Branch 1: Uptrend continuation — ratchet up only trail = MathMax(prev_trail, curr_src - nLoss); } else if(curr_src < prev_trail && prev_src < prev_trail) { // Branch 2: Downtrend continuation — ratchet down only trail = MathMin(prev_trail, curr_src + nLoss); } else if(curr_src > prev_trail) { // Branch 3: Bullish reversal — reset below price trail = curr_src - nLoss; } else { // Branch 4: Bearish reversal — reset above price trail = curr_src + nLoss; } g_trail[i] = trail; //============================================================ // PHASE 1b: Position State //============================================================ // Pine Script: // pos := iff(src[1] < nz(trail[1],0) and src > nz(trail[1],0), 1, // iff(src[1] > nz(trail[1],0) and src < nz(trail[1],0), -1, // nz(pos[1], 0))) //============================================================ if(prev_src < prev_trail && curr_src > prev_trail) g_pos[i] = 1.0; // Flip LONG else if(prev_src > prev_trail && curr_src < prev_trail) g_pos[i] = -1.0; // Flip SHORT else g_pos[i] = g_pos[i - 1]; // Hold //============================================================ // PHASE 1c: Signal Detection (Crossovers) //============================================================ // Pine Script: // ema = ema(src, 1) <- equals src exactly (period 1) // above = crossover(ema, xATRTrailingStop) // below = crossover(xATRTrailingStop, ema) // buy = src > xATRTrailingStop and above // sell = src < xATRTrailingStop and below // // crossover(A,B) = A[now] > B[now] AND A[prev] <= B[prev] //============================================================ bool above = (curr_src > trail) && (prev_src <= prev_trail); bool below = (curr_src < trail) && (prev_src >= prev_trail); bool buy_signal = (curr_src > trail) && above; bool sell_signal = (curr_src < trail) && below; //============================================================ // PHASE 2a: Trailing Stop Line Display //============================================================ if(InpShowTrailLine) { TrailLineBuffer[i] = trail; // Color: 0=Green(long), 1=Red(short), 2=Blue(neutral) // NOTE: DRAW_COLOR_LINE in MT5 paints the incoming segment // (from bar i-1 TO bar i) using TrailColorBuffer[i]. // Using g_pos[i-1] ensures the color change aligns visually // with the signal arrow at bar i, not one bar before it. double pos_for_color = (i >= 1) ? g_pos[i - 1] : 0.0; if(pos_for_color > 0.5) TrailColorBuffer[i] = 0.0; else if(pos_for_color < -0.5) TrailColorBuffer[i] = 1.0; else TrailColorBuffer[i] = 2.0; } else { TrailLineBuffer[i] = EMPTY_VALUE; TrailColorBuffer[i] = 0.0; } //============================================================ // PHASE 2b: Buy/Sell Arrows //============================================================ BuyArrowBuffer[i] = EMPTY_VALUE; SellArrowBuffer[i] = EMPTY_VALUE; if(InpShowSignals) { if(buy_signal) BuyArrowBuffer[i] = low[i] - InpArrowOffset * _Point; if(sell_signal) SellArrowBuffer[i] = high[i] + InpArrowOffset * _Point; } //============================================================ // PHASE 2c: Bar Coloring //============================================================ // Pine Script: // barbuy = src > xATRTrailingStop // barsell = src < xATRTrailingStop // barcolor(barbuy ? green : na) // barcolor(barsell ? red : na) //============================================================ if(InpColorBars) { BarOpenBuffer[i] = open[i]; BarHighBuffer[i] = high[i]; BarLowBuffer[i] = low[i]; BarCloseBuffer[i] = close[i]; if(curr_src > trail) BarColorBuffer[i] = 0.0; // Green else if(curr_src < trail) BarColorBuffer[i] = 1.0; // Red else BarColorBuffer[i] = 2.0; // None } else { BarOpenBuffer[i] = EMPTY_VALUE; BarHighBuffer[i] = EMPTY_VALUE; BarLowBuffer[i] = EMPTY_VALUE; BarCloseBuffer[i] = EMPTY_VALUE; BarColorBuffer[i] = 0.0; } //============================================================ // PHASE 2d: Position buffer (for EA access via iCustom) //============================================================ PosBuffer[i] = g_pos[i]; //============================================================ // PHASE 3: Alerts (last confirmed bar only) //============================================================ if(i == rates_total - 2) { if(buy_signal) FireAlert(true, time[i]); if(sell_signal) FireAlert(false, time[i]); } } return rates_total; } //+------------------------------------------------------------------+