UTBotAlerts/UTBotAlerts.mq5

531 lines
22 KiB
MQL5
Raw Permalink Normal View History

2026-02-20 20:05:16 -06:00
//╔════════════════════════════════════════════════════════════════════════╗
//║ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ █████╗ █████╗ █████╗ ███╗ ██╗ ║
//║ ██╔═══╝╚██╗██╔╝██╔══██╗██╔══██╗██╔═══╝██╔══██╗██╔═══╝██╔══██╗████╗ ██║ ║
//║ █████╗ ╚███╔╝ ██║ ██║██████╔╝█████╗ ███████║██║ ██║ ██║██╔██╗██║ ║
//║ ██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║██║╚████║ ║
//║ ██████╗██╔╝ ██╗╚█████╔╝██████╔╝██████╗██║ ██║╚█████╗╚█████╔╝██║ ╚███║ ║
//║ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚════╝ ╚═╝ ╚══╝ ║
//╚═══════ 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;
}
//+------------------------------------------------------------------+