跳至主要內容
金融世界

MT5-分享交易程式碼(多單)

182 分鐘33 次瀏覽
MT5-分享交易程式碼(多單)

//+------------------------------------------------------------------+

//| m11150809.mq5 — M115100 批次優勝定稿(ID49:階梯最多2層) |

//| 基底=最終版M115.60;手數改為 InpLots 可調;其餘邏輯不變 |

//+------------------------------------------------------------------+

#property copyright "M115"

#property version "1.00"

#property description "m11150809: M115.60 + stairMax2 (M115100 ID49 winner), configurable lots"

#property strict

#include <Trade/Trade.mqh>

#include <Trade/PositionInfo.mqh>

#define MAX_EXIT_LOG 32

#define ORDER_COMMENT_MAX 31

#define STAIR_LEVELS 2

input group "Identity"

input long InpMagic = 715809;

input string InpComment = "m11150809";

input int InpMaxSpreadPoints = 400;

input group "Lots"

input double InpLots = 0.01; // 下單手數(首單與加碼同手數)

input group "Logging"

input bool InpEnableHeartbeat = true;

input int InpHbRetainHours = 24;

input bool InpEnableTradeLog = true;

input int InpTradeRetainDays = 7;

input group "Infra"

input int InpOrderRetryMax = 3;

input int InpOrderRetryMs = 400;

input bool InpBlockNewOnDisconnect = true;

input group "Grid"

input int InpMaxGridLayers = 8;

input double InpGridStepFrac = 0.0015;

input double InpSlFrac = 0.976;

input int InpCoolBars = 5;

input group "HTF"

input ENUM_TIMEFRAMES InpHtf = PERIOD_H1;

input int InpHtfEmaFast = 50;

input int InpHtfEmaSlow = 200;

input bool InpExitOnHtfBear = true;

input bool InpBlockAddOnBear = true;

input group "EarlyFlatten"

input ENUM_TIMEFRAMES InpDumpTf = PERIOD_H1;

input bool InpUseFloatExit = true;

input double InpFloatLossPct = 1.80;

input bool InpUseAtrSpikeExit = true;

input int InpAtrPeriod = 14;

input double InpAtrSpikeMult = 2.20;

input int InpAtrSmaPeriod = 20;

input bool InpUseBearBarsExit = false;

input int InpBearBarsNeed = 3;

input bool InpEarlyExitOnlyIfLayers = true;

input int InpEarlyExitMinLayers = 4;

input bool InpPauseAddOnAtrSpike = true;

input group "Pause"

input bool InpUseExitPause = true;

input int InpPauseHours = 48;

input group "HighDD"

input bool InpUseHighDrawdownStop = true;

input ENUM_TIMEFRAMES InpHighTf = PERIOD_H4;

input int InpHighLookback = 60;

input double InpHighDrawdownPct = 6.0;

input bool InpFlattenOnHighDD = true;

input group "DailyVeto"

input bool InpUseDailyVeto = true;

input int InpDailyEmaPeriod = 50;

input group "Fuse"

input bool InpUseExitFuse = true;

input int InpFuseWindowDays = 5;

input int InpFuseMaxEarly = 3;

input bool InpFuseNeedsH1BullClear = true;

input group "LossHalt"

input bool InpUseDailyLossHalt = true;

input double InpDailyLossLimitAmt = 300.0;

input bool InpUseWeeklyLossHalt = false;

input double InpWeeklyLossLimitAmt = 800.0;

input group "MarginMagicDD"

input bool InpUseMarginCheck = true;

input double InpMarginSafetyMult = 1.2;

input bool InpUseAccountDdHalt = true;

input double InpAccountDdPct = 30.0;

input int InpMagicDdScanSec = 60;

input group "MultiATR"

input bool InpUseMultiAtrStep = true;

input int InpDynAtrPeriod = 14;

input double InpAtrMultM1 = 8.0;

input double InpAtrMultM5 = 3.5;

input double InpAtrMultM15 = 2.0;

input double InpAtrMultM30 = 1.5;

input double InpAtrMultH1 = 1.0;

input group "Entry"

input int InpKPeriod = 5;

input int InpDPeriod = 3;

input int InpSlowing = 3;

input double InpKdMax = 35.0;

input int InpBbPeriod = 20;

input double InpBbDev = 2.0;

input bool InpAllowKdOrBb = true;

input group "StaircaseTP"

input bool InpUseStairTp = true;

input bool InpStairLockOnly = true;

input double InpStair1ActPct = 0.12;

input double InpStair1LockPct = 0.00;

input double InpStair2ActPct = 0.25;

input double InpStair2LockPct = 0.08;

input double InpStair3ActPct = 0.45;

input double InpStair3LockPct = 0.20;

input double InpStair4ActPct = 0.70;

input double InpStair4LockPct = 0.40;

CTrade trade;

CPositionInfo pos;

int g_hEmaFast = INVALID_HANDLE;

int g_hEmaSlow = INVALID_HANDLE;

int g_hStoch = INVALID_HANDLE;

int g_hBb = INVALID_HANDLE;

int g_hAtrDump = INVALID_HANDLE;

int g_hEmaD1 = INVALID_HANDLE;

int g_hAtrM1 = INVALID_HANDLE;

int g_hAtrM5 = INVALID_HANDLE;

int g_hAtrM15 = INVALID_HANDLE;

int g_hAtrM30 = INVALID_HANDLE;

int g_hAtrH1 = INVALID_HANDLE;

datetime g_lastBar = 0;

double g_initPrice = 0.0;

double g_gridStep = 0.0;

datetime g_pauseUntil = 0;

bool g_fuseOn = false;

bool g_fuseSawBear = false;

datetime g_earlyExitLog[MAX_EXIT_LOG];

int g_earlyExitCount = 0;

bool g_closing_in_progress = false;

datetime g_close_lock_start = 0;

string g_pendingCloseReason = "";

bool g_closeDeferredOffline = false;

double g_lastSyncedSl = 0.0;

datetime g_lastSyncSlBar = 0;

int g_stairLevel = 0;

double g_stairLockPrice = 0.0;

bool g_posDirty = true;

int g_cCount = 0;

double g_cAvg = 0.0, g_cLowest = 0.0;

datetime g_cLastT = 0, g_cOldest = 0;

datetime g_cacheHtfBar = 0;

datetime g_cacheDumpBar = 0;

datetime g_cacheHighBar = 0;

datetime g_cacheD1Bar = 0;

datetime g_cacheM1Bar = 0;

bool g_cH1Bull = false;

bool g_cHtfBreak = false;

bool g_cAtrSpike = false;

bool g_cFarHigh = false;

bool g_cDailyVeto = false;

double g_cDynStep = 0.0;

datetime g_lastHbMinute = 0;

datetime g_lastPurgeCheck = 0;

bool g_wasConnected = true;

int g_disconnectCount = 0;

datetime g_lastDisconnectLog = 0;

double g_dayRealizedPnl = 0.0;

double g_weekRealizedPnl = 0.0;

datetime g_dayStamp = 0;

datetime g_weekStamp = 0;

bool g_dailyLossHalt = false;

bool g_weeklyLossHalt = false;

datetime g_lastPnlScan = 0;

double g_startEquity = 0.0;

datetime g_eaStartTime = 0;

double g_magicRealizedCached = 0.0;

datetime g_lastMagicRealizedScan = 0;

bool g_magicRealizedDirty = true;

bool g_eaFatalStop = false;

bool g_expertRemovePending = false;

datetime g_lastMarginWarn = 0;

datetime g_lastCloseWarn = 0;

long g_lastMarginMode = -1;

double g_bufF[3], g_bufS[3], g_bufK[3], g_bufD[3], g_bufBb[3], g_bufEmaD1[2];

double g_bufAtrDump[];

double g_bufAtr1[1], g_bufAtr5[1], g_bufAtr15[1], g_bufAtr30[1], g_bufAtrH1[1];

double TpMultByCount(const int count)

{

if(count <= 1) return 1.0020;

if(count == 2) return 1.0015;

if(count == 3) return 1.0012;

if(count == 4) return 1.0009;

if(count == 5) return 1.0006;

if(count == 6) return 1.0003;

return 1.0001;

}

void ResetStairLock()

{

g_stairLevel = 0;

g_stairLockPrice = 0.0;

}

double StairActPct(const int level)

{

if(level == 1) return InpStair1ActPct;

if(level == 2) return InpStair2ActPct;

if(level == 3) return InpStair3ActPct;

if(level == 4) return InpStair4ActPct;

return 0.0;

}

double StairLockPct(const int level)

{

if(level == 1) return InpStair1LockPct;

if(level == 2) return InpStair2LockPct;

if(level == 3) return InpStair3LockPct;

if(level == 4) return InpStair4LockPct;

return 0.0;

}

bool UpdateStairLock(const double avg, const double bid)

{

if(!InpUseStairTp || avg <= 0.0 || bid <= 0.0)

return (g_stairLevel > 0);

double profitPct = (bid / avg - 1.0) * 100.0;

int reached = 0;

for(int lvl = 1; lvl <= STAIR_LEVELS; ++lvl)

{

double actPct = StairActPct(lvl);

if(actPct <= 0.0) continue;

if(profitPct >= actPct)

reached = lvl;

}

if(reached > g_stairLevel)

{

double lockPct = StairLockPct(reached);

double candidate = NormalizeDouble(avg * (1.0 + lockPct / 100.0), DigitsSym());

if(candidate > g_stairLockPrice)

g_stairLockPrice = candidate;

g_stairLevel = reached;

Print(InpComment, " STAIR_UP level=", g_stairLevel,

" profitPct=", DoubleToString(profitPct, 3),

" lockPct=", DoubleToString(lockPct, 3),

" lockSl=", DoubleToString(g_stairLockPrice, DigitsSym()),

" avg=", DoubleToString(avg, DigitsSym()),

" bid=", DoubleToString(bid, DigitsSym()));

WriteTradeLog("STAIR_UP", 0, g_stairLockPrice, 0, g_cCount, avg, 0,

StringFormat("L%d_%.3f", g_stairLevel, lockPct), 0);

}

return (g_stairLevel > 0);

}

bool SpreadOk()

{

long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

if(spread < 0) return false;

return ((int)spread <= InpMaxSpreadPoints);

}

bool PricesValid(const double bid, const double ask)

{

if(bid <= 0.0 || ask <= 0.0) return false;

if(ask < bid) return false;

return true;

}

bool SymbolAllowsNewBuy()

{

ENUM_SYMBOL_TRADE_MODE mode = (ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE);

return (mode == SYMBOL_TRADE_MODE_FULL || mode == SYMBOL_TRADE_MODE_LONGONLY);

}

bool TerminalReadyForNewOrders()

{

if(IsStopped()) return false;

if(InpBlockNewOnDisconnect && !TerminalInfoInteger(TERMINAL_CONNECTED))

return false;

if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;

if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;

if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) return false;

if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) return false;

if(!SymbolAllowsNewBuy()) return false;

return true;

}

bool TerminalReadyForClose()

{

if(IsStopped()) return false;

if(!TerminalInfoInteger(TERMINAL_CONNECTED)) return false;

if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;

if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;

if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) return false;

if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) return false;

ENUM_SYMBOL_TRADE_MODE mode = (ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE);

if(mode == SYMBOL_TRADE_MODE_DISABLED) return false;

return true;

}

void SafeSleep(const int ms)

{

int wait = ms;

if(wait < 0) wait = 0;

if(wait > 2000) wait = 2000;

if(wait > 0) Sleep(wait);

}

string StartEquityGvName()

{

return "V15_SE_" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))

+ "_" + _Symbol

+ "_" + IntegerToString(InpMagic);

}

string StartTimeGvName()

{

return "V15_ST_" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))

+ "_" + _Symbol

+ "_" + IntegerToString(InpMagic);

}

void PersistStartBaseline()

{

string kEq = StartEquityGvName();

string kT = StartTimeGvName();

if(g_startEquity > 0.0)

GlobalVariableSet(kEq, g_startEquity);

if(g_eaStartTime > 0)

GlobalVariableSet(kT, (double)g_eaStartTime);

GlobalVariablesFlush();

}

void LoadOrInitStartEquity()

{

string kEq = StartEquityGvName();

string kT = StartTimeGvName();

bool haveEq = false;

if(GlobalVariableCheck(kEq))

{

double v = GlobalVariableGet(kEq);

if(v > 0.0)

{

g_startEquity = v;

haveEq = true;

}

}

if(GlobalVariableCheck(kT))

{

datetime t = (datetime)GlobalVariableGet(kT);

if(t > 0) g_eaStartTime = t;

}

if(!haveEq)

{

g_startEquity = AccountInfoDouble(ACCOUNT_EQUITY);

if(g_startEquity <= 0.0)

g_startEquity = AccountInfoDouble(ACCOUNT_BALANCE);

}

if(g_eaStartTime <= 0)

g_eaStartTime = TimeCurrent();

PersistStartBaseline();

}

void ClearStartEquityGv()

{

string kEq = StartEquityGvName();

string kT = StartTimeGvName();

if(GlobalVariableCheck(kEq)) GlobalVariableDel(kEq);

if(GlobalVariableCheck(kT)) GlobalVariableDel(kT);

GlobalVariablesFlush();

}

string LogInstanceTag()

{

string sym = _Symbol;

StringReplace(sym, ".", "_");

StringReplace(sym, "#", "_");

StringReplace(sym, " ", "_");

return sym + "_"

+ IntegerToString(InpMagic) + "_"

+ IntegerToString(ChartID()) + "_"

+ IntegerToString((int)_Period);

}

void WarnIfNettingAccount(const bool forcePrint)

{

long mode = AccountInfoInteger(ACCOUNT_MARGIN_MODE);

if(!forcePrint && mode == g_lastMarginMode) return;

g_lastMarginMode = mode;

if(mode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING)

Print(InpComment, " WARN: netting account — grid layer count may not match hedging design");

}

string DateStamp(const datetime t)

{

MqlDateTime dt;

TimeToStruct(t, dt);

return StringFormat("%04d%02d%02d", dt.year, dt.mon, dt.day);

}

datetime MidnightOfStamp(const string yyyymmdd)

{

if(StringLen(yyyymmdd) != 8) return 0;

MqlDateTime dt;

dt.year = (int)StringToInteger(StringSubstr(yyyymmdd, 0, 4));

dt.mon = (int)StringToInteger(StringSubstr(yyyymmdd, 4, 2));

dt.day = (int)StringToInteger(StringSubstr(yyyymmdd, 6, 2));

dt.hour = 0; dt.min = 0; dt.sec = 0;

return StructToTime(dt);

}

void PurgeOldDayFiles(const string prefix, const string ext, const int retainHours)

{

if(retainHours <= 0) return;

string fname = "";

long h = FileFindFirst(prefix + "*." + ext, fname);

if(h == INVALID_HANDLE) return;

do

{

int p = StringLen(prefix);

int e = StringFind(fname, "." + ext);

if(e > p + 7)

{

string stamp = StringSubstr(fname, p, 8);

datetime day0 = MidnightOfStamp(stamp);

if(day0 > 0)

{

datetime dayEnd = day0 + 86400;

if((TimeCurrent() - dayEnd) > (datetime)retainHours * 3600)

{

if(FileIsExist(fname))

{

FileDelete(fname);

Print(InpComment, " PURGE ", fname);

}

}

}

}

}

while(FileFindNext(h, fname));

FileFindClose(h);

}

void MaybePurgeLogs()

{

if(g_lastPurgeCheck > 0 && (TimeCurrent() - g_lastPurgeCheck) < 3600) return;

g_lastPurgeCheck = TimeCurrent();

if(InpEnableHeartbeat)

PurgeOldDayFiles("V15_HB_", "log", InpHbRetainHours);

if(InpEnableTradeLog)

PurgeOldDayFiles("V15_Trade_", "csv", InpTradeRetainDays * 24);

}

void WriteHeartbeatLine()

{

if(!InpEnableHeartbeat) return;

datetime now = TimeCurrent();

MqlDateTime dt;

TimeToStruct(now, dt);

datetime minuteKey = now - dt.sec;

if(minuteKey == g_lastHbMinute) return;

g_lastHbMinute = minuteKey;

string fname = "V15_HB_" + DateStamp(now) + "_" + LogInstanceTag() + ".log";

int fh = FileOpen(fname, FILE_READ | FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_SHARE_READ);

if(fh == INVALID_HANDLE)

{

Print(InpComment, " HB open fail ", GetLastError());

return;

}

FileSeek(fh, 0, SEEK_END);

bool connected = (bool)TerminalInfoInteger(TERMINAL_CONNECTED);

int spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

double avg, lo; datetime lt, ol;

int cnt = CountBuys(avg, lo, lt, ol);

double eq = AccountInfoDouble(ACCOUNT_EQUITY);

double bal = AccountInfoDouble(ACCOUNT_BALANCE);

double magicPnl = 0.0;

MagicBasketPnL(magicPnl);

string line = StringFormat(

"%s | sym=%s | conn=%d | spread=%d | pos=%d | avg=%.5f | eq=%.2f | bal=%.2f | startEq=%.2f | magicPnl=%.2f | pause=%d | fuse=%d | dayHalt=%d | weekHalt=%d | dayPnl=%.2f | weekPnl=%.2f | fatal=%d | closeLock=%d | discCnt=%d | step=%.5f | stair=%d | stairSl=%.5f",

TimeToString(now, TIME_DATE | TIME_SECONDS),

_Symbol,

connected ? 1 : 0,

spread,

cnt,

avg,

eq,

bal,

g_startEquity,

magicPnl,

PauseActive() ? 1 : 0,

g_fuseOn ? 1 : 0,

g_dailyLossHalt ? 1 : 0,

g_weeklyLossHalt ? 1 : 0,

g_dayRealizedPnl,

g_weekRealizedPnl,

g_eaFatalStop ? 1 : 0,

g_closing_in_progress ? 1 : 0,

g_disconnectCount,

g_cDynStep,

g_stairLevel,

g_stairLockPrice

);

FileWriteString(fh, line + "\n");

FileFlush(fh);

FileClose(fh);

MaybePurgeLogs();

}

void EnsureTradeCsvHeader(const int fh)

{

if(FileSize(fh) > 0) return;

FileWriteString(fh,

"timestamp,symbol,event,ticket,price,lots,basket_count,basket_avg,pnl,reason,spread,bid,ask,retcode,comment\n");

}

void WriteTradeLog(const string event,

const ulong ticket,

const double price,

const double lots,

const int basketCount,

const double basketAvg,

const double pnl,

const string reason,

const uint retcode)

{

if(!InpEnableTradeLog) return;

datetime now = TimeCurrent();

string fname = "V15_Trade_" + DateStamp(now) + "_" + LogInstanceTag() + ".csv";

int fh = FileOpen(fname, FILE_READ | FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_SHARE_READ);

if(fh == INVALID_HANDLE)

{

Print(InpComment, " TradeLog open fail ", GetLastError());

return;

}

FileSeek(fh, 0, SEEK_END);

EnsureTradeCsvHeader(fh);

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

int spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

string safeReason = reason;

StringReplace(safeReason, ",", ";");

string line = StringFormat(

"%s,%s,%s,%s,%.5f,%.2f,%d,%.5f,%.2f,%s,%d,%.5f,%.5f,%u,%s",

TimeToString(now, TIME_DATE | TIME_SECONDS),

_Symbol,

event,

IntegerToString((long)ticket),

price,

lots,

basketCount,

basketAvg,

pnl,

safeReason,

spread,

bid,

ask,

retcode,

InpComment

);

FileWriteString(fh, line + "\n");

FileFlush(fh);

FileClose(fh);

}

bool IsTransientTradeError(const uint rc)

{

return (

rc == TRADE_RETCODE_REQUOTE ||

rc == TRADE_RETCODE_PRICE_CHANGED ||

rc == TRADE_RETCODE_PRICE_OFF ||

rc == TRADE_RETCODE_CONNECTION ||

rc == TRADE_RETCODE_TIMEOUT ||

rc == TRADE_RETCODE_TOO_MANY_REQUESTS ||

rc == TRADE_RETCODE_LOCKED

);

}

void CheckConnectionState()

{

bool connected = (bool)TerminalInfoInteger(TERMINAL_CONNECTED);

if(!connected && g_wasConnected)

{

g_wasConnected = false;

g_disconnectCount++;

if(g_lastDisconnectLog == 0 || (TimeCurrent() - g_lastDisconnectLog) >= 60)

{

g_lastDisconnectLog = TimeCurrent();

InvalidatePosCache();

double avg, lo; datetime lt, ol;

int cnt = CountBuysRaw(avg, lo, lt, ol);

Print(InpComment, " DISCONNECTED #", g_disconnectCount);

WriteTradeLog("DISCONNECT", 0, 0, 0, cnt, avg, 0, "TERMINAL", 0);

}

}

else if(connected && !g_wasConnected)

{

g_wasConnected = true;

InvalidatePosCache();

double avg, lo; datetime lt, ol;

int cnt = CountBuysRaw(avg, lo, lt, ol);

Print(InpComment, " RECONNECTED");

WriteTradeLog("RECONNECT", 0, 0, 0, cnt, avg, 0, "TERMINAL", 0);

g_cacheHtfBar = g_cacheDumpBar = g_cacheHighBar = g_cacheD1Bar = g_cacheM1Bar = 0;

WarnIfNettingAccount(false);

}

}

double NormLot(double lot)

{

double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);

double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

if(step <= 0.0) step = 0.01;

if(lot < vmin) lot = vmin;

if(lot > vmax) lot = vmax;

lot = MathFloor(lot / step + 1e-8) * step;

return NormalizeDouble(lot, 2);

}

int DigitsSym() { return (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); }

string MakeOrderComment(const bool isAdd)

{

string suffix = isAdd ? "_G" : "_0";

string base = InpComment;

int keep = ORDER_COMMENT_MAX - StringLen(suffix);

if(keep < 1) keep = 1;

if(StringLen(base) > keep)

base = StringSubstr(base, 0, keep);

return base + suffix;

}

datetime DayStart(const datetime t)

{

MqlDateTime dt;

TimeToStruct(t, dt);

dt.hour = 0; dt.min = 0; dt.sec = 0;

return StructToTime(dt);

}

datetime WeekStartMonday(const datetime t)

{

MqlDateTime dt;

TimeToStruct(t, dt);

dt.hour = 0; dt.min = 0; dt.sec = 0;

datetime mid = StructToTime(dt);

int dow = dt.day_of_week;

int sinceMon = (dow == 0 ? 6 : dow - 1);

return mid - (datetime)sinceMon * 86400;

}

bool SumRealizedPnlFrom(const datetime fromTime, double &sumOut)

{

sumOut = 0.0;

if(fromTime <= 0) return false;

if(!HistorySelect(fromTime, TimeCurrent())) return false;

double sum = 0.0;

int total = HistoryDealsTotal();

for(int i = 0; i < total; ++i)

{

ulong deal = HistoryDealGetTicket(i);

if(deal == 0) continue;

if(HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol) continue;

if((long)HistoryDealGetInteger(deal, DEAL_MAGIC) != InpMagic) continue;

long entry = HistoryDealGetInteger(deal, DEAL_ENTRY);

if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT && entry != DEAL_ENTRY_OUT_BY)

continue;

sum += HistoryDealGetDouble(deal, DEAL_PROFIT)

+ HistoryDealGetDouble(deal, DEAL_SWAP)

+ HistoryDealGetDouble(deal, DEAL_COMMISSION);

}

sumOut = sum;

return true;

}

void RefreshLossHaltState(const bool force = false)

{

datetime now = TimeCurrent();

datetime day0 = DayStart(now);

datetime week0 = WeekStartMonday(now);

bool newDay = (day0 != g_dayStamp);

bool newWeek = (week0 != g_weekStamp);

bool dueScan = (g_lastPnlScan == 0 || (now - g_lastPnlScan) >= 60);

if(newDay)

{

g_dayStamp = day0;

if(g_dailyLossHalt)

{

g_dailyLossHalt = false;

Print(InpComment, " DAILY_LOSS_HALT OFF (new day)");

WriteTradeLog("DAILY_HALT_OFF", 0, 0, 0, g_cCount, g_cAvg, 0, "NEW_DAY", 0);

}

}

if(newWeek)

{

g_weekStamp = week0;

if(g_weeklyLossHalt)

{

g_weeklyLossHalt = false;

Print(InpComment, " WEEKLY_LOSS_HALT OFF (new week)");

WriteTradeLog("WEEKLY_HALT_OFF", 0, 0, 0, g_cCount, g_cAvg, 0, "NEW_WEEK", 0);

}

}

if(force || newDay || newWeek || dueScan)

{

double dayPnl = g_dayRealizedPnl;

double weekPnl = g_weekRealizedPnl;

bool dayOk = SumRealizedPnlFrom(day0, dayPnl);

bool weekOk = SumRealizedPnlFrom(week0, weekPnl);

if(dayOk) g_dayRealizedPnl = dayPnl;

if(weekOk) g_weekRealizedPnl = weekPnl;

if(dayOk || weekOk)

g_lastPnlScan = now;

}

if(InpUseDailyLossHalt && InpDailyLossLimitAmt > 0.0)

{

if(!g_dailyLossHalt && g_dayRealizedPnl <= -InpDailyLossLimitAmt)

{

g_dailyLossHalt = true;

Print(InpComment, " DAILY_LOSS_HALT ON dayPnl=", g_dayRealizedPnl,

" limit=", InpDailyLossLimitAmt);

WriteTradeLog("DAILY_HALT_ON", 0, 0, 0, g_cCount, g_cAvg, g_dayRealizedPnl,

"DAY_LOSS", 0);

}

}

if(InpUseWeeklyLossHalt && InpWeeklyLossLimitAmt > 0.0)

{

if(!g_weeklyLossHalt && g_weekRealizedPnl <= -InpWeeklyLossLimitAmt)

{

g_weeklyLossHalt = true;

Print(InpComment, " WEEKLY_LOSS_HALT ON weekPnl=", g_weekRealizedPnl,

" limit=", InpWeeklyLossLimitAmt);

WriteTradeLog("WEEKLY_HALT_ON", 0, 0, 0, g_cCount, g_cAvg, g_weekRealizedPnl,

"WEEK_LOSS", 0);

}

}

}

bool LossHaltActive()

{

return (g_dailyLossHalt || g_weeklyLossHalt);

}

bool MarginOkForLot(const double lot, const double price)

{

if(!InpUseMarginCheck) return true;

double need = 0.0;

if(!OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lot, price, need))

{

if(g_lastMarginWarn == 0 || (TimeCurrent() - g_lastMarginWarn) >= 60)

{

g_lastMarginWarn = TimeCurrent();

Print(InpComment, " MARGIN calc fail err=", GetLastError());

}

return false;

}

double freeM = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

double require = need * InpMarginSafetyMult;

if(freeM < require)

{

if(g_lastMarginWarn == 0 || (TimeCurrent() - g_lastMarginWarn) >= 60)

{

g_lastMarginWarn = TimeCurrent();

Print(InpComment, " MARGIN BLOCK free=", freeM,

" need=", require, " (raw=", need, ")");

WriteTradeLog("MARGIN_BLOCK", 0, price, lot, g_cCount, g_cAvg, 0,

"INSUFFICIENT", 0);

}

return false;

}

return true;

}

void InvalidateMagicRealizedCache()

{

g_magicRealizedDirty = true;

}

double MagicFloatingPnL()

{

double sum = 0.0;

for(int i = PositionsTotal() - 1; i >= 0; --i)

{

if(!pos.SelectByIndex(i)) continue;

if(pos.Symbol() != _Symbol || pos.Magic() != InpMagic) continue;

if(pos.PositionType() != POSITION_TYPE_BUY) continue;

sum += pos.Profit() + pos.Swap();

}

return sum;

}

bool RefreshMagicRealizedCache(const bool force)

{

int scanSec = InpMagicDdScanSec;

if(scanSec < 1) scanSec = 1;

bool due = (g_lastMagicRealizedScan == 0 ||

(TimeCurrent() - g_lastMagicRealizedScan) >= scanSec);

if(!force && !g_magicRealizedDirty && !due)

return true;

double realized = 0.0;

if(!SumRealizedPnlFrom(g_eaStartTime, realized))

return false;

g_magicRealizedCached = realized;

g_magicRealizedDirty = false;

g_lastMagicRealizedScan = TimeCurrent();

return true;

}

bool MagicBasketPnL(double &pnlOut, const bool forceRealized = false)

{

RefreshMagicRealizedCache(forceRealized || g_magicRealizedDirty);

pnlOut = g_magicRealizedCached + MagicFloatingPnL();

return true;

}

void CheckAccountDrawdownHalt()

{

if(!InpUseAccountDdHalt || InpAccountDdPct <= 0.0) return;

if(g_expertRemovePending) return;

if(g_eaFatalStop) return;

if(g_startEquity <= 0.0) return;

double magicPnl = 0.0;

MagicBasketPnL(magicPnl, false);

double lossLimit = g_startEquity * (InpAccountDdPct / 100.0);

if(magicPnl > -lossLimit) return;

MagicBasketPnL(magicPnl, true);

if(magicPnl > -lossLimit) return;

g_eaFatalStop = true;

Print(InpComment, " MAGIC_DD_HALT magicPnl=", magicPnl,

" startEq=", g_startEquity, " lossLimit=", -lossLimit,

" ddPct=", InpAccountDdPct);

WriteTradeLog("MAGIC_DD_HALT", 0, 0, 0, g_cCount, g_cAvg, magicPnl,

"MAGIC_DD", 0);

}

void TryFatalStopExit()

{

if(!g_eaFatalStop || g_expertRemovePending) return;

InvalidatePosCache();

double a, l; datetime lt, ol;

int left = CountBuysRaw(a, l, lt, ol);

if(left > 0)

{

if(!g_closing_in_progress)

CloseAllBuys("MAGIC_DD_HALT");

else if(g_close_lock_start > 0 && (TimeCurrent() - g_close_lock_start) >= 30)

{

g_closing_in_progress = false;

CloseAllBuys("MAGIC_DD_HALT");

}

return;

}

g_closing_in_progress = false;

g_close_lock_start = 0;

g_expertRemovePending = true;

ClearStartEquityGv();

Print(InpComment, " MAGIC_DD_HALT → ExpertRemove left=", left);

WriteTradeLog("MAGIC_DD_REMOVE", 0, 0, 0, left, 0, 0, "REMOVE", 0);

ExpertRemove();

}

bool HtfBull()

{

ArraySetAsSeries(g_bufF, true);

ArraySetAsSeries(g_bufS, true);

if(CopyBuffer(g_hEmaFast, 0, 0, 3, g_bufF) < 3) return false;

if(CopyBuffer(g_hEmaSlow, 0, 0, 3, g_bufS) < 3) return false;

double c1 = iClose(_Symbol, InpHtf, 1);

return (c1 > g_bufF[1] && g_bufF[1] > g_bufS[1]);

}

bool HtfBearBreak()

{

ArraySetAsSeries(g_bufF, true);

ArraySetAsSeries(g_bufS, true);

if(CopyBuffer(g_hEmaFast, 0, 0, 3, g_bufF) < 3) return false;

if(CopyBuffer(g_hEmaSlow, 0, 0, 3, g_bufS) < 3) return false;

double c1 = iClose(_Symbol, InpHtf, 1);

bool death = (g_bufF[2] >= g_bufS[2] && g_bufF[1] < g_bufS[1]);

bool below = (c1 < g_bufS[1]);

return (death || below);

}

bool FirstEntrySignal()

{

ArraySetAsSeries(g_bufK, true);

ArraySetAsSeries(g_bufD, true);

ArraySetAsSeries(g_bufBb, true);

if(CopyBuffer(g_hStoch, 0, 0, 3, g_bufK) < 3) return false;

if(CopyBuffer(g_hStoch, 1, 0, 3, g_bufD) < 3) return false;

if(CopyBuffer(g_hBb, 2, 0, 3, g_bufBb) < 3) return false;

double c1 = iClose(_Symbol, PERIOD_CURRENT, 1);

double l1 = iLow(_Symbol, PERIOD_CURRENT, 1);

bool kdGold = (g_bufK[2] <= g_bufD[2] && g_bufK[1] > g_bufD[1]);

bool kdOs = (g_bufK[1] <= InpKdMax || g_bufK[2] <= InpKdMax);

bool kdOk = (kdGold && kdOs);

bool touchBb = (l1 <= g_bufBb[1] || c1 <= g_bufBb[1]);

if(InpAllowKdOrBb) return (kdOk || touchBb);

return (kdOk && touchBb);

}

bool FloatLossHit(const double avg, const double bid)

{

if(!InpUseFloatExit || avg <= 0.0) return false;

return (bid <= avg * (1.0 - InpFloatLossPct / 100.0));

}

bool AtrSpikeHit()

{

if(!InpUseAtrSpikeExit || g_hAtrDump == INVALID_HANDLE) return false;

int need = InpAtrSmaPeriod;

if(need < 1) need = 1;

ArrayResize(g_bufAtrDump, need);

ArraySetAsSeries(g_bufAtrDump, true);

if(CopyBuffer(g_hAtrDump, 0, 1, need, g_bufAtrDump) < need) return false;

double sum = 0.0;

for(int i = 0; i < InpAtrSmaPeriod; ++i) sum += g_bufAtrDump[i];

double sma = sum / (double)InpAtrSmaPeriod;

if(sma <= 0.0) return false;

return (g_bufAtrDump[0] >= sma * InpAtrSpikeMult);

}

bool BearBarsHit()

{

if(!InpUseBearBarsExit || InpBearBarsNeed <= 0) return false;

for(int i = 1; i <= InpBearBarsNeed; ++i)

{

if(iClose(_Symbol, InpDumpTf, i) >= iOpen(_Symbol, InpDumpTf, i))

return false;

}

return true;

}

bool EarlyFlattenTrigger(const int count, const double avg, const double bid, const bool atrSpike)

{

if(InpEarlyExitOnlyIfLayers && count < InpEarlyExitMinLayers) return false;

if(FloatLossHit(avg, bid)) return true;

if(atrSpike) return true;

if(BearBarsHit()) return true;

return false;

}

bool FarBelowRecentHigh()

{

if(!InpUseHighDrawdownStop) return false;

int hh = iHighest(_Symbol, InpHighTf, MODE_HIGH, InpHighLookback, 1);

if(hh < 0) return false;

double peak = iHigh(_Symbol, InpHighTf, hh);

if(peak <= 0.0) return false;

double c = iClose(_Symbol, InpHighTf, 1);

double dd = (peak - c) / peak * 100.0;

return (dd >= InpHighDrawdownPct);

}

bool DailyBearVeto()

{

if(!InpUseDailyVeto || g_hEmaD1 == INVALID_HANDLE) return false;

ArraySetAsSeries(g_bufEmaD1, true);

if(CopyBuffer(g_hEmaD1, 0, 0, 2, g_bufEmaD1) < 2) return false;

return (iClose(_Symbol, PERIOD_D1, 1) < g_bufEmaD1[1]);

}

bool PauseActive()

{

if(!InpUseExitPause) return false;

return (g_pauseUntil > 0 && TimeCurrent() < g_pauseUntil);

}

void ArmPause(const string reason)

{

if(!InpUseExitPause || InpPauseHours <= 0) return;

g_pauseUntil = TimeCurrent() + (datetime)InpPauseHours * 3600;

Print(InpComment, " PAUSE until ", TimeToString(g_pauseUntil), " after ", reason);

}

void LogEarlyExit()

{

if(g_earlyExitCount < MAX_EXIT_LOG)

g_earlyExitLog[g_earlyExitCount++] = TimeCurrent();

else

{

for(int i = 1; i < MAX_EXIT_LOG; ++i)

g_earlyExitLog[i - 1] = g_earlyExitLog[i];

g_earlyExitLog[MAX_EXIT_LOG - 1] = TimeCurrent();

}

}

int CountEarlyInWindow()

{

if(!InpUseExitFuse) return 0;

datetime cut = TimeCurrent() - (datetime)InpFuseWindowDays * 86400;

int n = 0;

for(int i = 0; i < g_earlyExitCount; ++i)

if(g_earlyExitLog[i] >= cut) n++;

return n;

}

void UpdateFuse(const bool h1Bull)

{

if(!InpUseExitFuse) { g_fuseOn = false; return; }

if(!g_fuseOn && CountEarlyInWindow() >= InpFuseMaxEarly)

{

g_fuseOn = true;

g_fuseSawBear = !h1Bull;

Print(InpComment, " FUSE ON earlyCount=", CountEarlyInWindow(),

" windowDays=", InpFuseWindowDays);

}

if(g_fuseOn)

{

if(!h1Bull) g_fuseSawBear = true;

if(InpFuseNeedsH1BullClear)

{

if(g_fuseSawBear && h1Bull)

{

g_fuseOn = false;

g_fuseSawBear = false;

Print(InpComment, " FUSE OFF — H1 bull restored");

}

}

else if(h1Bull)

{

g_fuseOn = false;

Print(InpComment, " FUSE OFF");

}

}

}

bool RiskOffNewEntry()

{

if(g_eaFatalStop) return true;

if(PauseActive()) return true;

if(g_fuseOn) return true;

if(LossHaltActive()) return true;

if(g_cFarHigh) return true;

if(g_cDailyVeto) return true;

return false;

}

bool RiskOffAdds()

{

if(g_eaFatalStop) return true;

if(g_fuseOn) return true;

if(LossHaltActive()) return true;

if(g_cFarHigh) return true;

if(g_cDailyVeto) return true;

return false;

}

int CountBuysRaw(double &avg, double &lowest, datetime &lastT, datetime &oldest)

{

int n = 0;

double cost = 0.0, vol = 0.0;

avg = 0.0; lowest = 0.0; lastT = 0; oldest = 0;

bool have = false;

for(int i = PositionsTotal() - 1; i >= 0; --i)

{

if(!pos.SelectByIndex(i)) continue;

if(pos.Symbol() != _Symbol || pos.Magic() != InpMagic) continue;

if(pos.PositionType() != POSITION_TYPE_BUY) continue;

n++;

double p = pos.PriceOpen();

double v = pos.Volume();

cost += p * v; vol += v;

if(!have || p < lowest) { lowest = p; have = true; }

if(pos.Time() > lastT) lastT = pos.Time();

if(oldest == 0 || pos.Time() < oldest) oldest = pos.Time();

}

avg = (vol > 0.0 ? cost / vol : 0.0);

return n;

}

void InvalidatePosCache() { g_posDirty = true; }

int CountBuys(double &avg, double &lowest, datetime &lastT, datetime &oldest)

{

if(g_posDirty)

{

g_cCount = CountBuysRaw(g_cAvg, g_cLowest, g_cLastT, g_cOldest);

g_posDirty = false;

}

avg = g_cAvg;

lowest = g_cLowest;

lastT = g_cLastT;

oldest = g_cOldest;

return g_cCount;

}

bool CloseAllBuys(const string reason)

{

if(g_closing_in_progress)

{

if(g_close_lock_start > 0 && (TimeCurrent() - g_close_lock_start) < 30)

{

if(StringLen(reason) > 0) g_pendingCloseReason = reason;

return false;

}

Print(InpComment, " CLOSE LOCK TIMEOUT retry ", reason);

}

if(!TerminalReadyForClose())

{

g_closing_in_progress = true;

if(g_close_lock_start == 0) g_close_lock_start = TimeCurrent();

g_pendingCloseReason = reason;

g_closeDeferredOffline = true;

if(g_lastCloseWarn == 0 || (TimeCurrent() - g_lastCloseWarn) >= 60)

{

g_lastCloseWarn = TimeCurrent();

Print(InpComment, " CLOSE deferred (terminal/trade not ready) ", reason);

}

return false;

}

g_closing_in_progress = true;

g_close_lock_start = TimeCurrent();

g_pendingCloseReason = reason;

g_closeDeferredOffline = false;

trade.SetExpertMagicNumber(InpMagic);

trade.SetDeviationInPoints(40);

trade.SetTypeFillingBySymbol(_Symbol);

trade.SetAsyncMode(false);

bool ok = true;

for(int i = PositionsTotal() - 1; i >= 0; --i)

{

if(!pos.SelectByIndex(i)) continue;

if(pos.Symbol() != _Symbol || pos.Magic() != InpMagic) continue;

if(pos.PositionType() != POSITION_TYPE_BUY) continue;

ulong ticket = pos.Ticket();

double px = pos.PriceOpen();

double lots = pos.Volume();

double pnl = pos.Profit() + pos.Swap();

int cntBefore = g_cCount;

double avgBefore = g_cAvg;

bool closed = false;

uint lastRc = 0;

for(int attempt = 0; attempt <= InpOrderRetryMax; ++attempt)

{

if(!TerminalReadyForClose())

{

lastRc = TRADE_RETCODE_CONNECTION;

break;

}

if(trade.PositionClose(ticket))

{

closed = true;

break;

}

lastRc = trade.ResultRetcode();

if(!PositionSelectByTicket(ticket))

{

closed = true;

break;

}

if(!IsTransientTradeError(lastRc) || attempt >= InpOrderRetryMax)

break;

SafeSleep(InpOrderRetryMs);

}

if(closed)

{

WriteTradeLog("CLOSE", ticket, px, lots, cntBefore, avgBefore, pnl, reason, 0);

}

else

{

ok = false;

Print(InpComment, " CLOSE FAIL ", reason, " ticket=", ticket,

" rc=", lastRc, " ", trade.ResultRetcodeDescription());

WriteTradeLog("CLOSE_FAIL", ticket, px, lots, cntBefore, avgBefore, pnl, reason, lastRc);

}

}

InvalidatePosCache();

double a, l; datetime lt, ol;

int left = CountBuysRaw(a, l, lt, ol);

if(left == 0)

{

g_closing_in_progress = false;

g_close_lock_start = 0;

g_pendingCloseReason = "";

g_closeDeferredOffline = false;

g_initPrice = 0.0;

g_gridStep = 0.0;

g_lastSyncedSl = 0.0;

ResetStairLock();

Print(InpComment, " CLOSE_ALL ", reason);

WriteTradeLog("CLOSE_ALL", 0, 0, 0, 0, 0, 0, reason, 0);

InvalidateMagicRealizedCache();

RefreshLossHaltState(true);

bool isEarly = (StringFind(reason, "EARLY") == 0);

bool isSl = (reason == "SL_0.976");

if(isEarly)

{

LogEarlyExit();

ArmPause(reason);

}

else if(isSl)

ArmPause(reason);

}

return ok;

}

void MaintainCloseLock()

{

if(!g_closing_in_progress) return;

InvalidatePosCache();

double a, l; datetime lt, ol;

int left = CountBuysRaw(a, l, lt, ol);

if(left == 0)

{

g_closing_in_progress = false;

g_close_lock_start = 0;

g_pendingCloseReason = "";

g_closeDeferredOffline = false;

g_initPrice = 0.0;

g_gridStep = 0.0;

ResetStairLock();

return;

}

if(!TerminalReadyForClose())

{

g_closeDeferredOffline = true;

return;

}

bool timedOut = (g_close_lock_start > 0 && (TimeCurrent() - g_close_lock_start) >= 30);

if(timedOut || g_closeDeferredOffline)

{

string why = (StringLen(g_pendingCloseReason) > 0 ? g_pendingCloseReason : "CLOSE_RETRY");

Print(InpComment, " CLOSE LOCK retry remaining=", left, " reason=", why);

g_closeDeferredOffline = false;

g_closing_in_progress = false;

CloseAllBuys(why);

}

}

void SyncSl(const double slPrice, const bool force = false)

{

if(g_eaFatalStop || g_closing_in_progress) return;

datetime bar = iTime(_Symbol, PERIOD_CURRENT, 0);

if(!force && bar != 0 && bar == g_lastSyncSlBar &&

MathAbs(g_lastSyncedSl - slPrice) <= _Point)

return;

int digits = DigitsSym();

double sl = NormalizeDouble(slPrice, digits);

long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);

long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);

double minD = (double)MathMax(stops, freeze) * _Point;

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

if(bid <= 0.0) return;

if(sl >= bid - minD)

sl = NormalizeDouble(bid - MathMax(minD, 10 * _Point), digits);

trade.SetExpertMagicNumber(InpMagic);

trade.SetDeviationInPoints(40);

for(int i = PositionsTotal() - 1; i >= 0; --i)

{

if(!pos.SelectByIndex(i)) continue;

if(pos.Symbol() != _Symbol || pos.Magic() != InpMagic) continue;

if(pos.PositionType() != POSITION_TYPE_BUY) continue;

if(MathAbs(pos.StopLoss() - sl) > _Point)

trade.PositionModify(pos.Ticket(), sl, 0.0);

}

g_lastSyncedSl = sl;

g_lastSyncSlBar = bar;

}

double GetMultiAtrStep(double basePrice)

{

double step = basePrice * InpGridStepFrac;

if(step <= 0.0) step = 10 * _Point;

if(!InpUseMultiAtrStep) return step;

if(CopyBuffer(g_hAtrM1, 0, 1, 1, g_bufAtr1) < 1) return step;

if(CopyBuffer(g_hAtrM5, 0, 1, 1, g_bufAtr5) < 1) return step;

if(CopyBuffer(g_hAtrM15, 0, 1, 1, g_bufAtr15) < 1) return step;

if(CopyBuffer(g_hAtrM30, 0, 1, 1, g_bufAtr30) < 1) return step;

if(CopyBuffer(g_hAtrH1, 0, 1, 1, g_bufAtrH1) < 1) return step;

double sM1 = g_bufAtr1[0] * InpAtrMultM1;

double sM5 = g_bufAtr5[0] * InpAtrMultM5;

double sM15 = g_bufAtr15[0] * InpAtrMultM15;

double sM30 = g_bufAtr30[0] * InpAtrMultM30;

double sH1 = g_bufAtrH1[0] * InpAtrMultH1;

step = MathMax(step, sM1);

step = MathMax(step, sM5);

step = MathMax(step, sM15);

step = MathMax(step, sM30);

step = MathMax(step, sH1);

return step;

}

void RefreshCaches()

{

datetime htfBar = iTime(_Symbol, InpHtf, 0);

if(htfBar != 0 && htfBar != g_cacheHtfBar)

{

g_cacheHtfBar = htfBar;

g_cH1Bull = HtfBull();

g_cHtfBreak = HtfBearBreak();

}

datetime dumpBar = iTime(_Symbol, InpDumpTf, 0);

if(dumpBar != 0 && dumpBar != g_cacheDumpBar)

{

g_cacheDumpBar = dumpBar;

g_cAtrSpike = AtrSpikeHit();

}

datetime highBar = iTime(_Symbol, InpHighTf, 0);

if(highBar != 0 && highBar != g_cacheHighBar)

{

g_cacheHighBar = highBar;

g_cFarHigh = FarBelowRecentHigh();

}

datetime d1Bar = iTime(_Symbol, PERIOD_D1, 0);

if(d1Bar != 0 && d1Bar != g_cacheD1Bar)

{

g_cacheD1Bar = d1Bar;

g_cDailyVeto = DailyBearVeto();

}

datetime m1Bar = iTime(_Symbol, PERIOD_M1, 0);

if(m1Bar != 0 && m1Bar != g_cacheM1Bar)

{

g_cacheM1Bar = m1Bar;

double base = (g_initPrice > 0.0 ? g_initPrice : SymbolInfoDouble(_Symbol, SYMBOL_ASK));

g_cDynStep = GetMultiAtrStep(base);

}

}

bool OpenBuy(const bool isAdd)

{

if(g_eaFatalStop || g_expertRemovePending) return false;

if(!SpreadOk()) return false;

if(!TerminalReadyForNewOrders()) return false;

if(g_closing_in_progress) return false;

trade.SetExpertMagicNumber(InpMagic);

trade.SetDeviationInPoints(40);

trade.SetTypeFillingBySymbol(_Symbol);

trade.SetAsyncMode(false);

double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

if(!PricesValid(bid, ask)) return false;

double lot = NormLot(InpLots);

if(lot <= 0.0) return false;

if(!MarginOkForLot(lot, ask)) return false;

double prevInit = g_initPrice;

double prevStep = g_gridStep;

if(!isAdd)

{

g_initPrice = ask;

g_gridStep = GetMultiAtrStep(g_initPrice);

g_cDynStep = g_gridStep;

}

double sl = NormalizeDouble(g_initPrice * InpSlFrac, DigitsSym());

string cmt = MakeOrderComment(isAdd);

bool ok = false;

uint lastRc = 0;

for(int attempt = 0; attempt <= InpOrderRetryMax; ++attempt)

{

ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

if(!PricesValid(bid, ask))

{

lastRc = TRADE_RETCODE_PRICE_OFF;

break;

}

if(attempt > 0)

{

if(!isAdd && g_initPrice <= 0.0) g_initPrice = ask;

}

if(!MarginOkForLot(lot, ask))

{

lastRc = TRADE_RETCODE_NO_MONEY;

break;

}

if(trade.Buy(lot, _Symbol, ask, sl, 0.0, cmt))

{

ok = true;

break;

}

lastRc = trade.ResultRetcode();

if(!IsTransientTradeError(lastRc) || attempt >= InpOrderRetryMax)

break;

Print(InpComment, " BUY retry ", attempt + 1, "/", InpOrderRetryMax,

" rc=", lastRc);

SafeSleep(InpOrderRetryMs);

}

if(ok)

{

InvalidatePosCache();

double a, l; datetime lt, ol;

int cnt = CountBuysRaw(a, l, lt, ol);

double prStep = isAdd ? GetMultiAtrStep(g_initPrice) : g_gridStep;

Print(InpComment, isAdd ? " GRID" : " OPEN0",

" ask=", ask, " step=", prStep);

WriteTradeLog(isAdd ? "ADD" : "OPEN", trade.ResultOrder(), ask, lot, cnt, a, 0,

isAdd ? "GRID" : "FIRST", 0);

SyncSl(g_initPrice * InpSlFrac, true);

}

else

{

if(!isAdd)

{

g_initPrice = prevInit;

g_gridStep = prevStep;

}

Print(InpComment, " BUY FAIL ", lastRc, " ", trade.ResultRetcodeDescription());

WriteTradeLog(isAdd ? "ADD_FAIL" : "OPEN_FAIL", 0, ask, lot, g_cCount, g_cAvg, 0,

"BUY_FAIL", lastRc);

}

return ok;

}

void RecoverInitFromPositions()

{

double hi = 0.0;

for(int i = PositionsTotal() - 1; i >= 0; --i)

{

if(!pos.SelectByIndex(i)) continue;

if(pos.Symbol() != _Symbol || pos.Magic() != InpMagic) continue;

if(pos.PositionType() != POSITION_TYPE_BUY) continue;

if(pos.PriceOpen() > hi) hi = pos.PriceOpen();

}

if(hi > 0.0)

{

g_initPrice = hi;

g_gridStep = GetMultiAtrStep(g_initPrice);

g_cDynStep = g_gridStep;

}

}

int OnInit()

{

trade.SetExpertMagicNumber(InpMagic);

trade.SetAsyncMode(false);

if(InpLots <= 0.0)

{

Print(InpComment, " FATAL: InpLots must be > 0");

return INIT_FAILED;

}

if(InpMarginSafetyMult < 1.0)

Print(InpComment, " WARN: InpMarginSafetyMult < 1.0 — using unsafe margin buffer");

if(InpAccountDdPct > 0.0 && InpAccountDdPct < 1.0)

Print(InpComment, " WARN: InpAccountDdPct < 1% — very tight DD halt");

if(InpOrderRetryMax < 0)

Print(InpComment, " WARN: InpOrderRetryMax < 0");

g_lastMarginMode = -1;

WarnIfNettingAccount(true);

g_hEmaFast = iMA(_Symbol, InpHtf, InpHtfEmaFast, 0, MODE_EMA, PRICE_CLOSE);

g_hEmaSlow = iMA(_Symbol, InpHtf, InpHtfEmaSlow, 0, MODE_EMA, PRICE_CLOSE);

g_hStoch = iStochastic(_Symbol, PERIOD_CURRENT, InpKPeriod, InpDPeriod, InpSlowing,

MODE_SMA, STO_LOWHIGH);

g_hBb = iBands(_Symbol, PERIOD_CURRENT, InpBbPeriod, 0, InpBbDev, PRICE_CLOSE);

g_hAtrDump = iATR(_Symbol, InpDumpTf, InpAtrPeriod);

g_hEmaD1 = iMA(_Symbol, PERIOD_D1, InpDailyEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);

g_hAtrM1 = iATR(_Symbol, PERIOD_M1, InpDynAtrPeriod);

g_hAtrM5 = iATR(_Symbol, PERIOD_M5, InpDynAtrPeriod);

g_hAtrM15 = iATR(_Symbol, PERIOD_M15, InpDynAtrPeriod);

g_hAtrM30 = iATR(_Symbol, PERIOD_M30, InpDynAtrPeriod);

g_hAtrH1 = iATR(_Symbol, PERIOD_H1, InpDynAtrPeriod);

if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE ||

g_hStoch == INVALID_HANDLE || g_hBb == INVALID_HANDLE ||

g_hAtrDump == INVALID_HANDLE || g_hEmaD1 == INVALID_HANDLE ||

g_hAtrM1 == INVALID_HANDLE || g_hAtrM5 == INVALID_HANDLE ||

g_hAtrM15 == INVALID_HANDLE || g_hAtrM30 == INVALID_HANDLE ||

g_hAtrH1 == INVALID_HANDLE)

return INIT_FAILED;

ArrayInitialize(g_earlyExitLog, 0);

g_earlyExitCount = 0;

g_pauseUntil = 0;

g_fuseOn = false;

g_fuseSawBear = false;

g_closing_in_progress = false;

g_posDirty = true;

g_pendingCloseReason = "";

g_closeDeferredOffline = false;

g_cacheHtfBar = g_cacheDumpBar = g_cacheHighBar = g_cacheD1Bar = g_cacheM1Bar = 0;

g_lastHbMinute = 0;

g_lastPurgeCheck = 0;

g_wasConnected = (bool)TerminalInfoInteger(TERMINAL_CONNECTED);

g_disconnectCount = 0;

g_dayStamp = 0;

g_weekStamp = 0;

g_dailyLossHalt = false;

g_weeklyLossHalt = false;

g_eaFatalStop = false;

g_expertRemovePending = false;

g_lastCloseWarn = 0;

g_eaStartTime = 0;

g_magicRealizedCached = 0.0;

g_lastMagicRealizedScan = 0;

g_magicRealizedDirty = true;

ResetStairLock();

if(InpUseStairTp)

{

for(int s = 2; s <= STAIR_LEVELS; ++s)

{

double prevAct = StairActPct(s - 1);

double curAct = StairActPct(s);

if(prevAct > 0.0 && curAct > 0.0 && curAct <= prevAct)

Print(InpComment, " WARN: Stair act pct not ascending at level ", s);

}

}

LoadOrInitStartEquity();

RefreshMagicRealizedCache(true);

RefreshLossHaltState(true);

double a0, l0; datetime lt0, ol0;

if(CountBuysRaw(a0, l0, lt0, ol0) > 0 && g_initPrice <= 0.0)

RecoverInitFromPositions();

if(StringLen(InpComment) + 2 > ORDER_COMMENT_MAX)

Print(InpComment, " WARN: InpComment too long; will truncate for order comment (max ",

ORDER_COMMENT_MAX, ")");

EventSetTimer(1);

Print(InpComment, " m11150809 MultiATR stairMax=", STAIR_LEVELS,

" stairTp=", InpUseStairTp ? 1 : 0,

" stairLockOnly=", InpStairLockOnly ? 1 : 0,

" | startEq=", g_startEquity,

" startTime=", TimeToString(g_eaStartTime),

" magicDdPct=", InpAccountDdPct,

" magicDdScanSec=", InpMagicDdScanSec,

" marginCheck=", InpUseMarginCheck,

" blockDisc=", InpBlockNewOnDisconnect,

" dayLimit=", InpDailyLossLimitAmt,

" logTag=", LogInstanceTag());

return INIT_SUCCEEDED;

}

void OnDeinit(const int reason)

{

EventKillTimer();

if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast);

if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow);

if(g_hStoch != INVALID_HANDLE) IndicatorRelease(g_hStoch);

if(g_hBb != INVALID_HANDLE) IndicatorRelease(g_hBb);

if(g_hAtrDump != INVALID_HANDLE) IndicatorRelease(g_hAtrDump);

if(g_hEmaD1 != INVALID_HANDLE) IndicatorRelease(g_hEmaD1);

if(g_hAtrM1 != INVALID_HANDLE) IndicatorRelease(g_hAtrM1);

if(g_hAtrM5 != INVALID_HANDLE) IndicatorRelease(g_hAtrM5);

if(g_hAtrM15 != INVALID_HANDLE) IndicatorRelease(g_hAtrM15);

if(g_hAtrM30 != INVALID_HANDLE) IndicatorRelease(g_hAtrM30);

if(g_hAtrH1 != INVALID_HANDLE) IndicatorRelease(g_hAtrH1);

}

void OnTimer()

{

if(g_expertRemovePending) return;

CheckConnectionState();

WarnIfNettingAccount(false);

RefreshLossHaltState();

CheckAccountDrawdownHalt();

if(g_eaFatalStop)

{

MaintainCloseLock();

TryFatalStopExit();

return;

}

WriteHeartbeatLine();

}

void OnTradeTransaction(const MqlTradeTransaction &trans,

const MqlTradeRequest &request,

const MqlTradeResult &result)

{

if(trans.type == TRADE_TRANSACTION_DEAL_ADD ||

trans.type == TRADE_TRANSACTION_HISTORY_ADD ||

trans.type == TRADE_TRANSACTION_POSITION)

{

InvalidatePosCache();

if(trans.type == TRADE_TRANSACTION_DEAL_ADD && trans.deal != 0 &&

HistoryDealSelect(trans.deal))

{

if(HistoryDealGetString(trans.deal, DEAL_SYMBOL) == _Symbol &&

(long)HistoryDealGetInteger(trans.deal, DEAL_MAGIC) == InpMagic)

{

long entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);

if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT ||

entry == DEAL_ENTRY_OUT_BY)

{

InvalidateMagicRealizedCache();

RefreshLossHaltState(true);

}

}

}

}

}

void OnTick()

{

if(g_expertRemovePending) return;

CheckConnectionState();

CheckAccountDrawdownHalt();

if(g_eaFatalStop)

{

MaintainCloseLock();

TryFatalStopExit();

return;

}

if(IsStopped()) return;

MaintainCloseLock();

if(g_closing_in_progress) return;

RefreshCaches();

double avg = 0.0, lowest = 0.0;

datetime lastT = 0, oldest = 0;

int count = CountBuys(avg, lowest, lastT, oldest);

if(count == 0)

{

g_initPrice = 0.0;

g_gridStep = 0.0;

ResetStairLock();

}

else if(g_initPrice <= 0.0)

RecoverInitFromPositions();

UpdateFuse(g_cH1Bull);

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

if(!PricesValid(bid, ask)) return;

if(count > 0 && InpFlattenOnHighDD && g_cFarHigh)

{

CloseAllBuys("RISKOFF_FROM_HIGH");

ArmPause("RISKOFF_FROM_HIGH");

return;

}

if(count > 0 && avg > 0.0)

{

if(EarlyFlattenTrigger(count, avg, bid, g_cAtrSpike))

{

string why = "EARLY";

if(FloatLossHit(avg, bid)) why = "EARLY_FLOAT";

else if(g_cAtrSpike) why = "EARLY_ATR_H1";

else if(BearBarsHit()) why = "EARLY_BEAR_H1";

CloseAllBuys(why);

return;

}

}

if(count > 0 && InpExitOnHtfBear && g_cHtfBreak)

{

CloseAllBuys("HTF_BEAR_BREAK");

ArmPause("HTF_BEAR_BREAK");

return;

}

if(count > 0 && avg > 0.0 && g_initPrice > 0.0)

{

double baseSl = g_initPrice * InpSlFrac;

double effectiveSl = baseSl;

if(InpUseStairTp)

{

UpdateStairLock(avg, bid);

if(g_stairLevel > 0 && g_stairLockPrice > 0.0)

effectiveSl = MathMax(baseSl, g_stairLockPrice);

}

SyncSl(effectiveSl, false);

if(bid <= effectiveSl)

{

if(g_stairLevel > 0)

CloseAllBuys("STAIR_LOCK");

else

CloseAllBuys("SL_0.976");

return;

}

if(!InpUseStairTp || !InpStairLockOnly)

{

double tpPrice = avg * TpMultByCount(count);

if(bid >= tpPrice)

{

Print(InpComment, " TP count=", count, " avg=", avg);

CloseAllBuys("BASKET_TP");

return;

}

}

bool allowAdd = true;

if(InpBlockAddOnBear && !g_cH1Bull) allowAdd = false;

if(InpPauseAddOnAtrSpike && g_cAtrSpike) allowAdd = false;

if(InpUseBearBarsExit && BearBarsHit()) allowAdd = false;

if(RiskOffAdds()) allowAdd = false;

double dynStep = (g_cDynStep > 0.0 ? g_cDynStep : GetMultiAtrStep(g_initPrice));

if(allowAdd && count < InpMaxGridLayers && dynStep > 0.0)

{

int barsPassed = (lastT > 0 ? iBarShift(_Symbol, PERIOD_CURRENT, lastT) : 0);

if(barsPassed < 0) barsPassed = 0;

if(barsPassed >= InpCoolBars && ask <= lowest - dynStep)

OpenBuy(true);

}

}

datetime t = iTime(_Symbol, PERIOD_CURRENT, 0);

if(t == 0 || t == g_lastBar) return;

g_lastBar = t;

if(count != 0) return;

if(RiskOffNewEntry()) return;

if(!g_cH1Bull) return;

if(!FirstEntrySignal()) return;

OpenBuy(false);

}