//╔════════════════════════════════════════════════════════════════════════╗ //║ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ █████╗ █████╗ █████╗ ███╗ ██╗ ║ //║ ██╔═══╝╚██╗██╔╝██╔══██╗██╔══██╗██╔═══╝██╔══██╗██╔═══╝██╔══██╗████╗ ██║ ║ //║ █████╗ ╚███╔╝ ██║ ██║██████╔╝█████╗ ███████║██║ ██║ ██║██╔██╗██║ ║ //║ ██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║██║╚████║ ║ //║ ██████╗██╔╝ ██╗╚█████╔╝██████╔╝██████╗██║ ██║╚█████╗╚█████╔╝██║ ╚███║ ║ //║ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚════╝ ╚═╝ ╚══╝ ║ //╚═══════ Algorithms that observe ══════ Signals that speak ══════════════╝ //+------------------------------------------------------------------+ //| Faithful MQL4 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. | //+------------------------------------------------------------------+ //| MQL4 Notes: | //| - No DRAW_COLOR_LINE: trail uses 3 overlapping colored lines | //| - No DRAW_COLOR_CANDLES: bar coloring not available in MQL4 | //| (the MQL5 version supports bar coloring) | //| - iATR() returns value directly (no handle system) | //| - Buffers use as-series indexing (0 = newest bar) | //+------------------------------------------------------------------+ #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 (8 total: 5 displayed + 3 calculation) | //+------------------------------------------------------------------+ // Displayed: | // [0] Trail Green line (DRAW_LINE, clrLime) | // [1] Trail Red line (DRAW_LINE, clrRed) | // [2] Trail Blue line (DRAW_LINE, clrDodgerBlue) | // [3] Buy Arrow (DRAW_ARROW, clrLime) | // [4] Sell Arrow (DRAW_ARROW, clrRed) | // Calculation only: | // [5] Trail values (all, continuous, for recursive calc) | // [6] Position state (+1 long, -1 short, 0 neutral) | // [7] Source price (close or HA close) | //+------------------------------------------------------------------+ #property indicator_buffers 5 // Displayed buffers count //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ //| Pine Script mapping: | //| a = input(1, "Key Vaule...") -> InpKeyValue | //| c = input(10, "ATR Period") -> InpATRPeriod | //| h = input(false, "Signals from...") -> InpUseHeikinAshi | //+------------------------------------------------------------------+ extern string Sep1 = "=== UT Bot Core ==="; // ____________ extern double InpKeyValue = 1.0; // Key Value (ATR Multiplier / Sensitivity) extern int InpATRPeriod = 10; // ATR Period extern bool InpUseHeikinAshi = false; // Use Heikin Ashi Candles as Source extern string Sep2 = "=== Visuals ==="; // ____________ extern bool InpShowTrailLine = true; // Show Trailing Stop Line extern bool InpShowSignals = true; // Show Buy/Sell Arrows extern int InpArrowOffset = 10; // Arrow Distance from Bar (points) extern string Sep3 = "=== Alerts ==="; // ____________ extern bool InpAlertPopup = true; // Popup Alert extern bool InpAlertSound = true; // Sound Alert extern bool InpAlertPush = false; // Push Notification extern bool InpAlertEmail = false; // Email Notification //+------------------------------------------------------------------+ //| Indicator Buffers | //+------------------------------------------------------------------+ //--- Displayed buffers double TrailGreenBuffer[]; // [0] Trail line — long segments double TrailRedBuffer[]; // [1] Trail line — short segments double TrailBlueBuffer[]; // [2] Trail line — neutral segments double BuyArrowBuffer[]; // [3] Buy arrow positions double SellArrowBuffer[]; // [4] Sell arrow positions //--- Calculation-only buffers double TrailBuffer[]; // [5] Continuous trail values double PosBuffer[]; // [6] Position state double SrcBuffer[]; // [7] Source prices //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ 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; } //--- Register total buffer count (5 displayed + 3 calculation) IndicatorBuffers(8); //--- Buffer 0: Trail Green (long) SetIndexBuffer(0, TrailGreenBuffer); SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrLime); SetIndexLabel(0, "Trail (Long)"); SetIndexEmptyValue(0, EMPTY_VALUE); //--- Buffer 1: Trail Red (short) SetIndexBuffer(1, TrailRedBuffer); SetIndexStyle(1, DRAW_LINE, STYLE_SOLID, 2, clrRed); SetIndexLabel(1, "Trail (Short)"); SetIndexEmptyValue(1, EMPTY_VALUE); //--- Buffer 2: Trail Blue (neutral) SetIndexBuffer(2, TrailBlueBuffer); SetIndexStyle(2, DRAW_LINE, STYLE_SOLID, 2, clrDodgerBlue); SetIndexLabel(2, "Trail (Neutral)"); SetIndexEmptyValue(2, EMPTY_VALUE); //--- Buffer 3: Buy Arrow SetIndexBuffer(3, BuyArrowBuffer); SetIndexStyle(3, DRAW_ARROW, STYLE_SOLID, 2, clrLime); SetIndexArrow(3, 233); // Wingdings up arrow SetIndexLabel(3, "Buy Signal"); SetIndexEmptyValue(3, EMPTY_VALUE); //--- Buffer 4: Sell Arrow SetIndexBuffer(4, SellArrowBuffer); SetIndexStyle(4, DRAW_ARROW, STYLE_SOLID, 2, clrRed); SetIndexArrow(4, 234); // Wingdings down arrow SetIndexLabel(4, "Sell Signal"); SetIndexEmptyValue(4, EMPTY_VALUE); //--- Buffer 5: Trail values (calculation, hidden) SetIndexBuffer(5, TrailBuffer); SetIndexStyle(5, DRAW_NONE); SetIndexLabel(5, NULL); //--- Buffer 6: Position state (calculation, hidden) SetIndexBuffer(6, PosBuffer); SetIndexStyle(6, DRAW_NONE); SetIndexLabel(6, NULL); //--- Buffer 7: Source price (calculation, hidden) SetIndexBuffer(7, SrcBuffer); SetIndexStyle(7, DRAW_NONE); SetIndexLabel(7, NULL); //--- Short name IndicatorShortName(StringFormat("UT Bot (%.1f, %d%s)", InpKeyValue, InpATRPeriod, InpUseHeikinAshi ? ", HA" : "")); IndicatorDigits((int)MarketInfo(_Symbol, MODE_DIGITS)); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| 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 | //| Arrays are as-series: index 0 = newest, rates_total-1 = oldest | //+------------------------------------------------------------------+ void CalcHeikinAshiClose(double &ha_close[], int total) { if(total < 1) return; //--- Start from the oldest bar (highest index) int oldest = total - 1; double ha_open_val = Open[oldest]; ha_close[oldest] = (Open[oldest] + High[oldest] + Low[oldest] + Close[oldest]) / 4.0; //--- Iterate from old to new (decreasing index) for(int i = oldest - 1; i >= 0; 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((ENUM_TIMEFRAMES)_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; //--- Build source price array if(InpUseHeikinAshi) CalcHeikinAshiClose(SrcBuffer, rates_total); else { for(int k = rates_total - 1; k >= 0; k--) SrcBuffer[k] = Close[k]; } //--- Determine calculation range // MQL4 buffers are as-series: index 0 = newest, high index = oldest // We iterate from oldest uncalculated bar to newest (high to low index) int limit; if(prev_calculated <= 0) { //--- First run: initialize all buffers ArrayInitialize(TrailGreenBuffer, EMPTY_VALUE); ArrayInitialize(TrailRedBuffer, EMPTY_VALUE); ArrayInitialize(TrailBlueBuffer, EMPTY_VALUE); ArrayInitialize(BuyArrowBuffer, EMPTY_VALUE); ArrayInitialize(SellArrowBuffer, EMPTY_VALUE); ArrayInitialize(TrailBuffer, 0.0); ArrayInitialize(PosBuffer, 0.0); //--- Seed oldest bar int oldest = rates_total - 1; TrailBuffer[oldest] = SrcBuffer[oldest]; PosBuffer[oldest] = 0.0; limit = rates_total - 2; // Start from second oldest } else { //--- Subsequent calls: recalculate recent bars limit = rates_total - prev_calculated + 1; if(limit < 0) limit = 0; } //================================================================= // MAIN LOOP: Iterate from oldest uncalculated to newest // In as-series: oldest = high index, newest = low index // Previous (older) bar = index i+1 //================================================================= for(int i = limit; i >= 0; i--) { double curr_src = SrcBuffer[i]; double prev_src = SrcBuffer[i + 1]; double prev_trail = TrailBuffer[i + 1]; double nLoss = InpKeyValue * iATR(_Symbol, 0, InpATRPeriod, 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; } TrailBuffer[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))) // // In as-series: src[1] = prev_src = SrcBuffer[i+1] // trail[1] = prev_trail = TrailBuffer[i+1] // pos[1] = PosBuffer[i+1] //============================================================ if(prev_src < prev_trail && curr_src > prev_trail) PosBuffer[i] = 1.0; // Flip LONG else if(prev_src > prev_trail && curr_src < prev_trail) PosBuffer[i] = -1.0; // Flip SHORT else PosBuffer[i] = PosBuffer[i + 1]; // Hold //============================================================ // PHASE 1c: Signal Detection (Crossovers) //============================================================ // crossover(src, trail) = src[now] > trail[now] AND src[prev] <= trail[prev] // In as-series: prev = i+1 //============================================================ 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 //============================================================ // MQL4 has no DRAW_COLOR_LINE, so we use 3 overlapping lines. // Each color buffer holds the trail value for its segments, // with EMPTY_VALUE for gaps. Transition bars have the value // in BOTH the old and new color buffer to connect segments. // // Color is based on PosBuffer[i+1] (previous bar's state) // to align the visual color change with the signal arrow, // same fix as in the MQL5 version. //============================================================ TrailGreenBuffer[i] = EMPTY_VALUE; TrailRedBuffer[i] = EMPTY_VALUE; TrailBlueBuffer[i] = EMPTY_VALUE; if(InpShowTrailLine) { double pos_for_color = PosBuffer[i + 1]; if(pos_for_color > 0.5) TrailGreenBuffer[i] = trail; else if(pos_for_color < -0.5) TrailRedBuffer[i] = trail; else TrailBlueBuffer[i] = trail; //--- Bridge: at color transitions, also write the trail value // into the NEW color buffer at the previous bar (i+1) // so the line segment connects without gaps. // Only needed when pos changes between bar i+1 and bar i. if(i + 2 < rates_total) { double pos_prev_color = PosBuffer[i + 2]; if((int)pos_for_color != (int)pos_prev_color) { //--- The bar at i+1 is a transition point. // It already has the old color. Add the new color too. if(pos_for_color > 0.5) TrailGreenBuffer[i + 1] = TrailBuffer[i + 1]; else if(pos_for_color < -0.5) TrailRedBuffer[i + 1] = TrailBuffer[i + 1]; else TrailBlueBuffer[i + 1] = TrailBuffer[i + 1]; } } } //============================================================ // 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 3: Alerts (bar 1 = last confirmed bar) //============================================================ // In as-series: bar 0 = forming, bar 1 = last closed //============================================================ if(i == 1) { if(buy_signal) FireAlert(true, Time[i]); if(sell_signal) FireAlert(false, Time[i]); } } return rates_total; } //+------------------------------------------------------------------+