Files
ironsan2kk-pixel 6450d1efc6 Fix download links
2026-06-08 10:36:15 +02:00

1264 lines
76 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ★★★ EA_SingleLogic_LongShort ★★★
//---------------------------------------------
// (1) Input parameters
//---------------------------------------------
// --- Parameters for Magic_A
input double SARStep = 0.001; // SAR acceleration factor increment
input double SARMaxStep = 0.2; // Maximum SAR acceleration factor
input double TPPips = 90; // Take Profit in pips
input double SLPips = 70; // Stop Loss in pips
// Retry control parameters
extern int RetryCount = 3; // Number of retry attempts
extern int RetryIntervalMs = 1000; // Retry interval (milliseconds)
// --- Trailing stop settings
input bool UseTrailingStop = true; // true = enable trailing stop
input int TrailStepProfit = 30; // Trail activation profit (pips)
input int TrailShiftPips = 5; // Shift TP and SL by this many pips
// --- Fixed lot or compound lot selection
input bool UseCompoundLots = true; // true = compound, false = fixed lot
input double TargetMarginPercent = 5.0; // Target margin usage (% of balance)
extern double MarginLevelAlert = 250.0; // Margin level alert threshold (%)
// --- Trading hour restriction
input bool UseTradingHour = true; // true = restrict trading hours
input int TradingStartUTC = 7; // Trading start hour (UTC)
input int TradingEndUTC = 19; // Trading end hour (UTC, exclusive)
// --- EA status notification settings
extern bool NotifyEAStatus = true; // true = send status notifications
extern int StatusIntervalMin = 60; // Notification interval (minutes)
// --- Other settings
input double MaxSpreadPips = 3.0; // Maximum allowed spread (pips)
input int SlippagePips = 3; // Slippage (pips)
input double LotSize = 0.1; // Fixed lot size (for non-compound mode)
input int MaxOpenBuyPositions = 1; // Maximum BUY positions
input int MaxOpenSellPositions = 1; // Maximum SELL positions
#define BROKER_GMT_OFFSET_HOURS 2
//---------------------------------------------
// (2) Magic number
//---------------------------------------------
input int Magic_A = 67890;
//---------------------------------------------
// (3) Internal state variables
//---------------------------------------------
datetime prevTime_A = 0;
datetime lastEntryBarA = 0; // Bar time of the last entry (Magic_A)
datetime lastStatusSend = 0; // Last EA status notification time
bool EmergencyStop = false; // Emergency stop flag (internal)
bool EmergencyLatched = false; // Indicates if an emergency has ever occurred
// Exit latch counters
int buyExitTickCount = 0;
int sellExitTickCount = 0;
const int ExitTickThreshold = 5; // Required consecutive ticks
//---------------------------------------------
// (4) ENUM: Exit / Entry signals
//---------------------------------------------
enum SignalType
{
NO_SIGNAL = 0,
BUY_ENTRY,
BUY_EXIT,
SELL_ENTRY,
SELL_EXIT
};
//---------------------------------------------
// (5) Time utility functions (DST / UTC / trading hours)
//---------------------------------------------
// Assumes broker server time is UTC+2 (winter) / UTC+3 (summer)
// --- Daylight Saving Time detection (simplified, Northern Hemisphere)
bool IsDST(datetime t)
{
int year = TimeYear(t);
// Last Sunday of March
datetime marchLastSunday = StrToTime(StringFormat("%d.03.%d 02:00", year, 31));
while (TimeDayOfWeek(marchLastSunday) != 0)
marchLastSunday -= 86400;
// Last Sunday of October
datetime octLastSunday = StrToTime(StringFormat("%d.10.%d 03:00", year, 31));
while (TimeDayOfWeek(octLastSunday) != 0)
octLastSunday -= 86400;
return (t >= marchLastSunday && t < octLastSunday);
}
// --- Get current UTC hour (with DST adjustment)
int GetUTCHour()
{
datetime t = TimeCurrent();
int hour = TimeHour(t) - BROKER_GMT_OFFSET_HOURS;
if (IsDST(t))
hour -= 1; // Adjust for DST
if (hour < 0) hour += 24;
return hour;
}
// --- Trading hour check (can be enabled or disabled)
bool IsTradingHour()
{
if (!UseTradingHour) return true;
int utcHour = GetUTCHour();
return (utcHour >= TradingStartUTC && utcHour < TradingEndUTC);
}
//---------------------------------------------
// (6) New bar detection
//---------------------------------------------
bool IsNewBar(int timeframe, datetime &lastBarTime)
{
datetime currentBar = iTime(Symbol(), timeframe, 0);
if (currentBar != lastBarTime)
{
lastBarTime = currentBar;
return true;
}
return false;
}
//---------------------------------------------
// (7) Pip size calculation
//---------------------------------------------
double Pip()
{
return (Digits == 5 || Digits == 3) ? Point * 10 : Point;
}
//---------------------------------------------
// Convert pips to points (for slippage)
int PipToPoint(double pips)
{
return (int)MathRound(pips * Pip() / Point);
}
//---------------------------------------------
// (8) Lot size calculation: compound mode or fixed-lot mode
//---------------------------------------------
double CalculateLotSize()
{
if (!UseCompoundLots)
{
// Fixed-lot mode
double SlotStep = MarketInfo(_Symbol, MODE_LOTSTEP);
double SminLot = MarketInfo(_Symbol, MODE_MINLOT);
double SmaxLot = MarketInfo(_Symbol, MODE_MAXLOT);
double fixedLot = LotSize;
// Clamp to broker limits
double SadjustedLotSize = MathMax(SminLot, MathMin(fixedLot, SmaxLot));
// Align to lot step
SadjustedLotSize = MathFloor(SadjustedLotSize / SlotStep) * SlotStep;
return SadjustedLotSize;
}
// Compound mode (margin-based)
double accountBalance = AccountBalance();
double ClotStep = MarketInfo(_Symbol, MODE_LOTSTEP);
double CminLot = MarketInfo(_Symbol, MODE_MINLOT);
double CmaxLot = MarketInfo(_Symbol, MODE_MAXLOT);
double marginRequiredPerLot = MarketInfo(_Symbol, MODE_MARGINREQUIRED);
// -------------------------------------------------------
// Defensive: if margin information cannot be obtained due to broker specifications or symbol limitations,
// place an order using the symbol's minimum lot and log a warning.
// -------------------------------------------------------
if (marginRequiredPerLot <= 0.0)
{
PrintFormat(
"⚠ CalculateLotSize: invalid MODE_MARGINREQUIRED (%.5f). "
"Fallback to MINLOT=%.2f",
marginRequiredPerLot,
CminLot
);
return CminLot;
}
double targetMargin = accountBalance * TargetMarginPercent / 100.0;
double maxLotForMargin = targetMargin / marginRequiredPerLot;
double calculatedLotSize =
MathFloor(maxLotForMargin / ClotStep) * ClotStep;
double CadjustedLotSize =
MathMax(CminLot, MathMin(calculatedLotSize, CmaxLot));
return CadjustedLotSize;
}
//---------------------------------------------
// (9) Margin level check (anti-spam notification)
//---------------------------------------------
void CheckMarginLevel()
{
// Alert state (static to persist across ticks)
static bool alerted = false;
// No positions → no alert
if (OrdersTotal() == 0)
{
alerted = false;
return;
}
double margin = AccountMargin();
if (margin == 0)
{
alerted = false;
return;
}
double marginLevel = (AccountEquity() / margin) * 100.0;
// Alert trigger
if (marginLevel <= MarginLevelAlert && !alerted)
{
string timeStr = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
string msg =
"[EA_SingleLogic_LongShort]\n"
"● Margin level below threshold ●\n"
"Symbol: " + Symbol() + "\n"
"Time: " + timeStr + "\n"
"Threshold: " + DoubleToString(MarginLevelAlert, 1) + "%\n"
"Current: " + DoubleToString(marginLevel, 1) + "%";
Print(msg);
SendNotification(msg);
alerted = true;
}
// Reset alert once margin recovers
if (marginLevel > MarginLevelAlert)
{
alerted = false;
}
}
//---------------------------------------------
// (10) EA runtime status notification
//---------------------------------------------
void CheckEAStatus()
{
if (!NotifyEAStatus) return;
if (TimeCurrent() - lastStatusSend >= StatusIntervalMin * 60)
{
string msg =
"[EA_SingleLogic_LongShort is running]\n" +
"Time: " + TimeToString(TimeCurrent(), TIME_SECONDS);
SendNotification(msg);
Print(msg);
lastStatusSend = TimeCurrent();
}
}
//---------------------------------------------
// (11) Count open positions by type
// type: OP_BUY or OP_SELL
//---------------------------------------------
int CountOrdersByTypeThisEA(int type)
{
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() &&
OrderMagicNumber() == Magic_A &&
OrderType() == type)
{
count++;
}
}
}
return count;
}
//---------------------------------------------
// (12) Check if a new BUY order can be placed
// (emergency stop aware)
//---------------------------------------------
bool CanPlaceNewBuy()
{
if (EmergencyStop)
return false;
return CountOrdersByTypeThisEA(OP_BUY) < MaxOpenBuyPositions;
}
//---------------------------------------------
// (13) Check if a new SELL order can be placed
// (emergency stop aware)
//---------------------------------------------
bool CanPlaceNewSell()
{
if (EmergencyStop)
return false;
return CountOrdersByTypeThisEA(OP_SELL) < MaxOpenSellPositions;
}
//---------------------------------------------
// (14) Exit signal logic (BUY and SELL)
//---------------------------------------------
SignalType ExitSignalA()
{
double SAR0 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 0);
double SAR1 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 1);
double SAR2 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 2);
// BUY exit conditions
if ((Low[1] > SAR1 && Close[0] <= SAR0) ||
(High[2] < SAR2 && High[1] < SAR1 && High[0] < SAR0))
{
buyExitTickCount++;
}
else
{
buyExitTickCount = 0;
}
// SELL exit conditions
if ((High[1] < SAR1 && Close[0] >= SAR0) ||
(Low[2] > SAR2 && Low[1] > SAR1 && Low[0] > SAR0))
{
sellExitTickCount++;
}
else
{
sellExitTickCount = 0;
}
// Return EXIT signal once latch threshold is reached
if (buyExitTickCount >= ExitTickThreshold) return BUY_EXIT;
if (sellExitTickCount >= ExitTickThreshold) return SELL_EXIT;
return NO_SIGNAL;
}
//---------------------------------------------
// (15) Entry signal logic (BUY and SELL)
//---------------------------------------------
SignalType EntrySignalA()
{
double SAR1 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 1);
double SAR2 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 2);
double SAR3 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 3);
// BUY entry conditions
if ((High[2] < SAR2 && Close[1] >= SAR1) ||
(Low[3] > SAR3 && Low[2] > SAR2 && Low[1] > SAR1))
{
return BUY_ENTRY;
}
// SELL entry conditions
if ((Low[2] > SAR2 && Close[1] <= SAR1) ||
(High[3] < SAR3 && High[2] < SAR2 && High[1] < SAR1))
{
return SELL_ENTRY;
}
return NO_SIGNAL;
}
// ---------------------------------------------
// (16) RetryManager (single-task model)
// ---------------------------------------------
// Retry task types
enum RetryTaskType {
TASK_NONE = 0,
TASK_ENTRY,
TASK_EXIT,
TASK_MODIFY
};
// RetryTask structure
struct RetryTask {
RetryTaskType taskType;
int ticket; // Used for Exit / Modify
int orderType; // Entry: OP_BUY / OP_SELL
double lots;
double price; // Stored entry price (actual execution uses market price)
double sl;
double tp;
int slippage;
int retryLeft;
int lastAttemptMs;
};
// Global: single active retry task
RetryTask currentTask = { TASK_NONE };
// ---------------------------
// (17) EXIT-only queue (minimal implementation)
// ---------------------------
#define MAX_EXIT_QUEUE 20
RetryTask exitQueue[MAX_EXIT_QUEUE];
int exitQueueSize = 0;
bool PushExitQueue(const RetryTask &t)
{
if (exitQueueSize >= MAX_EXIT_QUEUE)
{
Print("❌ EXIT queue overflow");
return false;
}
exitQueue[exitQueueSize++] = t;
return true;
}
// EXIT queue helper functions
bool HasExitQueue()
{
return (exitQueueSize > 0);
}
RetryTask PopExitQueue()
{
RetryTask t = exitQueue[0];
for (int i = 1; i < exitQueueSize; i++)
exitQueue[i - 1] = exitQueue[i];
exitQueueSize--;
return t;
}
// ---------------------------
// (18) ResetCurrentTask
// ---------------------------
void ResetCurrentTask()
{
currentTask.taskType = TASK_NONE;
currentTask.ticket = 0;
currentTask.orderType = 0;
currentTask.lots = 0.0;
currentTask.price = 0.0;
currentTask.sl = 0.0;
currentTask.tp = 0.0;
currentTask.slippage = 0;
currentTask.retryLeft = 0;
currentTask.lastAttemptMs = 0;
}
// ---------------------------
// (19) RegisterRetryTask
// ---------------------------
bool RegisterRetryTask(const RetryTask &task)
{
if (currentTask.taskType != TASK_NONE)
{
PrintFormat(
"[RegisterRetryTask] Skip: another task active (exist=%d new=%d)",
currentTask.taskType,
task.taskType
);
return false;
}
currentTask = task;
currentTask.lastAttemptMs = 0; // Attempt immediately on next tick
PrintFormat(
"[RegisterRetryTask] Registered task type=%d ticket=%d",
currentTask.taskType,
currentTask.ticket
);
return true;
}
// ---------------------------
// (20) ProcessRetryTask
// ---------------------------
void ProcessRetryTask()
{
if (currentTask.taskType == TASK_NONE) return;
int now = GetTickCount();
// Wait until retry interval has elapsed
if (currentTask.lastAttemptMs != 0 &&
now - currentTask.lastAttemptMs < RetryIntervalMs)
{
return;
}
RefreshRates();
bool success = false;
int err = 0;
// ============================
// TASK_ENTRY
// ============================
if (currentTask.taskType == TASK_ENTRY)
{
// Always use the latest market price for entry
double execPrice = (currentTask.orderType == OP_BUY) ? Ask : Bid;
// Recalculate SL / TP based on execution price
double newSL, newTP;
if (currentTask.orderType == OP_BUY)
{
newSL = NormalizeDouble(execPrice - SLPips * Pip(), Digits);
newTP = NormalizeDouble(execPrice + TPPips * Pip(), Digits);
}
else
{
newSL = NormalizeDouble(execPrice + SLPips * Pip(), Digits);
newTP = NormalizeDouble(execPrice - TPPips * Pip(), Digits);
}
int ticket = OrderSend(
Symbol(),
currentTask.orderType,
currentTask.lots,
execPrice,
currentTask.slippage,
newSL,
newTP,
"Magic_A Entry",
Magic_A,
0,
clrOrange
);
if (ticket > 0)
{
success = true;
PrintFormat("✅ [Retry] Entry success: ticket=%d", ticket);
lastEntryBarA = iTime(Symbol(), PERIOD_CURRENT, 0);
}
else
{
err = GetLastError();
PrintFormat(
"❌ [Retry] Entry failed err=%d retryLeft=%d",
err,
currentTask.retryLeft
);
}
}
// ============================
// TASK_EXIT
// ============================
else if (currentTask.taskType == TASK_EXIT)
{
if (!OrderSelect(currentTask.ticket, SELECT_BY_TICKET))
{
// Order is already closed
success = true;
PrintFormat(
" [Retry] EXIT: ticket %d no longer exists -> treat as success",
currentTask.ticket
);
}
else
{
// Defensive handling for invalid lot size
if (currentTask.lots <= 0.0)
currentTask.lots = OrderLots();
int otype = OrderType();
double closePrice = (otype == OP_BUY) ? Bid : Ask;
bool closed = OrderClose(
currentTask.ticket,
currentTask.lots,
closePrice,
currentTask.slippage,
clrOrange
);
if (closed)
{
success = true;
PrintFormat(
"✅ [Retry] Exit success ticket=%d",
currentTask.ticket
);
}
else
{
err = GetLastError();
PrintFormat(
"❌ [Retry] Exit failed ticket=%d err=%d retryLeft=%d",
currentTask.ticket,
err,
currentTask.retryLeft
);
}
}
}
// ============================
// TASK_MODIFY
// ============================
else if (currentTask.taskType == TASK_MODIFY)
{
if (!OrderSelect(currentTask.ticket, SELECT_BY_TICKET))
{
// Order no longer exists
success = true;
PrintFormat(
" [Retry] MODIFY: ticket %d missing -> treat as success",
currentTask.ticket
);
}
else
{
double currOpen = OrderOpenPrice();
// Defensive correction for SL / TP values
double correctedSL =
(currentTask.sl <= 0) ? OrderStopLoss() : currentTask.sl;
double correctedTP =
(currentTask.tp <= 0) ? OrderTakeProfit() : currentTask.tp;
bool modified = OrderModify(
currentTask.ticket,
currOpen,
correctedSL,
correctedTP,
0,
clrAqua
);
if (modified)
{
success = true;
PrintFormat(
"✅ [Retry] Modify success ticket=%d",
currentTask.ticket
);
}
else
{
err = GetLastError();
PrintFormat(
"❌ [Retry] Modify failed ticket=%d err=%d retryLeft=%d",
currentTask.ticket,
err,
currentTask.retryLeft
);
}
}
}
// ============================
// Success handling
// ============================
if (success)
{
ResetCurrentTask();
return;
}
// ============================
// Error handling (retry or failure)
// ============================
bool transient = (
err == ERR_SERVER_BUSY ||
err == ERR_TRADE_CONTEXT_BUSY ||
err == ERR_PRICE_CHANGED ||
err == ERR_OFF_QUOTES ||
err == ERR_REQUOTE
);
if (transient && currentTask.retryLeft > 0)
{
currentTask.retryLeft--;
currentTask.lastAttemptMs = now;
PrintFormat(
"[Retry] transient error %d → retryLeft=%d",
err,
currentTask.retryLeft
);
return;
}
PrintFormat(
"[Retry] Task FAILED: type=%d ticket=%d err=%d",
currentTask.taskType,
currentTask.ticket,
err
);
ResetCurrentTask();
}
//---------------------------------------------
// (21) Exit handling (supports both BUY and SELL)
//---------------------------------------------
void HandleExitA(SignalType sig)
{
int slippagePoints = PipToPoint(SlippagePips);
// =======================
// BUY EXIT
// =======================
if (sig == BUY_EXIT)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() == Magic_A &&
OrderSymbol() == _Symbol &&
OrderType() == OP_BUY)
{
RefreshRates(); // Fetch latest market prices
double BUYclosePrice = Bid;
RetryTask t;
t.taskType = TASK_EXIT;
t.ticket = OrderTicket();
t.lots = OrderLots();
t.price = BUYclosePrice; // ← added: reference close price
t.slippage = slippagePoints;
t.retryLeft = RetryCount;
if (PushExitQueue(t))
PrintFormat("📌 BUY EXIT queued (ticket=%d)", t.ticket);
}
}
return;
}
// =======================
// SELL EXIT
// =======================
if (sig == SELL_EXIT)
{
for (int m = OrdersTotal() - 1; m >= 0; m--)
{
if (!OrderSelect(m, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() == Magic_A &&
OrderSymbol() == _Symbol &&
OrderType() == OP_SELL)
{
RefreshRates(); // Fetch latest market prices
double SELLclosePrice = Ask;
RetryTask t;
t.taskType = TASK_EXIT;
t.ticket = OrderTicket();
t.lots = OrderLots();
t.price = SELLclosePrice; // ← added: reference close price
t.slippage = slippagePoints;
t.retryLeft = RetryCount;
if (PushExitQueue(t))
PrintFormat("📌 SELL EXIT queued (ticket=%d)", t.ticket);
}
}
}
}
//---------------------------------------------
// (22) Entry handling (supports both BUY and SELL)
//---------------------------------------------
void HandleEntryA(SignalType sig)
{
if (sig == NO_SIGNAL) return;
// 🚨 Block new entries while in emergency stop mode
if (EmergencyStop)
{
Print("🚫 EmergencyStop active. New entry is blocked: ", sig);
return;
}
datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, 0);
// ▼ Second entry within the same bar → abnormal condition
if (lastEntryBarA == currentBarTime)
{
EmergencyCloseAll("Second entry detected within the same bar (abnormal)");
return;
}
if (!IsTradingHour()) return;
// Declare once and reuse
double orderLot;
// =======================
// BUY ENTRY
// =======================
if (sig == BUY_ENTRY)
{
double BUYspreadPips = (Ask - Bid) / Pip();
if (BUYspreadPips > MaxSpreadPips)
{
PrintFormat(
"❌ Spread too high (%.1f pips). Skip BUY entry.",
BUYspreadPips
);
return;
}
if (!CanPlaceNewBuy()) return;
double BUYprice = Ask;
double BUYsl = NormalizeDouble(BUYprice - SLPips * Pip(), Digits);
double BUYtp = NormalizeDouble(BUYprice + TPPips * Pip(), Digits);
int BUYslippagePoints = PipToPoint(SlippagePips);
// --- Retry-based entry (no Sleep, task registered to OnTick) ---
RefreshRates(); // Update prices
BUYprice = Ask; // Refresh execution price
orderLot = CalculateLotSize();
if (orderLot <= 0)
{
Print("[HandleEntryA] calculated lot <= 0. Fallback to LotSize param.");
orderLot = LotSize;
}
// === Register task to RetryManager ===
RetryTask t;
t.taskType = TASK_ENTRY;
t.orderType = OP_BUY;
t.lots = orderLot;
t.price = BUYprice;
t.sl = BUYsl;
t.tp = BUYtp;
t.slippage = BUYslippagePoints;
t.retryLeft = RetryCount;
if (RegisterRetryTask(t))
{
PrintFormat(
"📌 BUY ENTRY task registered. lot=%.2f sl=%.5f tp=%.5f",
orderLot,
BUYsl,
BUYtp
);
}
else
{
Print("⚠ BUY ENTRY task registration skipped (another task active)");
}
// BUY ends here (actual execution is handled in OnTick)
return;
}
// =======================
// SELL ENTRY
// =======================
if (sig == SELL_ENTRY)
{
double SELLspreadPips = (Ask - Bid) / Pip();
if (SELLspreadPips > MaxSpreadPips)
{
PrintFormat(
"❌ Spread too high (%.1f pips). Skip SELL entry.",
SELLspreadPips
);
return;
}
if (!CanPlaceNewSell()) return;
double SELLprice = Bid; // SELL uses Bid price
double SELLsl = NormalizeDouble(SELLprice + SLPips * Pip(), Digits);
double SELLtp = NormalizeDouble(SELLprice - TPPips * Pip(), Digits);
int SELLslippagePoints = PipToPoint(SlippagePips);
// --- Retry-based entry (no Sleep, task registered to OnTick) ---
RefreshRates();
SELLprice = Bid; // Refresh execution price
orderLot = CalculateLotSize();
if (orderLot <= 0)
{
Print("[HandleEntryA] calculated lot <= 0. Fallback to LotSize param.");
orderLot = LotSize;
}
// === Register task to RetryManager ===
RetryTask t;
t.taskType = TASK_ENTRY;
t.orderType = OP_SELL;
t.lots = orderLot;
t.price = SELLprice;
t.sl = SELLsl;
t.tp = SELLtp;
t.slippage = SELLslippagePoints;
t.retryLeft = RetryCount;
if (RegisterRetryTask(t))
{
PrintFormat(
"📌 SELL ENTRY task registered. lot=%.2f sl=%.5f tp=%.5f",
orderLot,
SELLsl,
SELLtp
);
}
else
{
Print("⚠ SELL ENTRY task registration skipped (another task active)");
}
// SELL ends here (actual execution is handled in OnTick)
return;
}
}
//---------------------------------------------
// (23) Step-based trailing stop
//---------------------------------------------
void ApplyTrailingStop()
{
if (!UseTrailingStop) return;
// ❗ Do not apply trailing while another retry task is active
if (currentTask.taskType != TASK_NONE) return;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderMagicNumber() != Magic_A) continue;
int type = OrderType();
if (type != OP_BUY && type != OP_SELL) continue;
int ticket = OrderTicket();
double openPrice = OrderOpenPrice();
double oldSL = OrderStopLoss();
double oldTP = OrderTakeProfit();
double currentPrice = (type == OP_BUY) ? Bid : Ask;
// Floating profit in pips
double profitPips = (currentPrice - openPrice) / Pip();
if (type == OP_SELL) profitPips = -profitPips;
// Minimum trailing condition not met
if (profitPips < TrailStepProfit) continue;
// ===============================
// 🔑 Calculate trailing step count
// ===============================
int stepCount = (int)(profitPips / TrailStepProfit);
double tgtSL, tgtTP, newSL, newTP;
if (type == OP_BUY)
{
tgtSL = openPrice + (stepCount * TrailShiftPips) * Pip();
tgtTP = openPrice + (stepCount * TrailShiftPips + TPPips) * Pip();
newSL = (oldSL < tgtSL) ? tgtSL : oldSL;
newTP = (oldTP < tgtTP) ? tgtTP : oldTP;
}
else
{
tgtSL = openPrice - (stepCount * TrailShiftPips) * Pip();
tgtTP = openPrice - (stepCount * TrailShiftPips + TPPips) * Pip();
newSL = (oldSL > tgtSL) ? tgtSL : oldSL;
newTP = (oldTP > tgtTP) ? tgtTP : oldTP;
}
newSL = NormalizeDouble(newSL, Digits);
newTP = NormalizeDouble(newTP, Digits);
// Skip update if the change is negligible
if (MathAbs(oldSL - newSL) <= Point * 0.5 &&
MathAbs(oldTP - newTP) <= Point * 0.5)
{
continue;
}
// ❗ Only one modify task can be registered at a time
if (currentTask.taskType != TASK_NONE) return;
// 🚀 Register MODIFY task to RetryManager (executed on OnTick)
RetryTask t;
t.taskType = TASK_MODIFY;
t.ticket = ticket;
t.sl = newSL;
t.tp = newTP;
t.retryLeft = RetryCount;
t.lastAttemptMs = 0;
if (RegisterRetryTask(t))
{
PrintFormat(
"🔧 StepTrailing MODIFY registered. ticket=%d step=%d sl=%.5f tp=%.5f",
ticket, stepCount, newSL, newTP
);
}
// ❗ Register only one task per tick
return;
}
}
//---------------------------------------------
// (24) Fail-safe abnormal state detection
//---------------------------------------------
void CheckForAbnormalState()
{
// 🚨 If emergency stop has already been latched, do nothing
// (reset only on EA restart)
if (EmergencyLatched)
return;
int openBuyCount = 0;
int openSellCount = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != Magic_A) continue;
if (OrderSymbol() != Symbol()) continue;
int type = OrderType();
if (type == OP_BUY) openBuyCount++;
if (type == OP_SELL) openSellCount++;
}
// ▼ Check for maximum open position count violation
if (openBuyCount > MaxOpenBuyPositions)
{
EmergencyLatched = true; // ← latch ON
EmergencyCloseAll(
StringFormat(
"Abnormal BUY position count (%d > %d)",
openBuyCount,
MaxOpenBuyPositions
)
);
return;
}
if (openSellCount > MaxOpenSellPositions)
{
EmergencyLatched = true; // ← latch ON
EmergencyCloseAll(
StringFormat(
"Abnormal SELL position count (%d > %d)",
openSellCount,
MaxOpenSellPositions
)
);
return;
}
// Note:
// Duplicate entry within the same bar is not handled here.
// It is detected immediately before entry execution.
}
// ---------------------------------------------
// (25) Emergency close all positions and block new entries
// (EA continues running)
// ---------------------------------------------
void EmergencyCloseAll(string reasonMessage)
{
EmergencyStop = true; // Completely block new entries
Print("🚨 EmergencyCloseAll triggered. Reason = ", reasonMessage);
const int maxRetry = 200;
const int retryInterval = 200; // milliseconds (0.2s interval)
for (int attempt = 0; attempt < maxRetry; attempt++)
{
RefreshRates();
// ---- Process all orders matching this EA's Magic number ----
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderMagicNumber() != Magic_A) continue;
int type = OrderType();
// ▼ Market positions
if (type == OP_BUY || type == OP_SELL)
{
double price = (type == OP_BUY) ? Bid : Ask; // use latest price
bool closed = OrderClose(
OrderTicket(),
OrderLots(),
price,
5,
clrRed
);
if (!closed)
{
PrintFormat(
"⚠️ OrderClose failed ticket=%d error=%d (retry=%d)",
OrderTicket(), GetLastError(), attempt
);
}
else
{
PrintFormat(
"✅ Market position closed successfully ticket=%d",
OrderTicket()
);
}
}
// ▼ Pending orders
else if (type == OP_BUYLIMIT || type == OP_SELLLIMIT ||
type == OP_BUYSTOP || type == OP_SELLSTOP)
{
bool deleted = OrderDelete(OrderTicket());
if (!deleted)
{
PrintFormat(
"⚠️ OrderDelete failed ticket=%d error=%d (retry=%d)",
OrderTicket(), GetLastError(), attempt
);
}
else
{
PrintFormat(
"🗑️ Pending order deleted successfully ticket=%d",
OrderTicket()
);
}
}
}
// ---- Are all orders cleared? ----
if (CountOrdersOfThisEA() == 0)
{
string msg =
"[Emergency Close Completed]\n" +
"Symbol: " + Symbol() + "\n" +
"Time: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n" +
"Reason: " + reasonMessage + "\n" +
"All positions have been closed/deleted.\n" +
"EA remains active in EmergencyStop mode (no new entries).";
SendNotification(msg);
Print(msg);
return;
}
if (attempt % 10 == 0)
{
PrintFormat(
"⏳ EmergencyClose retry %d/%d",
attempt, maxRetry
);
}
Sleep(retryInterval);
}
// ---- Orders still remain after maximum retries ----
string newmsg =
"[WARNING: Emergency Close Failed]\n" +
"Symbol: " + Symbol() + "\n" +
"Time: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n" +
"Reason: " + reasonMessage + "\n" +
"Orders still remain after maximum retry attempts.\n" +
"EmergencyStop mode is maintained (entries remain blocked).";
SendNotification(newmsg);
Print(newmsg);
}
// ---------------------------------------------
// (26) Helper: count orders belonging to Magic_A
// (used to verify completion of emergency close)
// ---------------------------------------------
int CountOrdersOfThisEA()
{
int c = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderMagicNumber() != Magic_A) continue;
c++;
}
return c;
}
//---------------------------------------------
// (27) Main OnTick processing
//---------------------------------------------
void OnTick()
{
RefreshRates();
// Check for abnormal/fail-safe conditions and margin alerts
CheckForAbnormalState();
CheckMarginLevel();
// =================================================
// ① Detect EXIT signals → push to EXIT queue (highest priority)
// =================================================
SignalType exitSig = ExitSignalA();
HandleExitA(exitSig); // ← does not register RetryTask directly; queued in EXIT queue
// =================================================
// ② If EXIT queue exists, flow the top task to RetryManager (highest priority)
// =================================================
if (currentTask.taskType == TASK_NONE && HasExitQueue())
{
RetryTask t = PopExitQueue();
RegisterRetryTask(t);
}
// =================================================
// ③ Trailing Stop (register MODIFY task)
// ※ Will naturally wait if an EXIT task is in progress
// =================================================
ApplyTrailingStop();
// =================================================
// ④ New bar entry (register ENTRY task)
// ※ Will naturally wait if an EXIT task is in progress
// =================================================
if (IsNewBar(PERIOD_CURRENT, prevTime_A))
{
SignalType entrySig = EntrySignalA();
HandleEntryA(entrySig);
}
// =================================================
// ⑤ Execute RetryManager
// =================================================
ProcessRetryTask();
// Send periodic EA status notification
CheckEAStatus();
}
// --- END ---