UpDATE
This commit is contained in:
@@ -23,6 +23,14 @@ input double LotSize = 0.1; // Lot Size
|
||||
input int MagicNumber = 123459123; // 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;
|
||||
@@ -35,6 +43,12 @@ datetime last_bar_time = 0;
|
||||
bool rsi_against_position = false;
|
||||
int bars_against_count = 0;
|
||||
|
||||
void ResetPositionTracking();
|
||||
void SyncTrackedPosition();
|
||||
double ATRPriceOnTF(const int period);
|
||||
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr);
|
||||
void TryReversalEscape();
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -77,21 +91,38 @@ void OnTick()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if this is a new bar
|
||||
datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
bool is_new_bar = (current_bar_time != last_bar_time);
|
||||
bool in_position = position_open || PositionExistsByMagic(_Symbol, MagicNumber);
|
||||
|
||||
// While flat, process only on new bars. While in position, allow intrabar reversal escape checks.
|
||||
if(!in_position && !is_new_bar)
|
||||
{
|
||||
return; // Still the same bar, don't process
|
||||
return;
|
||||
}
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
|
||||
// Update RSI values
|
||||
if(!UpdateRSI())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_position && UseReversalEscape)
|
||||
{
|
||||
TryReversalEscape();
|
||||
}
|
||||
|
||||
if(!is_new_bar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Keep local tracking aligned with actual terminal positions for this symbol/magic.
|
||||
SyncTrackedPosition();
|
||||
|
||||
// Check for existing position
|
||||
CheckExistingPosition();
|
||||
@@ -120,6 +151,155 @@ bool UpdateRSI()
|
||||
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 rates[];
|
||||
if(CopyRates(_Symbol, TimeFrame, 0, 4, rates) >= 4)
|
||||
{
|
||||
ArraySetAsSeries(rates, true);
|
||||
const double body = MathAbs(rates[1].close - rates[1].open);
|
||||
if(body >= ReversalBodyAtrMult * atr)
|
||||
{
|
||||
if(ptype == POSITION_TYPE_BUY && rates[1].close < rates[1].open)
|
||||
signs++;
|
||||
else if(ptype == POSITION_TYPE_SELL && rates[1].close > rates[1].open)
|
||||
signs++;
|
||||
}
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(rates[1].close < rates[2].close && rates[2].close < rates[3].close)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rates[1].close > rates[2].close && rates[2].close > rates[3].close)
|
||||
signs++;
|
||||
}
|
||||
}
|
||||
|
||||
return signs;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Cut losers fast on violent reversals (evaluated every tick) |
|
||||
//+------------------------------------------------------------------+
|
||||
void TryReversalEscape()
|
||||
{
|
||||
ulong live_ticket = GetPositionTicketByMagic(_Symbol, MagicNumber);
|
||||
if(live_ticket == 0)
|
||||
return;
|
||||
if(!PositionSelectByTicketSymbolAndMagic(live_ticket, _Symbol, 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 signs = CountReversalEscapeSigns(ptype, atr);
|
||||
if(signs < ReversalSignsRequired)
|
||||
return;
|
||||
|
||||
ClosePosition();
|
||||
Print("RSIScalpingBTCUSD: reversal escape signs=", signs, " need=", ReversalSignsRequired,
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Reset local position tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
void ResetPositionTracking()
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Sync local state with real position in terminal |
|
||||
//+------------------------------------------------------------------+
|
||||
void SyncTrackedPosition()
|
||||
{
|
||||
ulong live_ticket = GetPositionTicketByMagic(_Symbol, MagicNumber);
|
||||
if(live_ticket == 0)
|
||||
{
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we were not tracking (or ticket changed), start tracking the live position.
|
||||
if(!position_open || position_ticket != (int)live_ticket)
|
||||
{
|
||||
if(PositionSelectByTicketSymbolAndMagic(live_ticket, _Symbol, MagicNumber))
|
||||
{
|
||||
position_open = true;
|
||||
position_ticket = (int)live_ticket;
|
||||
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check existing position for exit conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -133,10 +313,7 @@ void CheckExistingPosition()
|
||||
// Check if position still exists with correct magic number AND symbol for THIS EA
|
||||
if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,20 +485,24 @@ void OpenSellPosition()
|
||||
//+------------------------------------------------------------------+
|
||||
void ClosePosition()
|
||||
{
|
||||
bool position_exists_before_close = PositionExistsByMagic(_Symbol, MagicNumber);
|
||||
if(!position_exists_before_close)
|
||||
{
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close position using helper that verifies symbol AND magic number for THIS EA
|
||||
if(ClosePositionByMagic(trade, _Symbol, MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
ResetPositionTracking();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position doesn't exist or wrong magic number - reset tracking
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
// Keep tracking when close fails (e.g. market closed); retry on next bar.
|
||||
if(!PositionExistsByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
ResetPositionTracking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,15 @@ input double InpBreakBuffer = 110; // Break confirmation
|
||||
input double InpLots = 0.10; // Position size
|
||||
input long InpMagic = 26042501; // Magic number
|
||||
input bool InpDrawTrendline = true; // Draw detected trendline
|
||||
input bool InpUseSessionModeGate = true; // Block entries when symbol/session disallow opens
|
||||
input bool InpBypassGateInTester = true; // Ignore gate in Strategy Tester for optimization
|
||||
|
||||
CTrade trade;
|
||||
|
||||
int g_maHandle = INVALID_HANDLE;
|
||||
datetime g_lastBarTime = 0;
|
||||
string g_lineName = "SimpleTrendline_Basis";
|
||||
datetime g_lastEntryBlockLog = 0;
|
||||
|
||||
struct TrendlineModel
|
||||
{
|
||||
@@ -180,6 +183,75 @@ bool GetCurrentPosition(long &type, double &volume)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsWithinAnyTradeSession(const datetime nowServer)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(nowServer, dt);
|
||||
ENUM_DAY_OF_WEEK day = (ENUM_DAY_OF_WEEK)dt.day_of_week;
|
||||
int nowSec = dt.hour * 3600 + dt.min * 60 + dt.sec;
|
||||
|
||||
datetime from = 0;
|
||||
datetime to = 0;
|
||||
bool hasAny = false;
|
||||
for(uint idx = 0; idx < 16; idx++)
|
||||
{
|
||||
if(!SymbolInfoSessionTrade(_Symbol, day, idx, from, to))
|
||||
break;
|
||||
hasAny = true;
|
||||
// SymbolInfoSessionTrade returns session boundaries as time-of-day values.
|
||||
MqlDateTime fdt, tdt;
|
||||
TimeToStruct(from, fdt);
|
||||
TimeToStruct(to, tdt);
|
||||
int fromSec = fdt.hour * 3600 + fdt.min * 60 + fdt.sec;
|
||||
int toSec = tdt.hour * 3600 + tdt.min * 60 + tdt.sec;
|
||||
|
||||
// from==to on some brokers means full-day session.
|
||||
if(fromSec == toSec)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(fromSec < toSec)
|
||||
{
|
||||
if(nowSec >= fromSec && nowSec <= toSec)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Session passes midnight.
|
||||
if(nowSec >= fromSec || nowSec <= toSec)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If broker does not expose sessions for this symbol, do not block by session.
|
||||
if(!hasAny)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanOpenNewPositionNow(const ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
if(!InpUseSessionModeGate)
|
||||
return true;
|
||||
if(InpBypassGateInTester && (bool)MQLInfoInteger(MQL_TESTER))
|
||||
return true;
|
||||
|
||||
long tradeMode = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED ||
|
||||
tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
||||
return false;
|
||||
if(orderType == ORDER_TYPE_BUY &&
|
||||
tradeMode == SYMBOL_TRADE_MODE_SHORTONLY)
|
||||
return false;
|
||||
if(orderType == ORDER_TYPE_SELL &&
|
||||
tradeMode == SYMBOL_TRADE_MODE_LONGONLY)
|
||||
return false;
|
||||
|
||||
if(!IsWithinAnyTradeSession(TimeCurrent()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryExitOnBreak(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
@@ -234,6 +306,16 @@ void TryPullbackEntry(const TrendlineModel &m)
|
||||
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
if(!CanOpenNewPositionNow(ORDER_TYPE_BUY))
|
||||
{
|
||||
datetime nowBar = iTime(_Symbol, _Period, 0);
|
||||
if(nowBar != g_lastEntryBlockLog)
|
||||
{
|
||||
g_lastEntryBlockLog = nowBar;
|
||||
Print("Buy entry skipped: symbol mode/session does not allow opening now");
|
||||
}
|
||||
return;
|
||||
}
|
||||
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
|
||||
}
|
||||
}
|
||||
@@ -245,6 +327,16 @@ void TryPullbackEntry(const TrendlineModel &m)
|
||||
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
if(!CanOpenNewPositionNow(ORDER_TYPE_SELL))
|
||||
{
|
||||
datetime nowBar = iTime(_Symbol, _Period, 0);
|
||||
if(nowBar != g_lastEntryBlockLog)
|
||||
{
|
||||
g_lastEntryBlockLog = nowBar;
|
||||
Print("Sell entry skipped: symbol mode/session does not allow opening now");
|
||||
}
|
||||
return;
|
||||
}
|
||||
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user