UpDATE
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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.03"
|
||||
|
||||
#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 = 4; // Bars to wait when RSI goes against position
|
||||
input bool ExitOnAdverseRsiBarStep = true; // new bar: exit if last closed RSI vs prior closed is against trade
|
||||
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
|
||||
input int ReversalATRPeriod = 14; // ATR lookback on signal timeframe
|
||||
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
|
||||
input int ReversalSignsRequired = 2; // 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
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int 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;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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);
|
||||
}
|
||||
|
||||
// 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_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(!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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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;
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(entry - bid >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(rsi_prev - rsi_current >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(ask - entry >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(rsi_current - rsi_prev >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
|
||||
MqlRates r[];
|
||||
if(CopyRates(_Symbol, TimeFrame, 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 = ATRPriceOnTF(ReversalATRPeriod);
|
||||
if(atr <= 0.0)
|
||||
return;
|
||||
|
||||
const int n = CountReversalEscapeSigns(ptype, atr);
|
||||
if(n < ReversalSignsRequired)
|
||||
return;
|
||||
|
||||
ClosePosition();
|
||||
Print("RSIScalpingXAUUSD: reversal escape signs=", n, " need=", ReversalSignsRequired,
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
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, MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// On each new bar: last completed RSI vs the bar before — exit if that step is adverse to the position
|
||||
if(ExitOnAdverseRsiBarStep)
|
||||
{
|
||||
if(current_position_type == POSITION_TYPE_BUY && rsi_prev < rsi_two_bars_ago)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
if(current_position_type == POSITION_TYPE_SELL && rsi_prev > rsi_two_bars_ago)
|
||||
{
|
||||
ClosePosition();
|
||||
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"))
|
||||
{
|
||||
position_ticket = trade.ResultOrder();
|
||||
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"))
|
||||
{
|
||||
position_ticket = trade.ResultOrder();
|
||||
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;
|
||||
return;
|
||||
}
|
||||
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=",
|
||||
trade.ResultRetcode(), " lastError=", GetLastError());
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,284 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M10; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 65; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 500; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 220; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 110; // Break confirmation buffer (points)
|
||||
input double InpLots = 0.10; // Position size
|
||||
input long InpMagic = 26042501; // Magic number
|
||||
input bool InpDrawTrendline = true; // Draw detected trendline
|
||||
|
||||
CTrade trade;
|
||||
|
||||
int g_maHandle = INVALID_HANDLE;
|
||||
datetime g_lastBarTime = 0;
|
||||
string g_lineName = "SimpleTrendline_Basis";
|
||||
|
||||
struct TrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double p1;
|
||||
double p2;
|
||||
double p3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
bool IsNewBar()
|
||||
{
|
||||
datetime t = iTime(_Symbol, _Period, 0);
|
||||
if(t == 0)
|
||||
return false;
|
||||
if(t != g_lastBarTime)
|
||||
{
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FindRecentCrossPoints(datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
|
||||
int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool BuildTrendlineFrom3Points(TrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
int n = FindRecentCrossPoints(ts, ps);
|
||||
if(n < 3)
|
||||
return false;
|
||||
|
||||
// We collected from recent to older in series order.
|
||||
// Re-map as oldest -> newest to stabilize slope direction.
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.p1 = pOld[0];
|
||||
m.p2 = pOld[1];
|
||||
m.p3 = pOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double TrendlinePriceAtTime(const TrendlineModel &m, datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void DrawTrendline(const TrendlineModel &m)
|
||||
{
|
||||
if(!InpDrawTrendline || !m.valid)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(_Symbol, _Period, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(_Period) * 20;
|
||||
|
||||
double pStart = TrendlinePriceAtTime(m, tStart);
|
||||
double pEnd = TrendlinePriceAtTime(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, g_lineName) < 0)
|
||||
ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, g_lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, g_lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
bool GetCurrentPosition(long &type, double &volume)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return false;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
return false;
|
||||
type = PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryExitOnBreak(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(!GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(_Symbol, _Period, 1);
|
||||
datetime t1 = iTime(_Symbol, _Period, 1);
|
||||
double line1 = TrendlinePriceAtTime(m, t1);
|
||||
double buf = InpBreakBuffer * _Point;
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
trade.PositionClose(_Symbol);
|
||||
}
|
||||
|
||||
void TryPullbackEntry(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
MqlRates bars1[], bars2[];
|
||||
if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1)
|
||||
return;
|
||||
if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1)
|
||||
return;
|
||||
if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1)
|
||||
return;
|
||||
|
||||
MqlRates b1 = bars1[0];
|
||||
MqlRates b2 = bars2[0];
|
||||
|
||||
double line1 = TrendlinePriceAtTime(m, b1.time);
|
||||
double tol = InpLineTouchTolerance * _Point;
|
||||
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1.low <= (line1 + tol));
|
||||
bool reclaim = (b1.close > line1);
|
||||
bool bullish = (b1.close > b1.open);
|
||||
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1.high >= (line1 - tol));
|
||||
bool reject = (b1.close < line1);
|
||||
bool bearish = (b1.close < b1.open);
|
||||
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice);
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
g_lastBarTime = 0;
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(g_maHandle);
|
||||
if(ObjectFind(0, g_lineName) >= 0)
|
||||
ObjectDelete(0, g_lineName);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(!IsNewBar())
|
||||
return;
|
||||
|
||||
TrendlineModel m;
|
||||
if(!BuildTrendlineFrom3Points(m))
|
||||
return;
|
||||
|
||||
DrawTrendline(m);
|
||||
TryExitOnBreak(m);
|
||||
TryPullbackEntry(m);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
; SimpleTrendline.mq5 optimization preset (TSLA-focused)
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback + break exits on TSLA volatility (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||1||16387||Y
|
||||
InpMAPeriod=55||30||5||160||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=500||250||50||1500||Y
|
||||
InpLineTouchTolerance=65.0||20.0||5.0||180.0||Y
|
||||
InpBreakBuffer=22.0||8.0||2.0||70.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -0,0 +1,21 @@
|
||||
; saved on 2026.04.25
|
||||
; optimization profile for double-top-bottom-catcher.mq5
|
||||
; load this in Strategy Tester > Inputs tab > Load
|
||||
;
|
||||
InpTf=1||0||0||49153||N
|
||||
InpHtf=5||0||0||49153||N
|
||||
InpUseHtfFilter=true||false||0||true||Y
|
||||
InpEmaFast=9||7||1||14||Y
|
||||
InpEmaSlow=21||18||1||34||Y
|
||||
InpPivotLeft=2||1||1||4||Y
|
||||
InpPivotRight=2||1||1||4||Y
|
||||
InpPatternLookbackBars=180||100||20||300||Y
|
||||
InpMinPatternSeparation=6||4||1||12||Y
|
||||
InpMaxPatternSeparation=50||20||5||80||Y
|
||||
InpTopBottomTolPts=120.0||50.0||10.0||250.0||Y
|
||||
InpMinEmaPriceDistPts=80.0||20.0||10.0||180.0||Y
|
||||
InpPrevLevelLookback=120||50||10||250||Y
|
||||
InpLots=0.01||0.01||0.0||0.01||N
|
||||
InpSlBufferPts=25||10||5||60||Y
|
||||
InpMagic=930101||930101||1||9301010||N
|
||||
InpSlippagePts=30||10||5||50||Y
|
||||
@@ -0,0 +1,350 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| double-top-bottom-catcher.mq5 |
|
||||
//| Lab EA: Double top/bottom catcher with EMA distance + HTF trend |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Lab"
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpTf = PERIOD_M1; // Signal timeframe
|
||||
input ENUM_TIMEFRAMES InpHtf = PERIOD_M5; // Higher timeframe
|
||||
input bool InpUseHtfFilter = true; // Require HTF trend alignment
|
||||
input int InpEmaFast = 9; // Fast EMA
|
||||
input int InpEmaSlow = 21; // Slow EMA
|
||||
|
||||
input int InpPivotLeft = 2; // Pivot bars left
|
||||
input int InpPivotRight = 2; // Pivot bars right
|
||||
input int InpPatternLookbackBars = 180; // Search range for patterns
|
||||
input int InpMinPatternSeparation = 6; // Min bars between tops/bottoms
|
||||
input int InpMaxPatternSeparation = 50; // Max bars between tops/bottoms
|
||||
input double InpTopBottomTolPts = 120; // Max diff between top/top or bottom/bottom
|
||||
input double InpMinEmaPriceDistPts = 80; // Min stretch from EMA at 2nd touch
|
||||
input int InpPrevLevelLookback = 120; // Lookback to find previous support/resistance
|
||||
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlBufferPts = 25; // SL buffer beyond pattern extreme
|
||||
input ulong InpMagic = 20260425;
|
||||
input int InpSlippagePts = 30;
|
||||
|
||||
CTrade g_trade;
|
||||
|
||||
int g_hEmaFast = INVALID_HANDLE;
|
||||
int g_hEmaSlow = INVALID_HANDLE;
|
||||
int g_hEmaFastHtf = INVALID_HANDLE;
|
||||
int g_hEmaSlowHtf = INVALID_HANDLE;
|
||||
|
||||
double g_emaFast[];
|
||||
double g_emaSlow[];
|
||||
double g_emaFastHtf[];
|
||||
double g_emaSlowHtf[];
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_trade.SetExpertMagicNumber(InpMagic);
|
||||
g_trade.SetDeviationInPoints(InpSlippagePts);
|
||||
SetTradeFillingBySymbol();
|
||||
|
||||
g_hEmaFast = iMA(_Symbol, InpTf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hEmaSlow = iMA(_Symbol, InpTf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hEmaFastHtf = iMA(_Symbol, InpHtf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hEmaSlowHtf = iMA(_Symbol, InpHtf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE ||
|
||||
g_hEmaFastHtf == INVALID_HANDLE || g_hEmaSlowHtf == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
ArraySetAsSeries(g_emaFast, true);
|
||||
ArraySetAsSeries(g_emaSlow, true);
|
||||
ArraySetAsSeries(g_emaFastHtf, true);
|
||||
ArraySetAsSeries(g_emaSlowHtf, true);
|
||||
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast);
|
||||
if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow);
|
||||
if(g_hEmaFastHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaFastHtf);
|
||||
if(g_hEmaSlowHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowHtf);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
static datetime lastBar = 0;
|
||||
datetime barTime = iTime(_Symbol, InpTf, 0);
|
||||
if(barTime == lastBar)
|
||||
return;
|
||||
lastBar = barTime;
|
||||
|
||||
const int need = MathMax(260, InpPatternLookbackBars + InpPrevLevelLookback + 20);
|
||||
if(CopyBuffer(g_hEmaFast, 0, 0, need, g_emaFast) < need) return;
|
||||
if(CopyBuffer(g_hEmaSlow, 0, 0, need, g_emaSlow) < need) return;
|
||||
if(CopyBuffer(g_hEmaFastHtf, 0, 0, 5, g_emaFastHtf) < 5) return;
|
||||
if(CopyBuffer(g_hEmaSlowHtf, 0, 0, 5, g_emaSlowHtf) < 5) return;
|
||||
|
||||
if(PositionExistsForMagic())
|
||||
return;
|
||||
|
||||
TryEnterLongDoubleBottom();
|
||||
if(!PositionExistsForMagic())
|
||||
TryEnterShortDoubleTop();
|
||||
}
|
||||
|
||||
void TryEnterLongDoubleBottom()
|
||||
{
|
||||
int firstBottom = -1; // older
|
||||
int secondBottom = -1; // newer
|
||||
double neckline = 0.0;
|
||||
double lowA = 0.0;
|
||||
double lowB = 0.0;
|
||||
|
||||
if(!FindDoubleBottom(firstBottom, secondBottom, neckline, lowA, lowB))
|
||||
return;
|
||||
|
||||
const int c = 1;
|
||||
double close1 = iClose(_Symbol, InpTf, c);
|
||||
if(close1 <= neckline)
|
||||
return; // wait for neckline break confirmation
|
||||
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double stretched = g_emaFast[secondBottom] - lowB;
|
||||
if(stretched < InpMinEmaPriceDistPts * pt)
|
||||
return; // no enough EMA/price displacement for reversal
|
||||
|
||||
if(close1 <= g_emaFast[c])
|
||||
return; // keep confirmation strict: close above EMA fast
|
||||
|
||||
if(InpUseHtfFilter && !(g_emaFastHtf[c] > g_emaSlowHtf[c]))
|
||||
return;
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
double sl = MathMin(lowA, lowB) - InpSlBufferPts * pt;
|
||||
double tp = FindPreviousResistance(firstBottom);
|
||||
if(tp <= ask + pt)
|
||||
return;
|
||||
if(sl >= ask - pt)
|
||||
return;
|
||||
|
||||
sl = NormalizeDouble(sl, digits);
|
||||
tp = NormalizeDouble(tp, digits);
|
||||
g_trade.Buy(InpLots, _Symbol, ask, sl, tp, "Double bottom");
|
||||
}
|
||||
|
||||
void TryEnterShortDoubleTop()
|
||||
{
|
||||
int firstTop = -1; // older
|
||||
int secondTop = -1; // newer
|
||||
double neckline = 0.0;
|
||||
double hiA = 0.0;
|
||||
double hiB = 0.0;
|
||||
|
||||
if(!FindDoubleTop(firstTop, secondTop, neckline, hiA, hiB))
|
||||
return;
|
||||
|
||||
const int c = 1;
|
||||
double close1 = iClose(_Symbol, InpTf, c);
|
||||
if(close1 >= neckline)
|
||||
return; // wait for neckline break confirmation
|
||||
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double stretched = hiB - g_emaFast[secondTop];
|
||||
if(stretched < InpMinEmaPriceDistPts * pt)
|
||||
return; // no enough EMA/price displacement for reversal
|
||||
|
||||
if(close1 >= g_emaFast[c])
|
||||
return; // keep confirmation strict: close below EMA fast
|
||||
|
||||
if(InpUseHtfFilter && !(g_emaFastHtf[c] < g_emaSlowHtf[c]))
|
||||
return;
|
||||
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
double sl = MathMax(hiA, hiB) + InpSlBufferPts * pt;
|
||||
double tp = FindPreviousSupport(firstTop);
|
||||
if(tp >= bid - pt)
|
||||
return;
|
||||
if(sl <= bid + pt)
|
||||
return;
|
||||
|
||||
sl = NormalizeDouble(sl, digits);
|
||||
tp = NormalizeDouble(tp, digits);
|
||||
g_trade.Sell(InpLots, _Symbol, bid, sl, tp, "Double top");
|
||||
}
|
||||
|
||||
bool FindDoubleBottom(int &firstBottom, int &secondBottom, double &neckline, double &lowA, double &lowB)
|
||||
{
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
int minShift = InpPivotRight + 1;
|
||||
int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2);
|
||||
if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2)
|
||||
return false;
|
||||
|
||||
for(int newer = minShift; newer <= maxShift; newer++)
|
||||
{
|
||||
if(!IsPivotLow(newer))
|
||||
continue;
|
||||
for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++)
|
||||
{
|
||||
int sep = older - newer;
|
||||
if(sep > InpMaxPatternSeparation)
|
||||
break;
|
||||
if(!IsPivotLow(older))
|
||||
continue;
|
||||
|
||||
double lNew = iLow(_Symbol, InpTf, newer);
|
||||
double lOld = iLow(_Symbol, InpTf, older);
|
||||
if(MathAbs(lNew - lOld) > InpTopBottomTolPts * pt)
|
||||
continue;
|
||||
|
||||
double neck = HighestHighBetween(newer, older);
|
||||
if(neck <= 0.0)
|
||||
continue;
|
||||
|
||||
firstBottom = older;
|
||||
secondBottom = newer;
|
||||
lowA = lOld;
|
||||
lowB = lNew;
|
||||
neckline = neck;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindDoubleTop(int &firstTop, int &secondTop, double &neckline, double &hiA, double &hiB)
|
||||
{
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
int minShift = InpPivotRight + 1;
|
||||
int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2);
|
||||
if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2)
|
||||
return false;
|
||||
|
||||
for(int newer = minShift; newer <= maxShift; newer++)
|
||||
{
|
||||
if(!IsPivotHigh(newer))
|
||||
continue;
|
||||
for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++)
|
||||
{
|
||||
int sep = older - newer;
|
||||
if(sep > InpMaxPatternSeparation)
|
||||
break;
|
||||
if(!IsPivotHigh(older))
|
||||
continue;
|
||||
|
||||
double hNew = iHigh(_Symbol, InpTf, newer);
|
||||
double hOld = iHigh(_Symbol, InpTf, older);
|
||||
if(MathAbs(hNew - hOld) > InpTopBottomTolPts * pt)
|
||||
continue;
|
||||
|
||||
double neck = LowestLowBetween(newer, older);
|
||||
if(neck <= 0.0)
|
||||
continue;
|
||||
|
||||
firstTop = older;
|
||||
secondTop = newer;
|
||||
hiA = hOld;
|
||||
hiB = hNew;
|
||||
neckline = neck;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsPivotLow(const int shift)
|
||||
{
|
||||
double v = iLow(_Symbol, InpTf, shift);
|
||||
for(int i = 1; i <= InpPivotLeft; i++)
|
||||
if(iLow(_Symbol, InpTf, shift + i) <= v) return false;
|
||||
for(int i = 1; i <= InpPivotRight; i++)
|
||||
if(iLow(_Symbol, InpTf, shift - i) < v) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPivotHigh(const int shift)
|
||||
{
|
||||
double v = iHigh(_Symbol, InpTf, shift);
|
||||
for(int i = 1; i <= InpPivotLeft; i++)
|
||||
if(iHigh(_Symbol, InpTf, shift + i) >= v) return false;
|
||||
for(int i = 1; i <= InpPivotRight; i++)
|
||||
if(iHigh(_Symbol, InpTf, shift - i) > v) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
double HighestHighBetween(const int shiftA, const int shiftB)
|
||||
{
|
||||
int from = MathMin(shiftA, shiftB);
|
||||
int to = MathMax(shiftA, shiftB);
|
||||
double v = -DBL_MAX;
|
||||
for(int i = from; i <= to; i++)
|
||||
v = MathMax(v, iHigh(_Symbol, InpTf, i));
|
||||
return v;
|
||||
}
|
||||
|
||||
double LowestLowBetween(const int shiftA, const int shiftB)
|
||||
{
|
||||
int from = MathMin(shiftA, shiftB);
|
||||
int to = MathMax(shiftA, shiftB);
|
||||
double v = DBL_MAX;
|
||||
for(int i = from; i <= to; i++)
|
||||
v = MathMin(v, iLow(_Symbol, InpTf, i));
|
||||
return v;
|
||||
}
|
||||
|
||||
double FindPreviousResistance(const int firstBottomShift)
|
||||
{
|
||||
int start = firstBottomShift + 1;
|
||||
int end = firstBottomShift + InpPrevLevelLookback;
|
||||
int bars = Bars(_Symbol, InpTf);
|
||||
end = MathMin(end, bars - 2);
|
||||
if(start > end)
|
||||
return 0.0;
|
||||
|
||||
double r = -DBL_MAX;
|
||||
for(int i = start; i <= end; i++)
|
||||
r = MathMax(r, iHigh(_Symbol, InpTf, i));
|
||||
return r;
|
||||
}
|
||||
|
||||
double FindPreviousSupport(const int firstTopShift)
|
||||
{
|
||||
int start = firstTopShift + 1;
|
||||
int end = firstTopShift + InpPrevLevelLookback;
|
||||
int bars = Bars(_Symbol, InpTf);
|
||||
end = MathMin(end, bars - 2);
|
||||
if(start > end)
|
||||
return 0.0;
|
||||
|
||||
double s = DBL_MAX;
|
||||
for(int i = start; i <= end; i++)
|
||||
s = MathMin(s, iLow(_Symbol, InpTf, i));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool PositionExistsForMagic()
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetTradeFillingBySymbol()
|
||||
{
|
||||
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||||
if((mask & SYMBOL_FILLING_IOC) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
else if((mask & SYMBOL_FILLING_FOK) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
else
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| rsi-dual-martingale-hybrid.mq5 |
|
||||
//| Two robots: RSI reversal martingale + RSI midpoint trend helper |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Lab"
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- core
|
||||
input ENUM_TIMEFRAMES InpTf = PERIOD_M5;
|
||||
input int InpRsiLen = 14;
|
||||
input double InpRsiOverbought = 70.0;
|
||||
input double InpRsiOversold = 30.0;
|
||||
input double InpRsiMid = 50.0;
|
||||
|
||||
//--- money
|
||||
input double InpBaseLot = 0.01;
|
||||
input int InpSlippagePts = 30;
|
||||
input ulong InpMagicBase = 2026042501;
|
||||
|
||||
//--- robot A: RSI reversal martingale
|
||||
input bool InpEnableReversalMartingale = true;
|
||||
input double InpMartingaleMult = 1.7;
|
||||
input int InpMartingaleStepPts = 300;
|
||||
input int InpMartingaleMaxLevels = 6;
|
||||
|
||||
//--- robot B: reverse martingale trend follow (RSI cross midpoint)
|
||||
input bool InpEnableTrendReverseMartingale = true;
|
||||
input double InpTrendPyramidMult = 1.5;
|
||||
input int InpTrendPyramidStepPts = 250;
|
||||
input int InpTrendMaxLevels = 5;
|
||||
|
||||
//--- rescue / coordination
|
||||
input bool InpEnableRescue = true;
|
||||
input double InpTroubleLossMoney = -8.0; // martingale basket in trouble below this
|
||||
input double InpRescueLotMult = 2.0; // base lot multiplier for rescue trade
|
||||
input int InpRescueCooldownBars = 3;
|
||||
|
||||
CTrade g_trade;
|
||||
int g_hRsi = INVALID_HANDLE;
|
||||
double g_rsi[];
|
||||
|
||||
datetime g_lastBar = 0;
|
||||
int g_lastRescueBarIndex = -1000000;
|
||||
|
||||
enum RobotDirection
|
||||
{
|
||||
DIR_NONE = 0,
|
||||
DIR_BUY = 1,
|
||||
DIR_SELL = -1
|
||||
};
|
||||
|
||||
// Magic map:
|
||||
// base + 1 : reversal martingale basket
|
||||
// base + 2 : trend reverse-martingale basket
|
||||
// base + 3 : rescue positions
|
||||
ulong MagicRev() { return InpMagicBase + 1; }
|
||||
ulong MagicTrend() { return InpMagicBase + 2; }
|
||||
ulong MagicRescue() { return InpMagicBase + 3; }
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_trade.SetDeviationInPoints(InpSlippagePts);
|
||||
SetTradeFillingBySymbol();
|
||||
|
||||
g_hRsi = iRSI(_Symbol, InpTf, InpRsiLen, PRICE_CLOSE);
|
||||
if(g_hRsi == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
ArraySetAsSeries(g_rsi, true);
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_hRsi != INVALID_HANDLE)
|
||||
IndicatorRelease(g_hRsi);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(CopyBuffer(g_hRsi, 0, 0, 10, g_rsi) < 10)
|
||||
return;
|
||||
|
||||
// Rescue management can run every tick.
|
||||
ManageRescueCoordination();
|
||||
|
||||
datetime t = iTime(_Symbol, InpTf, 0);
|
||||
if(t == g_lastBar)
|
||||
return;
|
||||
g_lastBar = t;
|
||||
|
||||
if(InpEnableReversalMartingale)
|
||||
RunReversalMartingale();
|
||||
|
||||
if(InpEnableTrendReverseMartingale)
|
||||
RunTrendReverseMartingale();
|
||||
}
|
||||
|
||||
void RunReversalMartingale()
|
||||
{
|
||||
ulong magic = MagicRev();
|
||||
int count = BasketCountByMagic(magic);
|
||||
double rsi1 = g_rsi[1];
|
||||
|
||||
// No fixed TP/SL: close reversal basket when mean-reversion reaches RSI midpoint.
|
||||
RobotDirection dir = BasketDirectionByMagic(magic);
|
||||
if(count > 0 &&
|
||||
((dir == DIR_BUY && rsi1 >= InpRsiMid) ||
|
||||
(dir == DIR_SELL && rsi1 <= InpRsiMid)))
|
||||
{
|
||||
CloseBasketByMagic(magic);
|
||||
return;
|
||||
}
|
||||
|
||||
double rsi2 = g_rsi[2];
|
||||
|
||||
if(count == 0)
|
||||
{
|
||||
if(rsi2 < InpRsiOversold && rsi1 > InpRsiOversold)
|
||||
{
|
||||
OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "REV start");
|
||||
return;
|
||||
}
|
||||
if(rsi2 > InpRsiOverbought && rsi1 < InpRsiOverbought)
|
||||
{
|
||||
OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "REV start");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(dir == DIR_NONE || count >= InpMartingaleMaxLevels)
|
||||
return;
|
||||
|
||||
double lastEntry = LastEntryPriceByMagic(magic);
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
|
||||
bool adverseEnough = false;
|
||||
if(dir == DIR_BUY)
|
||||
adverseEnough = (lastEntry - bid) >= (InpMartingaleStepPts * pt);
|
||||
else if(dir == DIR_SELL)
|
||||
adverseEnough = (ask - lastEntry) >= (InpMartingaleStepPts * pt);
|
||||
|
||||
if(!adverseEnough)
|
||||
return;
|
||||
|
||||
double lot = NormalizeVolume(InpBaseLot * MathPow(InpMartingaleMult, count));
|
||||
OpenMarketByDirection(magic, dir, lot, "REV scale");
|
||||
}
|
||||
|
||||
void RunTrendReverseMartingale()
|
||||
{
|
||||
ulong magic = MagicTrend();
|
||||
int count = BasketCountByMagic(magic);
|
||||
RobotDirection dir = BasketDirectionByMagic(magic);
|
||||
double basketProfit = BasketProfitByMagic(magic);
|
||||
double rsi1 = g_rsi[1];
|
||||
double rsi2 = g_rsi[2];
|
||||
|
||||
// No fixed TP/SL: close trend basket when RSI crosses back through midpoint.
|
||||
if(count > 0 &&
|
||||
((dir == DIR_BUY && rsi2 > InpRsiMid && rsi1 < InpRsiMid) ||
|
||||
(dir == DIR_SELL && rsi2 < InpRsiMid && rsi1 > InpRsiMid)))
|
||||
{
|
||||
CloseBasketByMagic(magic);
|
||||
return;
|
||||
}
|
||||
|
||||
if(count == 0)
|
||||
{
|
||||
if(rsi2 < InpRsiMid && rsi1 > InpRsiMid)
|
||||
{
|
||||
OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "TREND cross");
|
||||
return;
|
||||
}
|
||||
if(rsi2 > InpRsiMid && rsi1 < InpRsiMid)
|
||||
{
|
||||
OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "TREND cross");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(dir == DIR_NONE || count >= InpTrendMaxLevels)
|
||||
return;
|
||||
if(basketProfit <= 0.0)
|
||||
return; // reverse martingale: only add into winners
|
||||
|
||||
double lastEntry = LastEntryPriceByMagic(magic);
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
|
||||
bool favorableEnough = false;
|
||||
if(dir == DIR_BUY)
|
||||
favorableEnough = (bid - lastEntry) >= (InpTrendPyramidStepPts * pt);
|
||||
else if(dir == DIR_SELL)
|
||||
favorableEnough = (lastEntry - ask) >= (InpTrendPyramidStepPts * pt);
|
||||
|
||||
if(!favorableEnough)
|
||||
return;
|
||||
|
||||
double lot = NormalizeVolume(InpBaseLot * MathPow(InpTrendPyramidMult, count));
|
||||
OpenMarketByDirection(magic, dir, lot, "TREND add");
|
||||
}
|
||||
|
||||
void ManageRescueCoordination()
|
||||
{
|
||||
if(!InpEnableRescue)
|
||||
return;
|
||||
|
||||
ulong mRev = MagicRev();
|
||||
ulong mRes = MagicRescue();
|
||||
|
||||
double revProfit = BasketProfitByMagic(mRev);
|
||||
int revCount = BasketCountByMagic(mRev);
|
||||
|
||||
if(revCount == 0)
|
||||
{
|
||||
CloseBasketByMagic(mRes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 1: no fixed rescue TP/SL; close rescue on RSI midpoint recross against rescue direction.
|
||||
RobotDirection rescueDir = BasketDirectionByMagic(mRes);
|
||||
if(BasketCountByMagic(mRes) > 0 &&
|
||||
((rescueDir == DIR_BUY && g_rsi[2] > InpRsiMid && g_rsi[1] < InpRsiMid) ||
|
||||
(rescueDir == DIR_SELL && g_rsi[2] < InpRsiMid && g_rsi[1] > InpRsiMid)))
|
||||
{
|
||||
ulong worstTicket = WorstTicketByMagic(mRev);
|
||||
CloseBasketByMagic(mRes);
|
||||
if(worstTicket != 0)
|
||||
g_trade.PositionClose(worstTicket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 2: if martingale basket is in trouble, launch one trend-aligned rescue trade.
|
||||
if(revProfit > InpTroubleLossMoney)
|
||||
return;
|
||||
|
||||
if(BasketCountByMagic(mRes) > 0)
|
||||
return;
|
||||
|
||||
int barsNow = iBars(_Symbol, InpTf);
|
||||
if((barsNow - g_lastRescueBarIndex) < InpRescueCooldownBars)
|
||||
return;
|
||||
|
||||
RobotDirection helperDir = (g_rsi[1] >= InpRsiMid ? DIR_BUY : DIR_SELL);
|
||||
|
||||
// avoid adding rescue in same direction as losing reversal basket when RSI trend disagrees
|
||||
RobotDirection revDir = BasketDirectionByMagic(mRev);
|
||||
if(revDir == helperDir)
|
||||
helperDir = (helperDir == DIR_BUY ? DIR_SELL : DIR_BUY);
|
||||
|
||||
double lot = NormalizeVolume(InpBaseLot * InpRescueLotMult);
|
||||
if(OpenMarketByDirection(mRes, helperDir, lot, "RESCUE"))
|
||||
g_lastRescueBarIndex = barsNow;
|
||||
}
|
||||
|
||||
bool OpenMarketByDirection(const ulong magic, const RobotDirection dir, const double lot, const string comment)
|
||||
{
|
||||
if(dir == DIR_NONE || lot <= 0.0)
|
||||
return false;
|
||||
|
||||
g_trade.SetExpertMagicNumber(magic);
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(dir == DIR_BUY)
|
||||
return g_trade.Buy(lot, _Symbol, ask, 0.0, 0.0, comment);
|
||||
return g_trade.Sell(lot, _Symbol, bid, 0.0, 0.0, comment);
|
||||
}
|
||||
|
||||
int BasketCountByMagic(const ulong magic)
|
||||
{
|
||||
int n = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
double BasketProfitByMagic(const ulong magic)
|
||||
{
|
||||
double sum = 0.0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
sum += PositionGetDouble(POSITION_PROFIT);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
RobotDirection BasketDirectionByMagic(const ulong magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
long type = PositionGetInteger(POSITION_TYPE);
|
||||
return (type == POSITION_TYPE_BUY ? DIR_BUY : DIR_SELL);
|
||||
}
|
||||
return DIR_NONE;
|
||||
}
|
||||
|
||||
double LastEntryPriceByMagic(const ulong magic)
|
||||
{
|
||||
datetime newest = 0;
|
||||
double price = 0.0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
datetime t = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
if(t >= newest)
|
||||
{
|
||||
newest = t;
|
||||
price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
}
|
||||
}
|
||||
return price;
|
||||
}
|
||||
|
||||
ulong WorstTicketByMagic(const ulong magic)
|
||||
{
|
||||
double worstProfit = DBL_MAX;
|
||||
ulong worstTicket = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
double p = PositionGetDouble(POSITION_PROFIT);
|
||||
if(p < worstProfit)
|
||||
{
|
||||
worstProfit = p;
|
||||
worstTicket = ticket;
|
||||
}
|
||||
}
|
||||
return worstTicket;
|
||||
}
|
||||
|
||||
void CloseBasketByMagic(const ulong magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
||||
g_trade.PositionClose(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
double NormalizeVolume(const double volRaw)
|
||||
{
|
||||
double vMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||||
double vMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||||
double vStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||||
if(vStep <= 0.0)
|
||||
vStep = 0.01;
|
||||
|
||||
double v = MathMax(vMin, MathMin(vMax, volRaw));
|
||||
v = MathFloor(v / vStep) * vStep;
|
||||
int vd = 2;
|
||||
if(vStep < 0.01) vd = 3;
|
||||
if(vStep < 0.001) vd = 4;
|
||||
return NormalizeDouble(v, vd);
|
||||
}
|
||||
|
||||
void SetTradeFillingBySymbol()
|
||||
{
|
||||
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||||
if((mask & SYMBOL_FILLING_IOC) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
else if((mask & SYMBOL_FILLING_FOK) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
else
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
}
|
||||
Reference in New Issue
Block a user