turns-00083.parquet:26720
a31944396ecdf646abcff47c
turn 3/6gpt-4.1-mini-2025-04-14EnglishRomania4376 words
degenerate_repetitionAbsentFinal dense release
USER
//+------------------------------------------------------------------+
//| Break of Structure EA with floating risk cap (full, compile) |
//| Added ADX filter and Losing Streak filter |
//| Added cooldown per candle & minimum time interval cooldown |
//| Improved cooldown after close - scanning recent closed deals |
//| Added Fair Value Gap (FVG) filter for entry signals |
//+------------------------------------------------------------------+
#property copyright ""
#property version "1.6"
#property strict
#include <Trade\Trade.mqh>
input double inpLot = 0.05;
input bool useRiskPercent = true;
input double riskPercent = 1.0; // Position risk % per trade
input int sl_atr_mult = 3;
input double r2r_ratio = 2.0;
input ENUM_TIMEFRAMES volatilityTF = PERIOD_M5;
input int atrPeriod = 14;
input int swingLookback = 20;
input ENUM_TIMEFRAMES swingTF = PERIOD_M1;
// Shortest-term MA (existing)
input int maPeriod = 50;
input ENUM_TIMEFRAMES maTimeframe = PERIOD_M15;
// Medium-term MA (new)
input int maPeriod2 = 100;
input ENUM_TIMEFRAMES maTimeframe2 = PERIOD_M15;
// Long-term MA (new)
input int maPeriod3 = 200;
input ENUM_TIMEFRAMES maTimeframe3 = PERIOD_M15;
input int tradeStartHour = 7;
input int tradeEndHour = 22;
input int maxPositions = 1;
input uint inpMagicNumber = 123456;
input bool enableTrailingSL = true;
enum ENUM_TrailingStopMode
{
TrailingNone = 0,
TrailingMarketStructure = 1,
TrailingATR = 2,
TrailingHybrid = 3
};
input ENUM_TrailingStopMode trailingStopMode = TrailingHybrid;
input int ms_swingLookback = 20;
input double atr_trail_mult = 2.5;
input uint TrailUpdateIntervalSec = 15;
input double MinSLBufferATRMult = 1.0;
input double TrailingProfitTriggerPerc = 0.5;
input bool enableReverseEntries = true;
input double maxRiskPerTradePercent = 1.0; // Floating loss cap only!
input double maxDailyLossAmount = 350.0;
// --- ADX filter inputs ---
input int adxPeriod = 14; // ADX period
input double adxThreshold = 25.0; // ADX minimum threshold to allow trading
input ENUM_TIMEFRAMES adxTimeframe = PERIOD_M15; // ADX timeframe to use for filtering
// --- Losing streak inputs ---
input int maxLosingStreak = 3; // Max consecutive losing trades allowed
// --- POST-CLOSE COOLDOWN ---
input int postCloseCooldownSeconds = 180; // Cooldown seconds after a position closes before new trades allowed.
// --- Fair Value Gap filter input ---
input bool useFairValueGapFilter = true; // Enable Fair Value Gap filter for entries
CTrade trade;
int ma_handle = INVALID_HANDLE;
int ma_handle2 = INVALID_HANDLE; // Medium-term MA handle
int ma_handle3 = INVALID_HANDLE; // Long-term MA handle
int atr_handle = INVALID_HANDLE;
int adx_handle = INVALID_HANDLE; // ADX handle
string swingHighLineName = "SwingHighLine";
string swingLowLineName = "SwingLowLine";
struct TrailingUpdateInfo
{
ulong position_ticket;
datetime last_update;
};
TrailingUpdateInfo g_trailingUpdateInfo[];
static double g_initialEquity = 0.0;
static datetime g_lastResetDay = 0;
static int g_currentLosingStreak = 0;
static datetime g_lastLosingStreakResetDay = 0;
static ulong g_lastHistoryTickTime = 0;
// *** Cooldown globals ***
datetime lastTradeCandleTime = 0; // last candle time when trade opened
ENUM_TIMEFRAMES cooldownTimeframe = swingTF; // timeframe for candle cooldown
datetime lastTradeOpenTime = 0; // last trade open timestamp
int tradeIntervalSeconds = 180; // min seconds between trades
//+------------------------------------------------------------------+
void ResetDailyEquityIfNewDay()
{
datetime now = TimeCurrent();
MqlDateTime tm;
TimeToStruct(now, tm);
MqlDateTime todayMidnight = {};
todayMidnight.year = tm.year;
todayMidnight.mon = tm.mon;
todayMidnight.day = tm.day;
todayMidnight.hour = 0;
todayMidnight.min = 0;
todayMidnight.sec = 0;
datetime midnightTime = StructToTime(todayMidnight);
if(g_lastResetDay != midnightTime)
{
g_lastResetDay = midnightTime;
g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY);
PrintFormat("Daily equity reset at %s new initialEquity=%.2f", TimeToString(now, TIME_DATE|TIME_SECONDS), g_initialEquity);
}
}
//+------------------------------------------------------------------+
void ResetLosingStreakIfNewDay()
{
datetime now = TimeCurrent();
MqlDateTime tm;
TimeToStruct(now, tm);
MqlDateTime todayMidnight = {};
todayMidnight.year = tm.year;
todayMidnight.mon = tm.mon;
todayMidnight.day = tm.day;
todayMidnight.hour = 0;
todayMidnight.min = 0;
todayMidnight.sec = 0;
datetime midnightTime = StructToTime(todayMidnight);
if(g_lastLosingStreakResetDay != midnightTime)
{
g_lastLosingStreakResetDay = midnightTime;
g_currentLosingStreak = 0;
PrintFormat("Losing streak reset at new day %s", TimeToString(now, TIME_DATE|TIME_SECONDS));
}
}
//+------------------------------------------------------------------+
void UpdateLosingStreak()
{
datetime now = TimeCurrent();
if(!HistorySelect(now - 86400, now))
{
Print("HistorySelect failed");
return;
}
ulong maxClosedTime = 0;
int total = HistoryDealsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
datetime closeTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
if(closeTime <= g_lastHistoryTickTime)
continue;
maxClosedTime = MathMax(maxClosedTime, (ulong)closeTime);
string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
ulong magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
if(symbol != _Symbol || magic != inpMagicNumber)
continue;
long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entryType != DEAL_ENTRY_OUT)
continue;
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
if(profit < 0)
{
g_currentLosingStreak++;
PrintFormat("Losing trade detected (ticket %I64d, profit %.2f). Current losing streak: %d", ticket, profit, g_currentLosingStreak);
}
else
{
if(g_currentLosingStreak > 0)
PrintFormat("Winning trade detected (ticket %I64d, profit %.2f). Resetting losing streak.", ticket, profit);
g_currentLosingStreak = 0;
}
}
if(maxClosedTime > 0)
g_lastHistoryTickTime = maxClosedTime;
}
//+------------------------------------------------------------------+
double CalculateLotSize(double sl_distance_price)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
if(balance <= 0 || sl_distance_price <= 0)
return inpLot;
if(!useRiskPercent)
{
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lot = inpLot;
if(lot < min_lot) lot = min_lot;
if(lot > max_lot) lot = max_lot;
lot = MathFloor(lot / lot_step) * lot_step;
return lot;
}
else
{
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
if(tick_value <= 0 || tick_size <= 0 || contract_size <= 0)
return inpLot;
double point_value = tick_value / tick_size;
double calculatedLot = (balance * (riskPercent / 100.0)) / (sl_distance_price * point_value * contract_size);
if(calculatedLot <= 0)
return inpLot;
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lot = MathFloor(calculatedLot / lot_step) * lot_step;
if(lot < min_lot) lot = min_lot;
if(lot > max_lot) lot = max_lot;
return lot;
}
}
//+------------------------------------------------------------------+
bool IsPositionOpen(ENUM_POSITION_TYPE type)
{
int total = PositionsTotal();
for(int i = 0; i < total; i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
{
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(pos_type == type)
return true;
}
}
}
return false;
}
//+------------------------------------------------------------------+
bool CanUpdateTrailing(ulong ticket)
{
datetime now = TimeCurrent();
int count = ArraySize(g_trailingUpdateInfo);
for(int i=0; i<count; i++)
{
if(g_trailingUpdateInfo[i].position_ticket == ticket)
{
if(now - g_trailingUpdateInfo[i].last_update < (int)TrailUpdateIntervalSec)
return false;
g_trailingUpdateInfo[i].last_update = now;
return true;
}
}
ArrayResize(g_trailingUpdateInfo, count + 1);
g_trailingUpdateInfo[count].position_ticket = ticket;
g_trailingUpdateInfo[count].last_update = now;
return true;
}
//+------------------------------------------------------------------+
void UpdateTrailingStopByMarketStructure(ulong position_ticket)
{
if(!PositionSelectByTicket(position_ticket))
return;
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double current_sl = PositionGetDouble(POSITION_SL);
double current_tp = PositionGetDouble(POSITION_TP);
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
string symbol = PositionGetString(POSITION_SYMBOL);
if(current_tp <= 0)
return;
if(!CanUpdateTrailing(position_ticket))
return;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
if(bid <= 0 || ask <= 0)
return;
if(pos_type == POSITION_TYPE_BUY)
{
double half_tp_level = entry_price + 0.5 * (current_tp - entry_price);
if(bid < half_tp_level)
return;
int lowestBar = iLowest(symbol, PERIOD_CURRENT, MODE_LOW, ms_swingLookback, 0);
if(lowestBar < 0)
return;
double swing_low = iLow(symbol, PERIOD_CURRENT, lowestBar);
double buffer = 2 * point;
double new_sl = NormalizeDouble(swing_low - buffer, digits);
if(new_sl > current_sl && new_sl < entry_price)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = position_ticket;
req.symbol = symbol;
req.sl = new_sl;
req.tp = current_tp;
req.deviation = 10;
if(!OrderSend(req, res))
PrintFormat("Failed to update trailing SL for position %I64d, error %d", position_ticket, GetLastError());
else if(res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
else
PrintFormat("Trailing SL moved up for position %I64d: new SL=%.5f", position_ticket, new_sl);
}
}
else if(pos_type == POSITION_TYPE_SELL)
{
double half_tp_level = entry_price - 0.5 * (entry_price - current_tp);
if(ask > half_tp_level)
return;
int highestBar = iHighest(symbol, PERIOD_CURRENT, MODE_HIGH, ms_swingLookback, 0);
if(highestBar < 0)
return;
double swing_high = iHigh(symbol, PERIOD_CURRENT, highestBar);
double buffer = 2 * point;
double new_sl = NormalizeDouble(swing_high + buffer, digits);
if(new_sl < current_sl && new_sl > entry_price)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = position_ticket;
req.symbol = symbol;
req.sl = new_sl;
req.tp = current_tp;
req.deviation = 10;
if(!OrderSend(req, res))
PrintFormat("Failed to update trailing SL for position %I64d, error %d", position_ticket, GetLastError());
else if(res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
else
PrintFormat("Trailing SL moved down for position %I64d: new SL=%.5f", position_ticket, new_sl);
}
}
}
//+------------------------------------------------------------------+
void UpdateTrailingStopByATR(ulong position_ticket)
{
if(!PositionSelectByTicket(position_ticket))
return;
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double current_sl = PositionGetDouble(POSITION_SL);
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
string symbol = PositionGetString(POSITION_SYMBOL);
if(!CanUpdateTrailing(position_ticket))
return;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double atr_buf[];
if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
return;
double atr = atr_buf[0];
if(atr <= 0)
return;
double current_price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID)
: SymbolInfoDouble(symbol, SYMBOL_ASK);
if(current_price <= 0) return;
double new_sl = 0;
if(pos_type == POSITION_TYPE_BUY)
{
new_sl = NormalizeDouble(current_price - atr * atr_trail_mult, digits);
double min_sl_dist = atr * MinSLBufferATRMult;
if(new_sl < (current_price - min_sl_dist))
new_sl = current_price - min_sl_dist;
if(new_sl > current_sl && new_sl < current_price)
{
MqlTradeRequest req={};
MqlTradeResult res={};
req.action = TRADE_ACTION_SLTP;
req.position = position_ticket;
req.symbol = symbol;
req.sl = new_sl;
req.tp = PositionGetDouble(POSITION_TP);
req.deviation = 10;
if(!OrderSend(req, res))
PrintFormat("ATR trailing SL update failed for position %I64d, error %d", position_ticket, GetLastError());
else if(res.retcode != TRADE_RETCODE_DONE)
PrintFormat("ATR trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
else
PrintFormat("ATR trailing SL moved up for position %I64d: new SL=%.5f", position_ticket, new_sl);
}
}
else if(pos_type == POSITION_TYPE_SELL)
{
new_sl = NormalizeDouble(current_price + atr * atr_trail_mult, digits);
double min_sl_dist = atr * MinSLBufferATRMult;
if(new_sl > (current_price + min_sl_dist))
new_sl = current_price + min_sl_dist;
if(new_sl < current_sl && new_sl > current_price)
{
MqlTradeRequest req={};
MqlTradeResult res={};
req.action = TRADE_ACTION_SLTP;
req.position = position_ticket;
req.symbol = symbol;
req.sl = new_sl;
req.tp = PositionGetDouble(POSITION_TP);
req.deviation = 10;
if(!OrderSend(req, res))
PrintFormat("ATR trailing SL update failed for position %I64d, error %d", position_ticket, GetLastError());
else if(res.retcode != TRADE_RETCODE_DONE)
PrintFormat("ATR trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
else
PrintFormat("ATR trailing SL moved down for position %I64d: new SL=%.5f", position_ticket, new_sl);
}
}
}
//+------------------------------------------------------------------+
void UpdateTrailingStopHybrid(ulong position_ticket)
{
if(!PositionSelectByTicket(position_ticket))
return;
if(!CanUpdateTrailing(position_ticket))
return;
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
string symbol = PositionGetString(POSITION_SYMBOL);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double atr_buf[];
if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
return;
double atr = atr_buf[0];
if(atr <= 0)
return;
double min_sl_distance = atr * MinSLBufferATRMult;
double initial_sl_dist = MathAbs(entry_price - sl);
if(initial_sl_dist <= 0) initial_sl_dist = atr * sl_atr_mult;
double current_price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID)
: SymbolInfoDouble(symbol, SYMBOL_ASK);
if(current_price <= 0) return;
double move_in_profit = (pos_type == POSITION_TYPE_BUY) ? (current_price - entry_price) : (entry_price - current_price);
if(move_in_profit < TrailingProfitTriggerPerc * initial_sl_dist)
return;
double ms_new_sl = 0;
bool ms_sl_valid = false;
if(pos_type == POSITION_TYPE_BUY)
{
int lowestBar = iLowest(symbol, PERIOD_CURRENT, MODE_LOW, ms_swingLookback, 0);
if(lowestBar >= 0)
{
double swing_low = iLow(symbol, PERIOD_CURRENT, lowestBar);
ms_new_sl = NormalizeDouble(swing_low - 2*point, digits);
ms_sl_valid = (ms_new_sl > sl) && (ms_new_sl < entry_price);
}
}
else if(pos_type == POSITION_TYPE_SELL)
{
int highestBar = iHighest(symbol, PERIOD_CURRENT, MODE_HIGH, ms_swingLookback, 0);
if(highestBar >= 0)
{
double swing_high = iHigh(symbol, PERIOD_CURRENT, highestBar);
ms_new_sl = NormalizeDouble(swing_high + 2*point, digits);
ms_sl_valid = (ms_new_sl < sl) && (ms_new_sl > entry_price);
}
}
double atr_new_sl = 0;
if(pos_type == POSITION_TYPE_BUY)
{
atr_new_sl = NormalizeDouble(current_price - atr * atr_trail_mult, digits);
atr_new_sl = MathMax(atr_new_sl, current_price - min_sl_distance);
atr_new_sl = (atr_new_sl > sl && atr_new_sl < current_price) ? atr_new_sl : 0;
}
else if(pos_type == POSITION_TYPE_SELL)
{
atr_new_sl = NormalizeDouble(current_price + atr * atr_trail_mult, digits);
atr_new_sl = MathMin(atr_new_sl, current_price + min_sl_distance);
atr_new_sl = (atr_new_sl < sl && atr_new_sl > current_price) ? atr_new_sl : 0;
}
double new_sl = 0;
if(ms_sl_valid && atr_new_sl > 0)
{
if(pos_type == POSITION_TYPE_BUY)
new_sl = MathMax(ms_new_sl, atr_new_sl);
else
new_sl = MathMin(ms_new_sl, atr_new_sl);
}
else if(ms_sl_valid)
new_sl = ms_new_sl;
else if(atr_new_sl > 0)
new_sl = atr_new_sl;
else
return;
bool update_sl = false;
if(pos_type == POSITION_TYPE_BUY)
update_sl = (new_sl > sl && new_sl < current_price);
else
update_sl = (new_sl < sl && new_sl > current_price);
if(update_sl)
{
MqlTradeRequest req={};
MqlTradeResult res={};
req.action = TRADE_ACTION_SLTP;
req.position = position_ticket;
req.symbol = symbol;
req.sl = new_sl;
req.tp = tp;
req.deviation = 10;
if(!OrderSend(req,res))
PrintFormat("Trailing SL update failed for position %I64d, err=%d", position_ticket, GetLastError());
else if(res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Trailing SL update rejected for position %I64d, retcode=%d", position_ticket, res.retcode);
else
PrintFormat("Trailing SL updated for position %I64d: old SL=%.5f new SL=%.5f", position_ticket, sl, new_sl);
}
}
//+------------------------------------------------------------------+
//-- Fair Value Gap detection function --
// Returns:
// 1 if bullish FVG detected (Candle1 High < Candle3 Low)
// -1 if bearish FVG detected (Candle1 Low > Candle3 High)
// 0 if no gap or error
int DetectFairValueGap(ENUM_TIMEFRAMES timeframe, int startShift)
{
MqlRates bars[3];
// Copy 3 bars starting from startShift, i.e. bars[0] = bar at startShift
if(CopyRates(_Symbol, timeframe, startShift, 3, bars) != 3)
{
Print("DetectFairValueGap: failed to copy 3 bars");
return 0;
}
// Candle1 = bars[0], Candle2 = bars[1], Candle3 = bars[2]
double c1_high = bars[0].high;
double c1_low = bars[0].low;
double c3_high = bars[2].high;
double c3_low = bars[2].low;
// Bullish FVG: Candle1 High < Candle3 Low
if(c1_high < c3_low)
{
double gapBull = c3_low - c1_high;
PrintFormat("Bullish Fair Value Gap detected between candle1 high %.5f and candle3 low %.5f (Gap %.5f)", c1_high, c3_low, gapBull);
return 1;
}
// Bearish FVG: Candle1 Low > Candle3 High
if(c1_low > c3_high)
{
double gapBear = c1_low - c3_high;
PrintFormat("Bearish Fair Value Gap detected between candle1 low %.5f and candle3 high %.5f (Gap %.5f)", c1_low, c3_high, gapBear);
return -1;
}
return 0;
}
//+------------------------------------------------------------------+
int OnInit()
{
ma_handle = iMA(_Symbol, maTimeframe, maPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(ma_handle == INVALID_HANDLE)
{
Print("Failed to create EMA handle");
return INIT_FAILED;
}
ma_handle2 = iMA(_Symbol, maTimeframe2, maPeriod2, 0, MODE_EMA, PRICE_CLOSE);
if(ma_handle2 == INVALID_HANDLE)
{
Print("Failed to create medium-term EMA handle");
IndicatorRelease(ma_handle);
return INIT_FAILED;
}
ma_handle3 = iMA(_Symbol, maTimeframe3, maPeriod3, 0, MODE_EMA, PRICE_CLOSE);
if(ma_handle3 == INVALID_HANDLE)
{
Print("Failed to create long-term EMA handle");
IndicatorRelease(ma_handle);
IndicatorRelease(ma_handle2);
return INIT_FAILED;
}
atr_handle = iATR(_Symbol, volatilityTF, atrPeriod);
if(atr_handle == INVALID_HANDLE)
{
Print("Failed to create ATR handle");
IndicatorRelease(ma_handle);
IndicatorRelease(ma_handle2);
IndicatorRelease(ma_handle3);
return INIT_FAILED;
}
adx_handle = iADX(_Symbol, adxTimeframe, adxPeriod);
if(adx_handle == INVALID_HANDLE)
{
Print("Failed to create ADX handle");
IndicatorRelease(ma_handle);
IndicatorRelease(ma_handle2);
IndicatorRelease(ma_handle3);
IndicatorRelease(atr_handle);
return INIT_FAILED;
}
if(!ObjectCreate(0, swingHighLineName, OBJ_HLINE, 0, TimeCurrent(), 0))
{
Print("Failed to create swing high line");
IndicatorRelease(ma_handle);
IndicatorRelease(ma_handle2);
IndicatorRelease(ma_handle3);
IndicatorRelease(atr_handle);
IndicatorRelease(adx_handle);
return INIT_FAILED;
}
ObjectSetInteger(0, swingHighLineName, OBJPROP_COLOR, clrGreen);
ObjectSetInteger(0, swingHighLineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, swingHighLineName, OBJPROP_BACK, true);
ObjectSetInteger(0, swingHighLineName, OBJPROP_RAY_RIGHT, true);
if(!ObjectCreate(0, swingLowLineName, OBJ_HLINE, 0, TimeCurrent(), 0))
{
Print("Failed to create swing low line");
ObjectDelete(0, swingHighLineName);
IndicatorRelease(ma_handle);
IndicatorRelease(ma_handle2);
IndicatorRelease(ma_handle3);
IndicatorRelease(atr_handle);
IndicatorRelease(adx_handle);
return INIT_FAILED;
}
ObjectSetInteger(0, swingLowLineName, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, swingLowLineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, swingLowLineName, OBJPROP_BACK, true);
ObjectSetInteger(0, swingLowLineName, OBJPROP_RAY_RIGHT, true);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ObjectDelete(0, swingHighLineName);
ObjectDelete(0, swingLowLineName);
if(ma_handle != INVALID_HANDLE)
{
IndicatorRelease(ma_handle);
ma_handle = INVALID_HANDLE;
}
if(ma_handle2 != INVALID_HANDLE)
{
IndicatorRelease(ma_handle2);
ma_handle2 = INVALID_HANDLE;
}
if(ma_handle3 != INVALID_HANDLE)
{
IndicatorRelease(ma_handle3);
ma_handle3 = INVALID_HANDLE;
}
if(atr_handle != INVALID_HANDLE)
{
IndicatorRelease(atr_handle);
atr_handle = INVALID_HANDLE;
}
if(adx_handle != INVALID_HANDLE)
{
IndicatorRelease(adx_handle);
adx_handle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
// Static variables to throttle print frequency
static datetime lastFvgNoTradePrintCandle = 0;
static datetime lastAdxBelowThresholdPrintCandle = 0;
static datetime lastPostCloseCooldownPrintTime = 0;
void OnTick()
{
datetime now = TimeCurrent();
// Post-close cooldown message throttled (once every 60s)
if(postCloseCooldownSeconds > 0 && IsRecentPositionClose(postCloseCooldownSeconds))
{
if (now - lastPostCloseCooldownPrintTime >= 60)
{
PrintFormat("Post-close cooldown active, skipping trades at %s", TimeToString(now));
lastPostCloseCooldownPrintTime = now;
}
return;
}
else
{
lastPostCloseCooldownPrintTime = 0;
}
ResetDailyEquityIfNewDay();
ResetLosingStreakIfNewDay();
UpdateLosingStreak();
if(maxLosingStreak > 0 && g_currentLosingStreak >= maxLosingStreak)
{
PrintFormat("Current losing streak %d >= max allowed %d. No new trades allowed now.", g_currentLosingStreak, maxLosingStreak);
return;
}
if(g_initialEquity == 0.0)
g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY);
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
double equityDrawdown = g_initialEquity - currentEquity;
if(equityDrawdown >= maxDailyLossAmount)
{
PrintFormat("Max combined daily loss $%.2f exceeded (drawdown=%.2f). No new trades allowed.", maxDailyLossAmount, equityDrawdown);
return;
}
//--- FLOATING RISK CLOSE LOGIC ---
if(maxRiskPerTradePercent > 0.0)
{
int total = PositionsTotal();
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double maxLossMoney = balance * (maxRiskPerTradePercent / 100.0);
for(int i=total-1; i>=0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber &&
PositionGetString(POSITION_SYMBOL) == _Symbol)
{
double currentProfit = PositionGetDouble(POSITION_PROFIT);
if(currentProfit < 0 && MathAbs(currentProfit) >= maxLossMoney)
{
PrintFormat("Closing position %I64d as floating loss reached %.2f (cap is %.2f)",
ticket, MathAbs(currentProfit), maxLossMoney);
trade.PositionClose(ticket);
}
}
}
}
}
//--- TRAILING SL ---
if(enableTrailingSL && trailingStopMode != TrailingNone)
{
int total = PositionsTotal();
for(int i=0; i<total; i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
{
if(trailingStopMode == TrailingMarketStructure)
UpdateTrailingStopByMarketStructure(ticket);
else if(trailingStopMode == TrailingATR)
UpdateTrailingStopByATR(ticket);
else if(trailingStopMode == TrailingHybrid)
UpdateTrailingStopHybrid(ticket);
}
}
}
}
//--- POSITION LIMIT CHECK ---
int openPosCount = 0, totalPositions = PositionsTotal();
for(int i=0; i<totalPositions; i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
{
openPosCount++;
}
}
}
if(maxPositions > 0 && openPosCount >= maxPositions)
return;
//--- TRADING TIME ---
MqlDateTime tm;
TimeToStruct(TimeCurrent(), tm);
if(tm.hour < tradeStartHour || tm.hour >= tradeEndHour)
return;
// --- MINIMUM TIME INTERVAL COOLDOWN CHECK ---
if(lastTradeOpenTime != 0 && (now - lastTradeOpenTime) < tradeIntervalSeconds)
return; // interval cooldown active, skip trade
// --- COOLDOWN PER CANDLE CHECK ---
MqlRates candleRates[];
if(CopyRates(_Symbol, cooldownTimeframe, 1, 1, candleRates) != 1)
{
Print("Failed to get last closed candle data for cooldown check");
return;
}
datetime currentCandleTime = candleRates[0].time;
if(currentCandleTime == lastTradeCandleTime)
return; // candle cooldown active, skip trade
//--- INDICATORS ---
double ma_buf1[], ma_buf2[], ma_buf3[];
if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf1) <= 0) return;
if(CopyBuffer(ma_handle2, 0, 1, 1, ma_buf2) <= 0) return;
if(CopyBuffer(ma_handle3, 0, 1, 1, ma_buf3) <= 0) return;
double MA1 = ma_buf1[0];
double MA2 = ma_buf2[0];
double MA3 = ma_buf3[0];
double atr_buf[];
if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
return;
double atr = atr_buf[0];
if(atr <= 0)
atr = 10.0 * _Point;
double adx_buf[];
if(adx_handle == INVALID_HANDLE)
{
Print("ADX handle invalid");
return;
}
if(CopyBuffer(adx_handle, 0, 1, 1, adx_buf) <= 0)
{
Print("Failed to copy ADX buffer");
return;
}
double adx_value = adx_buf[0];
// ADX below threshold message throttled (once per candle)
if(adx_value < adxThreshold)
{
if(currentCandleTime != lastAdxBelowThresholdPrintCandle)
{
PrintFormat("ADX=%.2f below threshold %.2f, skipping new entries", adx_value, adxThreshold);
lastAdxBelowThresholdPrintCandle = currentCandleTime;
}
return; // skip trade entries when ADX too low
}
else
{
lastAdxBelowThresholdPrintCandle = 0;
}
double sl_dist = atr * sl_atr_mult;
double tp_dist = sl_dist * r2r_ratio;
int highestBar = iHighest(_Symbol, swingTF, MODE_HIGH, swingLookback, 1);
int lowestBar = iLowest(_Symbol, swingTF, MODE_LOW, swingLookback, 1);
if(highestBar < 0 || lowestBar < 0)
return;
double lastSwingHigh = iHigh(_Symbol, swingTF, highestBar);
double lastSwingLow = iLow(_Symbol, swingTF, lowestBar);
ObjectSetDouble(0, swingHighLineName, OBJPROP_PRICE, lastSwingHigh);
ObjectSetDouble(0, swingLowLineName, OBJPROP_PRICE, lastSwingLow);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0 || bid <= 0)
return;
bool is_uptrend = (MA1 > MA2) && (MA2 > MA3) && (ask > MA2);
bool is_downtrend = (MA1 < MA2) && (MA2 < MA3) && (ask < MA2);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double lot = CalculateLotSize(sl_dist);
if(lot < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
return;
// --- Fair Value Gap detection ---
int fvgSignal = 0;
if(useFairValueGapFilter)
{
// Using shift=1 means last fully formed candles excluding current candle
fvgSignal = DetectFairValueGap(swingTF, 1);
if(fvgSignal == 0)
{
if(currentCandleTime != lastFvgNoTradePrintCandle)
{
Print("No Fair Value Gap detected, skipping new entries per FVG filter.");
lastFvgNoTradePrintCandle = currentCandleTime;
}
return; // Skip trades if no FVG detected and filter enabled
}
else
{
lastFvgNoTradePrintCandle = 0;
}
}
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.symbol = _Symbol;
request.volume = lot;
request.deviation = 10;
request.magic = inpMagicNumber;
if(!enableReverseEntries)
{
if(ask > lastSwingHigh && is_uptrend && !IsPositionOpen(POSITION_TYPE_BUY))
{
if(!useFairValueGapFilter || fvgSignal == 1)
{
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_BUY;
request.price = ask;
request.sl = NormalizeDouble(ask - sl_dist, digits);
request.tp = NormalizeDouble(ask + tp_dist, digits);
if(!OrderSend(request, result))
PrintFormat("Buy order send failed, error %d", GetLastError());
else if(result.retcode != TRADE_RETCODE_DONE)
PrintFormat("Buy order rejected, retcode %d", result.retcode);
else
{
PrintFormat("Buy opened @ %.5f lot %.2f SL %.5f TP %.5f", ask, lot, request.sl, request.tp);
lastTradeCandleTime = currentCandleTime;
lastTradeOpenTime = now;
}
}
}
else if(bid < lastSwingLow && is_downtrend && !IsPositionOpen(POSITION_TYPE_SELL))
{
if(!useFairValueGapFilter || fvgSignal == -1)
{
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_SELL;
request.price = bid;
request.sl = NormalizeDouble(bid + sl_dist, digits);
request.tp = NormalizeDouble(bid - tp_dist, digits);
if(!OrderSend(request, result))
PrintFormat("Sell order send failed, error %d", GetLastError());
else if(result.retcode != TRADE_RETCODE_DONE)
PrintFormat("Sell order rejected, retcode %d", result.retcode);
else
{
PrintFormat("Sell opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
lastTradeCandleTime = currentCandleTime;
lastTradeOpenTime = now;
}
}
}
}
else
{
if(ask > lastSwingHigh && is_downtrend && !IsPositionOpen(POSITION_TYPE_SELL))
{
if(!useFairValueGapFilter || fvgSignal == -1)
{
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_SELL;
request.price = bid;
request.sl = NormalizeDouble(bid + sl_dist, digits);
request.tp = NormalizeDouble(bid - tp_dist, digits);
if(!OrderSend(request, result))
PrintFormat("Reverse Sell order send failed, error %d", GetLastError());
else if(result.retcode != TRADE_RETCODE_DONE)
PrintFormat("Reverse Sell order rejected, retcode %d", result.retcode);
else
{
PrintFormat("Reverse Sell opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
lastTradeCandleTime = currentCandleTime;
lastTradeOpenTime = now;
}
}
}
else if(bid < lastSwingLow && is_uptrend && !IsPositionOpen(POSITION_TYPE_BUY))
{
if(!useFairValueGapFilter || fvgSignal == 1)
{
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_BUY;
request.price = ask;
request.sl = NormalizeDouble(ask - sl_dist, digits);
request.tp = NormalizeDouble(ask + tp_dist, digits);
if(!OrderSend(request, result))
PrintFormat("Reverse Buy order send failed, error %d", GetLastError());
else if(result.retcode != TRADE_RETCODE_DONE)
PrintFormat("Reverse Buy order rejected, retcode %d", result.retcode);
else
{
PrintFormat("Reverse Buy opened @ %.5f lot %.2f SL %.5f TP %.5f", ask, lot, request.sl, request.tp);
lastTradeCandleTime = currentCandleTime;
lastTradeOpenTime = now;
}
}
}
}
}
//+------------------------------------------------------------------+
// IsRecentPositionClose function (scans recent closed position deals)
bool IsRecentPositionClose(uint cooldownSecs)
{
datetime now = TimeCurrent();
if(!HistorySelect(now - cooldownSecs - 10, now))
{
Print("HistorySelect failed");
return false;
}
int totalDeals = HistoryDealsTotal();
for(int i = totalDeals - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket == 0)
continue;
datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
if(dealTime == 0 || (now - dealTime) > cooldownSecs)
continue;
string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
ulong magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(symbol == _Symbol && magic == inpMagicNumber && entry == DEAL_ENTRY_OUT)
{
PrintFormat("Recent position close detected at %s, cooldown active.", TimeToString(dealTime));
return true;
}
}
return false;
}
//+------------------------------------------------------------------+ASSISTANT
Here is a detailed comparison and summary of differences between the **second EA version (v1.6)** you just posted and the **first EA version (v1.7)** you posted earlier:
---
### 1. **Version and Header Comments:**
- **First version**: `#property version "1.7"` and a more detailed header with reverse mode waiting FVG logic, added cooldown improvements, more comments.
- **Second version**: `#property version "1.6"` with a simpler header; it lacks some newer comments about reverse mode and cooldown improvements.
---
### 2. **Reverse Entry Logic:**
- **First (v1.7) version**:
Uses **reverse break flags** `reverseSellBreakOccurred` and `reverseBuyBreakOccurred` plus timestamps to wait for **Fair Value Gap (FVG) confirmation** after break before placing reverse trades.
- Detects reverse break (e.g. downtrend + ask > lastSwingHigh) → sets flag.
- Waits for matching FVG signal (bearish for sell, bullish for buy) **after** break.
- Opens reverse trade only after FVG appears post-break.
- Contains logic to reset flags if conditions invalidate.
- **Reverse mode fully controls entries** — exits `OnTick()` after reverse entries logic.
- **Second (v1.6) version**:
Simpler **reverse entries logic without flags**. It directly checks if:
- `enableReverseEntries` is true:
- If price breaks above swing high in **downtrend**, then open SELL immediately if FVG signal bearish or no FVG filter.
- If price breaks below swing low in **uptrend**, open BUY immediately if bullish FVG/no filter.
- No waiting state or flags, trades placed instantly on signal if conditions met.
- Reverse mode does **not** short-circuit processing, all logic contained inside if/else blocks.
**Summary:**
Version 1.7 introduces a more sophisticated **flag-based waiting system for reverse trades** that waits for FVG confirmation *after* break, whereas version 1.6 opens reverse trades immediately upon break + FVG signal without waiting state flags.
---
### 3. **Fair Value Gap (FVG) filter usage:**
- **Version 1.7**:
In **normal mode (non-reverse)**, skips entries if no FVG detected but does *not* fully skip in reverse mode (as waiting flag logic handles that). Prints log "No FVG, skipping new entries" throttled once per candle.
- **Version 1.6**:
In both normal and reverse modes, if no FVG is detected and filter enabled, it returns early (skips trading). No special handling for reverse waiting flags here.
---
### 4. **Trailing Stop Updates:**
Both versions have identical trailing stop functions:
`UpdateTrailingStopByMarketStructure()`, `UpdateTrailingStopByATR()`, and `UpdateTrailingStopHybrid()`. No difference detected here.
---
### 5. **Cooldown / Delay Management:**
Both versions check:
- Post-close cooldown (`IsRecentPositionClose()`) with throttled logging.
- Minimum time interval cooldown (`lastTradeOpenTime` and `tradeIntervalSeconds`).
- Per candle cooldown (`lastTradeCandleTime`).
No major code differences in this aspect between versions.
---
### 6. **ADX Filter Behavior:**
Both versions apply the ADX filter strictly same way:
- Skip trade if ADX below threshold.
- Print message throttled once per candle.
---
### 7. **Position Opening / Closing Conditions:**
- **Version 1.7**:
More advanced position checks, including floating risk close (positions closed if losing beyond maxRiskPerTradePercent)—same for v1.6.
- **Order sending difference in reverse mode:**
In v1.7, reverse orders sent only after waiting for FVG post break (with flags).
In v1.6, reverse orders sent immediately if conditions met.
---
### 8. **Globals / Flags:**
- **Version 1.7**:
Defines two global flags and timestamps:
```mql
static bool reverseSellBreakOccurred = false;
static datetime reverseSellBreakTime = 0;
static bool reverseBuyBreakOccurred = false;
static datetime reverseBuyBreakTime = 0;
```
- **Version 1.6**:
These flags are missing.
---
### 9. **Other minor differences:**
- **Version 1.7** uses `#property version "1.7"`, includes additional comments on reverse mode waiting & cooldown improvements.
- **Version 1.6** has fewer comments and no explicit reverse waiting flags.
---
## **Summary Table:**
| Feature | Version 1.7 (Newer) | Version 1.6 (Older) |
|--------------------------------|-------------------------------------------------|-----------------------------------------------|
| Reverse Mode Entry Logic | Uses flags to detect reverse break and waits for FVG confirmation before trade | Immediate entry on break + FVG, no wait flags |
| Fair Value Gap Filter | Skips entries if no FVG in normal mode; reverse mode waits for FVG using flags | Skips entries if no FVG in all cases |
| Reverse Entry Flag Variables | Present (`reverseSellBreakOccurred`, `reverseBuyBreakOccurred`) | Absent |
| Post-close cooldown improvements | Included with throttled logging | Included, similar |
| Trailing stop update functions | Same | Same |
| Floating risk trade close logic | Same | Same |
| Print/log throttling | Enhanced throttling for ADX/no FVG/post-close | Same |
| Version number | 1.7 | 1.6 |
---
## **In essence:**
- The key **functional difference** is the **reverse entry logic** implementation.
- Version 1.7 is more **robust and sophisticated**, avoiding premature reverse trades by *waiting* for FVG confirmation with persistent flags.
- Version 1.6 is simpler and might enter reverse trades immediately on price and FVG signal without confirmation waiting.
---
If you want me to help you merge these features or modify one version to include the other’s behavior, just ask!