turns-00083.parquet:25876
218a8d3277fc44f93d6da798
turn 1/1gpt-4.1-mini-2025-04-14EnglishRomania3728 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 |
//+------------------------------------------------------------------+
#property copyright ""
#property version "1.6"
#property strict
#include <Trade\Trade.mqh>
input double inpLot = 0.05;
input bool useRiskPercent = false;
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 = false;
input double maxRiskPerTradePercent = 1.0; // Floating loss cap only!
input double maxDailyLossAmount = 500.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 = 60; // Cooldown seconds after a position closes before new trades allowed.
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 = 60; // 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);
}
}
//+------------------------------------------------------------------+
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;
}
}
//+------------------------------------------------------------------+
void OnTick()
{
datetime now = TimeCurrent();
// Block trading if a position closed recently, using cooldown window
if(postCloseCooldownSeconds > 0 && IsRecentPositionClose(postCloseCooldownSeconds))
{
PrintFormat("Post-close cooldown active, skipping trades at %s", TimeToString(now));
return;
}
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];
if(adx_value < adxThreshold)
{
PrintFormat("ADX=%.2f below threshold %.2f, skipping new entries", adx_value, adxThreshold);
return;
}
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);
bool is_downtrend = (MA1 < MA2) && (MA2 < MA3);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double lot = CalculateLotSize(sl_dist);
if(lot < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
return;
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))
{
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))
{
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))
{
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))
{
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
Your provided MQL5 Expert Advisor code looks fully functional and well-structured, implementing a break-of-structure trading logic with: - Multiple moving averages (short, medium, long) - ATR-based SL and TP calculation - ADX filter - Losing streak filter - Floating risk cap (max loss per trade) - Trailing stop (multiple modes) - Post-close cooldown on new trades - Minimum time interval cooldown - Cooldown per candle cooldown --- ### Notes & Suggestions: 1. **Logical Flow** The `OnTick()` function correctly sequences checks: - post-close cooldown - daily equity and losing streak resets - update losing streak by scanning closed deals - max losing streak / max daily loss check - floating loss cap / maxRiskPerTradePercent check to forcibly close positions - trailing stops update per position - position limit and trading window checks - cooldown timer and candle cooldown - ADX filter - confirm trend by 3 MAs - Check for breakouts beyond last swing high/lows - Place orders as appropriate 2. **Risk Management** Your dynamic lot sizing in `CalculateLotSize` looks solid, handling both fixed lots and percentage risk. Ensure your SL price distance (`sl_distance_price`) is accurately calculated and meaningful. 3. **Trailing Stops** Trailing stop modes: - Market structure uses recent swing highs/lows with a small buffer - ATR trailing uses ATR multiples for dynamic SL placement - Hybrid picks the more protective SL between MS and ATR methods, triggers trailing only after some profit gained 4. **Cooldowns** Multiple cooldown logics are combined effectively: - Post-close (seconds after position closes) - Minimum time interval between trades - Cooldown per candle (only once per candle) 5. **ADX Filter** Prevents trading if ADX is below threshold, to filter out low volatility trends. --- ### Potential Improvements / Checks: - **Error Handling on OrderSend**: You print errors, but consider adding retry logic or alerts if critical order sending errors occur frequently. - **HistorySelect Usage**: You call `HistorySelect(now - 86400, now)` and similarly in `IsRecentPositionClose()`. Be mindful of possible limitations in some brokers or accounts where history may not be full. Also, `HistorySelect` only loads history deals; ensure that no history buffer overflow occurs. - **Trailing SL Timing** You currently update trailing SL every tick but regulated to once every `TrailUpdateIntervalSec` seconds per position. This is good; alternatively, consider OnTimer based updates for less frequent updates. - **Synchronization of Indicators** You’re using different timeframes for swing points, MAs, ATR, ADX, and cooldown candles. That is expected but consider if all timeframes align with your strategy objective. - **Enable/Disable Trading** The code uses `enableReverseEntries` flag which toggles reversal entries on breakouts against trend. Make sure you carefully backtest that. - **Memory & Handles Cleanup** The use of `IndicatorRelease()` on all indicator handles on `OnDeinit()` is proper. - **PositionCheck** Your `IsPositionOpen` function scans all positions on the symbol with your magic number and type. If you run multi-instrument multi-EA, ensure magic number separation. --- ### Summary Your EA has a comprehensive implementation with strong risk and trade management features included. If you want me to help with any enhancements, specific functionality explaining, debugging, or testing advice, just ask! Otherwise, it seems ready for backtesting and live demo. --- If you want, I can help describe the trading logic in simple terms or write a companion indicator or panel to visualize the trades. Just let me know!