UTBotAlerts/UTBotAlerts.mq4

448 lines
39 KiB
MQL4
Raw Permalink Normal View History

2026-02-20 20:05:16 -06:00
<EFBFBD><EFBFBD>//T%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%P%W%
//Q% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%W% Q%
//Q% <EFBFBD>%<EFBFBD>%T%P%P%P%]%Z%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%]%<EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%P%P%P%]%<EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%P%P%P%]%<EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%Q% Q%
//Q% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%T%]% <EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%T%]%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%T%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%Q% Q%
//Q% <EFBFBD>%<EFBFBD>%T%P%P%]% <EFBFBD>%<EFBFBD>%T%<EFBFBD>%<EFBFBD>%W% <EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%P%P%]% <EFBFBD>%<EFBFBD>%T%P%P%<EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q%<EFBFBD>%<EFBFBD>%Q%Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%Q% Q%
//Q% <EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%T%]% <EFBFBD>%<EFBFBD>%W%Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%T%]%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%T%]%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W%<EFBFBD>%<EFBFBD>%Q% <EFBFBD>%<EFBFBD>%Q%Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%W%Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%<EFBFBD>%T%]%<EFBFBD>%<EFBFBD>%Q% Z%<EFBFBD>%<EFBFBD>%<EFBFBD>%Q% Q%
//Q% Z%P%P%P%P%P%]%Z%P%]% Z%P%]% Z%P%P%P%P%]% Z%P%P%P%P%P%]% Z%P%P%P%P%P%]%Z%P%]% Z%P%]% Z%P%P%P%P%]% Z%P%P%P%P%]% Z%P%]% Z%P%P%]% Q%
//Z%P%P%P%P%P%P%P% Algorithms that observe P%P%P%P%P%P% Signals that speak P%P%P%P%P%P%P%P%P%P%P%P%P%P%]%
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+