Files
profitable-expert-advisor/frontline/units-trailing/RSIScalpingXAUUSD-trailing/main.mq5
T
2026-05-02 15:55:04 +02:00

646 lines
22 KiB
Plaintext

//+------------------------------------------------------------------+
//| RSIScalping.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.06"
#include <Trade\Trade.mqh>
#include "../_united/MagicNumberHelpers.mqh"
//--- Input parameters
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
input int RSI_Period = 14; // RSI Period
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
input double RSI_Overbought = 71; // RSI Overbought Level
input double RSI_Oversold = 57; // RSI Oversold Level
input bool UseEntrySlopeFilter = false; // require RSI momentum on entry bars
input double EntryMinSlopePerBar = 1.0; // minimum RSI delta per bar for entry
input double RSI_Target_Buy = 80; // RSI Target for Buy Exit
input double RSI_Target_Sell = 57; // RSI Target for Sell Exit
input int BarsToWait = 1; // Bars to wait when RSI goes against position
input double LotSize = 0.1; // Lot Size
input int MagicNumber = 129102315; // Magic Number
input int Slippage = 3; // Slippage in points
input group "=== Reversal escape (intrabar, multi-signal) ==="
input bool UseReversalEscape = true; // run while in position every tick (now uses ReversalEscapeTimeFrame)
input ENUM_TIMEFRAMES ReversalEscapeTimeFrame = PERIOD_M5; // ATR / RSI velocity / bar signs on this TF (not signal TF)
input int ReversalATRPeriod = 14; // ATR lookback on ReversalEscapeTimeFrame
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
input int ReversalSignsRequired = 1; // how many independent signs must align
input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer
input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign
input group "=== Trailing stop ==="
input bool UseTrailingStop = true; // move SL behind price while in profit
input double TrailingStopDistancePoints = 71.0; // SL distance from current bid/ask (points)
input double TrailingActivationPoints = 41.0; // min profit before trailing (0 = same as distance)
input group "=== Intrabar give-back (same bar reversals) ==="
input bool UseGiveBackExit = true; // exit if price gives back vs best tick since entry
input int GiveBackATRPeriod = 14; // ATR period on signal timeframe (Wilder)
input double GiveBackAtrMult = 0.1; // close when retrace from peak/trough >= this * ATR
input bool GiveBackRequireMfe = true; // long: only after bid was above entry; short: ask below entry
//--- Global variables
CTrade trade;
int rsi_handle;
int rsi_escape_handle = INVALID_HANDLE; // RSI on ReversalEscapeTimeFrame (may alias rsi_handle)
double rsi_buffer[];
double rsi_prev, rsi_current, rsi_two_bars_ago;
bool position_open = false;
int position_ticket = 0;
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
datetime last_bar_time = 0;
bool rsi_against_position = false;
int bars_against_count = 0;
ulong g_giveback_track_ticket = 0;
double g_peak_bid_since_entry = 0.0;
double g_trough_ask_since_entry = 0.0;
void ResetIntrabarGiveBackState();
void TryGiveBackExit();
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize RSI indicator
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
if(rsi_handle == INVALID_HANDLE)
{
return(INIT_FAILED);
}
if(ReversalEscapeTimeFrame == TimeFrame)
rsi_escape_handle = rsi_handle;
else
{
rsi_escape_handle = iRSI(_Symbol, ReversalEscapeTimeFrame, RSI_Period, RSI_Applied_Price);
if(rsi_escape_handle == INVALID_HANDLE)
{
return(INIT_FAILED);
}
}
// Initialize trade object
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
// Allocate arrays
ArraySetAsSeries(rsi_buffer, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(rsi_escape_handle != INVALID_HANDLE && rsi_escape_handle != rsi_handle)
IndicatorRelease(rsi_escape_handle);
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
return;
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
const bool new_bar = (current_bar_time != last_bar_time);
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
if(!in_pos && !new_bar)
return;
if(!UpdateRSI())
return;
if(in_pos && UseReversalEscape)
TryReversalEscape();
if(in_pos && UseGiveBackExit)
TryGiveBackExit();
if(in_pos && UseTrailingStop)
ApplyTrailingStop();
if(!new_bar)
return;
last_bar_time = current_bar_time;
ResyncPositionFromMarket();
CheckExistingPosition();
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
CheckEntrySignals();
}
//+------------------------------------------------------------------+
//| Update RSI values |
//+------------------------------------------------------------------+
bool UpdateRSI()
{
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
{
return false;
}
rsi_current = rsi_buffer[0]; // Current bar
rsi_prev = rsi_buffer[1]; // Previous bar
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
return true;
}
//+------------------------------------------------------------------+
//| Wilder ATR in price units (signal timeframe) |
//+------------------------------------------------------------------+
double ATRPriceOnTF(const int period)
{
if(period < 1)
return 0.0;
MqlRates rates[];
const int need = period + 2;
if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need)
return 0.0;
ArraySetAsSeries(rates, true);
double sum = 0.0;
for(int i = 1; i <= period; i++)
{
const double hl = rates[i].high - rates[i].low;
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
sum += MathMax(hl, MathMax(hc, lc));
}
return sum / (double)period;
}
//+------------------------------------------------------------------+
//| Wilder ATR on arbitrary timeframe |
//+------------------------------------------------------------------+
double WilderATRForTF(const ENUM_TIMEFRAMES tf, const int period)
{
if(period < 1)
return 0.0;
MqlRates rates[];
const int need = period + 2;
if(CopyRates(_Symbol, tf, 0, need, rates) < need)
return 0.0;
ArraySetAsSeries(rates, true);
double sum = 0.0;
for(int i = 1; i <= period; i++)
{
const double hl = rates[i].high - rates[i].low;
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
sum += MathMax(hl, MathMax(hc, lc));
}
return sum / (double)period;
}
//+------------------------------------------------------------------+
//| Independent adverse signs (need ReversalSignsRequired to exit) |
//+------------------------------------------------------------------+
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr)
{
if(atr <= 0.0)
return 0;
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int signs = 0;
double rsi_esc[];
ArraySetAsSeries(rsi_esc, true);
const bool ok_esc_rsi = (rsi_escape_handle != INVALID_HANDLE &&
CopyBuffer(rsi_escape_handle, 0, 0, 2, rsi_esc) >= 2);
if(ptype == POSITION_TYPE_BUY)
{
if(entry - bid >= ReversalAdverseAtrMult * atr)
signs++;
if(ok_esc_rsi && rsi_esc[1] - rsi_esc[0] >= ReversalRsiVelocity)
signs++;
}
else if(ptype == POSITION_TYPE_SELL)
{
if(ask - entry >= ReversalAdverseAtrMult * atr)
signs++;
if(ok_esc_rsi && rsi_esc[0] - rsi_esc[1] >= ReversalRsiVelocity)
signs++;
}
else
return 0;
MqlRates r[];
if(CopyRates(_Symbol, ReversalEscapeTimeFrame, 0, 4, r) >= 4)
{
ArraySetAsSeries(r, true);
const double body = MathAbs(r[1].close - r[1].open);
if(body >= ReversalBodyAtrMult * atr)
{
if(ptype == POSITION_TYPE_BUY && r[1].close < r[1].open)
signs++;
else if(ptype == POSITION_TYPE_SELL && r[1].close > r[1].open)
signs++;
}
if(ptype == POSITION_TYPE_BUY)
{
if(r[1].close < r[2].close && r[2].close < r[3].close)
signs++;
}
else
{
if(r[1].close > r[2].close && r[2].close > r[3].close)
signs++;
}
}
return signs;
}
//+------------------------------------------------------------------+
//| Cut losers fast on violent reversals (evaluated every tick) |
//+------------------------------------------------------------------+
void TryReversalEscape()
{
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
return;
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double atr = WilderATRForTF(ReversalEscapeTimeFrame, ReversalATRPeriod);
if(atr <= 0.0)
return;
const int n = CountReversalEscapeSigns(ptype, atr);
if(n < ReversalSignsRequired)
return;
ClosePosition();
Print("RSIScalpingXAUUSD: reversal escape TF=", EnumToString(ReversalEscapeTimeFrame),
" signs=", n, " need=", ReversalSignsRequired,
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
//+------------------------------------------------------------------+
//| Reset give-back peak/trough tracking |
//+------------------------------------------------------------------+
void ResetIntrabarGiveBackState()
{
g_giveback_track_ticket = 0;
g_peak_bid_since_entry = 0.0;
g_trough_ask_since_entry = 0.0;
}
//+------------------------------------------------------------------+
//| Exit when intrabar price gives back sharply vs best since entry |
//+------------------------------------------------------------------+
void TryGiveBackExit()
{
if(!UseGiveBackExit || GiveBackAtrMult <= 0.0)
return;
const ulong ticket = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(ticket == 0 || !PositionSelectByTicket(ticket))
{
ResetIntrabarGiveBackState();
return;
}
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(g_giveback_track_ticket != ticket)
{
g_giveback_track_ticket = ticket;
if(ptype == POSITION_TYPE_BUY)
{
g_peak_bid_since_entry = bid;
g_trough_ask_since_entry = 0.0;
}
else
{
g_trough_ask_since_entry = ask;
g_peak_bid_since_entry = 0.0;
}
}
const double atr = ATRPriceOnTF(GiveBackATRPeriod);
if(atr <= 0.0)
return;
const double threshold = GiveBackAtrMult * atr;
if(ptype == POSITION_TYPE_BUY)
{
if(bid > g_peak_bid_since_entry)
g_peak_bid_since_entry = bid;
if(GiveBackRequireMfe && g_peak_bid_since_entry <= entry)
return;
if(g_peak_bid_since_entry - bid >= threshold)
{
ClosePosition();
Print("RSIScalpingXAUUSD: give-back exit BUY retrace=",
DoubleToString(g_peak_bid_since_entry - bid, digits),
" thr=", DoubleToString(threshold, digits));
}
}
else if(ptype == POSITION_TYPE_SELL)
{
if(ask < g_trough_ask_since_entry)
g_trough_ask_since_entry = ask;
if(GiveBackRequireMfe && g_trough_ask_since_entry >= entry)
return;
if(ask - g_trough_ask_since_entry >= threshold)
{
ClosePosition();
Print("RSIScalpingXAUUSD: give-back exit SELL retrace=",
DoubleToString(ask - g_trough_ask_since_entry, digits),
" thr=", DoubleToString(threshold, digits));
}
}
}
//+------------------------------------------------------------------+
//| Trail SL behind favorable price (every tick when enabled) |
//+------------------------------------------------------------------+
void ApplyTrailingStop()
{
if(TrailingStopDistancePoints <= 0.0)
return;
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
return;
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point <= 0.0)
return;
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
const double trail_dist = TrailingStopDistancePoints * point;
const double activation_pts = (TrailingActivationPoints > 0.0)
? TrailingActivationPoints
: TrailingStopDistancePoints;
const double activation = activation_pts * point;
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
const double min_dist = (double)stops_level * point;
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double cur_sl = PositionGetDouble(POSITION_SL);
const double cur_tp = PositionGetDouble(POSITION_TP);
if(ptype == POSITION_TYPE_BUY)
{
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(bid - entry <= activation)
return;
double new_sl = NormalizeDouble(bid - trail_dist, digits);
if(min_dist > 0.0 && bid - new_sl < min_dist)
new_sl = NormalizeDouble(bid - min_dist, digits);
if(new_sl >= bid || new_sl <= 0.0)
return;
if(cur_sl > 0.0 && new_sl <= cur_sl)
return;
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
}
else if(ptype == POSITION_TYPE_SELL)
{
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(entry - ask <= activation)
return;
double new_sl = NormalizeDouble(ask + trail_dist, digits);
if(min_dist > 0.0 && new_sl - ask < min_dist)
new_sl = NormalizeDouble(ask + min_dist, digits);
if(new_sl <= ask || new_sl <= 0.0)
return;
if(cur_sl > 0.0 && new_sl >= cur_sl)
return;
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
}
}
void ResyncPositionFromMarket()
{
if(position_open)
return;
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(t == 0 || !PositionSelectByTicket(t))
return;
position_ticket = (int)t;
position_open = true;
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
}
//+------------------------------------------------------------------+
//| Check existing position for exit conditions |
//+------------------------------------------------------------------+
void CheckExistingPosition()
{
if(!position_open)
{
return;
}
// Check if position still exists with correct magic number
if(!PositionSelectByTicketAndMagic(position_ticket, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
// Exit conditions based on RSI target
if(current_position_type == POSITION_TYPE_BUY)
{
// Check if RSI is against the position (below oversold)
if(rsi_current < RSI_Oversold)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit long position when RSI reaches buy target
if(rsi_current >= RSI_Target_Buy)
{
ClosePosition();
}
}
}
else if(current_position_type == POSITION_TYPE_SELL)
{
// Check if RSI is against the position (above overbought)
if(rsi_current > RSI_Overbought)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit short position when RSI reaches sell target
if(rsi_current <= RSI_Target_Sell)
{
ClosePosition();
}
}
}
}
//+------------------------------------------------------------------+
//| Check for entry signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
const double upSlope1 = rsi_prev - rsi_two_bars_ago; // older->prev
const double upSlope2 = rsi_current - rsi_prev; // prev->current
const double dnSlope1 = rsi_two_bars_ago - rsi_prev; // older->prev
const double dnSlope2 = rsi_prev - rsi_current; // prev->current
const bool buySlopeOk = (!UseEntrySlopeFilter) || (upSlope1 >= EntryMinSlopePerBar && upSlope2 >= EntryMinSlopePerBar);
const bool sellSlopeOk = (!UseEntrySlopeFilter) || (dnSlope1 >= EntryMinSlopePerBar && dnSlope2 >= EntryMinSlopePerBar);
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold && buySlopeOk)
{
OpenBuyPosition();
}
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought && sellSlopeOk)
{
OpenSellPosition();
}
}
//+------------------------------------------------------------------+
//| Open buy position |
//+------------------------------------------------------------------+
void OpenBuyPosition()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
{
const ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, _Symbol, (ulong)MagicNumber))
{
position_ticket = (int)t;
position_open = true;
current_position_type = POSITION_TYPE_BUY;
}
}
}
//+------------------------------------------------------------------+
//| Open sell position |
//+------------------------------------------------------------------+
void OpenSellPosition()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
{
const ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, _Symbol, (ulong)MagicNumber))
{
position_ticket = (int)t;
position_open = true;
current_position_type = POSITION_TYPE_SELL;
}
}
}
//+------------------------------------------------------------------+
//| Close current position |
//+------------------------------------------------------------------+
void ClosePosition()
{
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
ResetIntrabarGiveBackState();
return;
}
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
ResetIntrabarGiveBackState();
return;
}
Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=",
trade.ResultRetcode(), " lastError=", GetLastError());
}