448 lines
No EOL
39 KiB
MQL4
448 lines
No EOL
39 KiB
MQL4
//╔════════════════════════════════════════════════════════════════════════╗
|
|
//║ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ █████╗ █████╗ █████╗ ███╗ ██╗ ║
|
|
//║ ██╔═══╝╚██╗██╔╝██╔══██╗██╔══██╗██╔═══╝██╔══██╗██╔═══╝██╔══██╗████╗ ██║ ║
|
|
//║ █████╗ ╚███╔╝ ██║ ██║██████╔╝█████╗ ███████║██║ ██║ ██║██╔██╗██║ ║
|
|
//║ ██╔══╝ ██╔██╗ ██║ ██║██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║██║╚████║ ║
|
|
//║ ██████╗██╔╝ ██╗╚█████╔╝██████╔╝██████╗██║ ██║╚█████╗╚█████╔╝██║ ╚███║ ║
|
|
//║ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚════╝ ╚════╝ ╚═╝ ╚══╝ ║
|
|
//╚═══════ 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;
|
|
}
|
|
//+------------------------------------------------------------------+ |