//+------------------------------------------------------------------+ //| AXIOM-Delta_Forex_v1.0.mq5 | //| AXIOM-DELTA FOREX - PROFESSIONAL MT5 EXPERT | //| | //| Multi-symbol statistical mean-reversion Expert Advisor, | //| dedicated to the SPOT FOREX market. | //| | //| Ported from the AXIOM-Delta v3.8 champion engine configuration | //| and adapted to forex microstructure: | //| * XS layer : cross-sectional mean reversion across the basket | //| * MR layer : single-symbol rolling z-score mean reversion | //| * FC layer : intentionally NOT ported - spot forex has no | //| funding rates, so funding arbitrage is impossible | //| * Discipline gates : daily loss limit / consecutive-loss pause | //| / max drawdown halt | //| * Meta-label : adaptive position sizing from rolling layer | //| performance (win-rate multiplier) | //| * Forex filters : max spread, trading session, rollover | //| blackout, ATR volatility band, high-impact | //| economic-news blackout, weekend flatten | //| * Risk engine : risk-%-of-equity sizing, tick-value accurate, | //| margin pre-check, stops-level compliance, total | //| open-risk cap, netting/hedging account support | //| * On-chart HUD : broker connection status + ping, AutoTrade | //| state, engine state, per-symbol monitor (z-scores,| //| state, direction/lots/PnL), layer stats, | //| discipline gauges, account summary | //| * Resilience : single-instance lock, state persistence across | //| restarts (cooldowns, discipline, meta-label), | //| order retries on requote/price-off, guarded news | //| calendar calls (auto-degrades if unavailable) | //| | //| INSTALL | //| 1. Copy this file into \MQL5\Experts\ | //| (MetaEditor: File -> Open Data Folder) | //| 2. Open it in MetaEditor and press F7 (Compile) | //| 3. Attach the EA to ANY ONE chart (recommended EURUSD H1) - | //| the EA scans the whole symbol basket itself | //| 4. Make sure the "Algo Trading" toolbar button is ON | //| | //| NOTES | //| * Symbol names must match YOUR broker exactly (suffixes like | //| EURUSD.a or EURUSDm must be written in the basket input). | //| * All hours are BROKER (server) time, not local time. | //| * SL/TP are catastrophic backstops; the primary exit is the | //| statistical signal itself (z-score reversion). | //| * Runs in the Strategy Tester (multi-symbol). The news filter | //| and the dashboard are automatically disabled in non-visual | //| tester/optimization runs. | //| | //| RISK WARNING: trading leveraged FX involves substantial risk of | //| loss. Past performance of any statistical model does not | //| guarantee future results. Use on demo first. | //+------------------------------------------------------------------+ #property copyright "AXIOM-Delta Trading Systems" #property link "" #property version "1.00" #property description "Professional multi-symbol statistical mean-reversion EA, dedicated to spot FOREX." #property description "Layers: XS cross-sectional mean reversion + MR single-symbol z-score reversion." #property description "Discipline gates, meta-label adaptive sizing, spread/session/rollover/news/weekend filters." #property description "On-chart HUD: broker connection status, engine state and active-symbol monitor." #property description "The crypto FC (funding-rate) layer is not ported: spot forex has no funding rates." #property description "Attach to any single chart (recommended EURUSD H1); the EA scans the whole basket itself." #include //+------------------------------------------------------------------+ //| Constants | //+------------------------------------------------------------------+ #define PFX "AXDP_" // panel object prefix #define NLAYERS 2 #define LAYER_XS 0 #define LAYER_MR 1 #define META_CAP 32 // ring buffer capacity for meta-label #define ATR_PERIOD 14 #define PANEL_MAXSYM 14 // max symbol rows shown in the HUD #define TITLE_H 24 // HUD title strip height, px //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "==== 1. GENERAL ====" input long InpMagic = 38380; // Base magic number (XS = base, MR = base+1) input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Working timeframe (all layers) input string InpSymbols = "EURUSD,GBPUSD,USDJPY,AUDUSD,USDCAD,USDCHF,NZDUSD,EURGBP,EURJPY,GBPJPY,AUDJPY,EURCHF"; // Forex basket (comma separated, broker names) input int InpScanSeconds = 10; // Signal scan interval, seconds (min 2) input int InpSlippage = 10; // Max slippage / deviation, points input bool InpVerboseLog = true; // Verbose journal logging input bool InpNotifyPush = false; // Push notifications on critical events input group "==== 2. XS LAYER - CROSS-SECTIONAL MEAN REVERSION ====" input bool InpXsEnabled = true; // Enable XS layer input double InpXsZEntry = 2.9; // Entry |z| threshold vs basket input double InpXsZExit = 1.0; // Exit |z| threshold input int InpXsMomPeriod = 3; // Momentum lookback, bars input int InpXsCooldown = 12; // Cooldown after exit, bars input int InpXsMaxPos = 6; // Max simultaneous XS positions input double InpXsRiskPct = 0.50; // Risk per position, % of equity input double InpXsSlPct = 3.0; // Stop-loss backstop, % of price input double InpXsTpPct = 5.0; // Take-profit backstop, % of price input group "==== 3. MR LAYER - SINGLE-SYMBOL Z-SCORE ====" input bool InpMrEnabled = true; // Enable MR layer input double InpMrZEntry = 3.6; // Entry |z| threshold input double InpMrZExit = 1.2; // Exit |z| threshold input int InpMrLookback = 96; // Rolling window, bars input int InpMrCooldown = 12; // Cooldown after exit, bars input int InpMrMaxPos = 4; // Max simultaneous MR positions input double InpMrRiskPct = 0.35; // Risk per position, % of equity input double InpMrSlPct = 2.5; // Stop-loss backstop, % of price input double InpMrTpPct = 4.0; // Take-profit backstop, % of price input group "==== 4. RISK & DISCIPLINE ====" input double InpDailyLossLimitPct = 2.0; // Daily loss limit, % (halts new entries for the day) input double InpMaxDdPct = 5.0; // Max equity drawdown from peak, % (halts until restart) input int InpMaxConsecLosses = 4; // Consecutive losses triggering a pause input int InpPauseBars = 24; // Pause duration, bars input double InpMaxTotalRiskPct = 3.0; // Max total open risk, % of equity input double InpMinMarginLevel = 300.0; // Min margin level for new entries, % (0 = off) input double InpFixedLots = 0.0; // Fixed lots override (0 = risk-based sizing) input bool InpCloseOnDailyHalt = false; // Flatten all positions when daily halt triggers input bool InpCloseBeforeWeekend = true; // Close all positions before the weekend input int InpFridayCloseHour = 21; // Friday flatten hour, server time input group "==== 5. FOREX FILTERS ====" input int InpMaxSpreadPoints = 30; // Max spread for entries, points input bool InpSessionFilter = false; // Restrict entries to trading hours input int InpSessionStartHour = 7; // Session start hour, server time input int InpSessionEndHour = 21; // Session end hour, server time input bool InpAvoidRollover = true; // Block entries around 00:00 rollover input double InpMinAtrPct = 0.02; // Min ATR(14) as % of price (dead-market filter) input double InpMaxAtrPct = 1.50; // Max ATR(14) as % of price (chaos filter) input bool InpNewsFilter = true; // High-impact economic news filter (live only) input int InpNewsBufferMin = 30; // News blackout window, minutes before/after input group "==== 6. META-LABEL (ADAPTIVE SIZING) ====" input bool InpMetaLabel = true; // Adaptive sizing from rolling layer performance input int InpMetaWindow = 20; // Rolling performance window, trades (5..32) input group "==== 7. DASHBOARD (HUD) ====" input bool InpShowPanel = true; // Show on-chart dashboard input ENUM_BASE_CORNER InpPanelCorner = CORNER_LEFT_UPPER; // Dashboard corner input int InpPanelX = 12; // Margin from chart edge, px input int InpPanelY = 24; // Margin from chart edge, px input int InpPanelWidth = 400; // Dashboard width, px input int InpFontSize = 9; // Body font size input color InpColBg = C'16,20,28'; // Background input color InpColBorder = C'70,80,95'; // Border input color InpColTitle = C'0,210,170'; // Title / accents input color InpColText = clrWhiteSmoke; // Normal text input color InpColGood = C'90,220,130'; // Positive input color InpColBad = C'255,90,90'; // Negative input color InpColWarn = C'255,170,60'; // Warning input color InpColDim = C'140,150,165'; // Muted //+------------------------------------------------------------------+ //| Types | //+------------------------------------------------------------------+ enum ESymbolState { SS_OK = 0, // tradable / holding SS_COOLDOWN = 1, // in post-exit cooldown SS_SPREAD = 2, // spread too wide SS_NEWS = 3, // high-impact news blackout SS_SESSION = 4, // outside session / rollover SS_VOLAT = 5, // ATR outside band SS_DATA = 6, // not enough history yet SS_MAXPOS = 7, // layer position cap reached SS_NETTING = 8, // blocked: netting account, other layer on symbol SS_BLOCKED = 9 // blocked: engine-level halt }; struct SSymbol { string name; // broker symbol name string base; // base currency (news filter) string prof; // profit currency (news filter) double point; double tickSize; double tickValue; double volMin; double volMax; double volStep; int digits; long stopsLevel; // min stop distance, points int atrHandle; datetime lastBarTime; // time of current forming bar int cd[NLAYERS]; // remaining cooldown bars per layer double xsMom; // cross-sectional momentum input double xsZ; // cross-sectional z-score double mrZ; // rolling z-score double atrPct; // ATR(14) as % of price double close1; // last closed close int xsDir; // bound XS position direction (+1/-1/0) int mrDir; // bound MR position direction ulong xsTicket; ulong mrTicket; double xsLots; double mrLots; double posPnl; // floating PnL of our positions on symbol int state; // ESymbolState bool dataOk; }; struct SMeta { int res[META_CAP]; // 1 = win, 0 = loss (ring, newest at head-1) int head; int count; int trades; int wins; }; struct SPending { ulong posId; // POSITION_IDENTIFIER being accumulated string sym; int layer; double pnl; // accumulated realized pnl (incl. swap/commission) }; //+------------------------------------------------------------------+ //| Globals | //+------------------------------------------------------------------+ CTrade g_trade; SSymbol g_sym[]; int g_symCount = 0; bool g_hedging = false; bool g_tester = false; bool g_visual = false; bool g_optim = false; bool g_warm = false; // first full data pass completed bool g_prevConn = true; datetime g_initTime = 0; datetime g_lastScan = 0; datetime g_lastSave = 0; datetime g_lastNewsRef = 0; int g_dayStamp = 0; double g_dayStartEquity = 0.0; double g_equityPeak = 0.0; double g_layerDayPnl[NLAYERS] = {0.0, 0.0}; int g_layerPos[NLAYERS] = {0, 0}; int g_consec = 0; datetime g_pauseUntil = 0; bool g_ddHalt = false; bool g_dailyHalt = false; string g_blockReason = ""; SMeta g_meta[NLAYERS]; // news cache string g_cur[]; int g_curCount = 0; string g_newsCur[]; datetime g_newsTime[]; int g_newsCount = 0; bool g_newsOk = true; // pending closed positions (stats finalization) SPending g_pend[]; int g_pendCount = 0; // HUD state bool g_panelMin = false; int g_panelH = 100; //+------------------------------------------------------------------+ //| Small utilities | //+------------------------------------------------------------------+ void Log(const string msg) { if(InpVerboseLog) Print("[AXD] ", msg); } void LogErr(const string msg) { Print("[AXD][ERR] ", msg); } void Notify(const string msg) { if(g_tester || g_optim || !InpNotifyPush) return; if(!SendNotification(msg)) Log("push notification failed, err=" + IntegerToString(GetLastError())); } int LayerMagic(const int layer) { return (int)InpMagic + layer; } string LayerTag(const int layer) { return (layer == LAYER_XS) ? "XS" : "MR"; } int FindSymbol(const string sym) { for(int i = 0; i < g_symCount; i++) if(g_sym[i].name == sym) return i; return -1; } string FmtZ(const double v) { return StringFormat("%+.2f", v); } string FmtMoney(const double v) { return StringFormat("%+.2f", v); } int CurrentDayStamp() { MqlDateTime st; TimeToStruct(TimeCurrent(), st); return st.year * 10000 + st.mon * 100 + st.day; } string UptimeText() { long sec = (long)(TimeCurrent() - g_initTime); if(sec < 0) sec = 0; int h = (int)(sec / 3600); int m = (int)((sec % 3600) / 60); int s = (int)(sec % 60); return StringFormat("%02d:%02d:%02d", h, m, s); } double Mean(const double &a[], const int n) { if(n <= 0) return 0.0; double s = 0.0; for(int i = 0; i < n; i++) s += a[i]; return s / n; } double Sd(const double &a[], const int n) { if(n <= 1) return 0.0; double m = Mean(a, n); double s = 0.0; for(int i = 0; i < n; i++) s += (a[i] - m) * (a[i] - m); return MathSqrt(s / (n - 1)); } double NormLots(const double lots, const double step) { if(step <= 0.0) return lots; return NormalizeDouble(MathFloor(lots / step + 0.000000001) * step, 8); } string LockGV() { return StringFormat("AXD_LOCK_%I64d", InpMagic); } string StateFile() { return StringFormat("AXIOMDeltaForex_%I64d.state", InpMagic); } //+------------------------------------------------------------------+ //| Basket construction | //+------------------------------------------------------------------+ bool InitBasket() { string parts[]; int n = StringSplit(InpSymbols, ',', parts); if(n < 3) { LogErr("basket needs at least 3 symbols for cross-sectional statistics"); return false; } for(int i = 0; i < n; i++) { string s = parts[i]; StringTrimLeft(s); StringTrimRight(s); StringToUpper(s); if(StringLen(s) == 0) continue; if(FindSymbol(s) >= 0) continue; if(!SymbolSelect(s, true)) { Log("symbol not available at this broker, skipped: " + s); continue; } if((ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(s, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_FULL) { Log("trading disabled on symbol, skipped: " + s); continue; } SSymbol st; st.name = s; st.base = SymbolInfoString(s, SYMBOL_CURRENCY_BASE); st.prof = SymbolInfoString(s, SYMBOL_CURRENCY_PROFIT); st.point = SymbolInfoDouble(s, SYMBOL_POINT); st.digits = (int)SymbolInfoInteger(s, SYMBOL_DIGITS); st.volMin = SymbolInfoDouble(s, SYMBOL_VOLUME_MIN); st.volMax = SymbolInfoDouble(s, SYMBOL_VOLUME_MAX); st.volStep = SymbolInfoDouble(s, SYMBOL_VOLUME_STEP); st.tickSize = SymbolInfoDouble(s, SYMBOL_TRADE_TICK_SIZE); st.tickValue = SymbolInfoDouble(s, SYMBOL_TRADE_TICK_VALUE); st.stopsLevel= SymbolInfoInteger(s, SYMBOL_TRADE_STOPS_LEVEL); st.atrHandle = iATR(s, InpTimeframe, ATR_PERIOD); if(st.atrHandle == INVALID_HANDLE) Log("ATR handle failed for " + s + " (volatility filter degraded)"); st.lastBarTime = 0; st.cd[LAYER_XS] = 0; st.cd[LAYER_MR] = 0; st.xsMom = 0.0; st.xsZ = 0.0; st.mrZ = 0.0; st.atrPct = 0.0; st.close1 = 0.0; st.xsDir = 0; st.mrDir = 0; st.xsTicket = 0; st.mrTicket = 0; st.xsLots = 0.0; st.mrLots = 0.0; st.posPnl = 0.0; st.state = SS_DATA; st.dataOk = false; ArrayResize(g_sym, g_symCount + 1); g_sym[g_symCount] = st; g_symCount++; } if(g_symCount < 3) { LogErr("less than 3 tradable symbols in basket - XS layer impossible"); return false; } BuildCurrencyList(); Log(StringFormat("basket ready: %d symbols, TF %s, account type: %s", g_symCount, EnumToString(InpTimeframe), g_hedging ? "hedging" : "netting")); return true; } void ReleaseBasket() { for(int i = 0; i < g_symCount; i++) if(g_sym[i].atrHandle != INVALID_HANDLE) IndicatorRelease(g_sym[i].atrHandle); } void BuildCurrencyList() { ArrayResize(g_cur, 0); g_curCount = 0; for(int i = 0; i < g_symCount; i++) { string cs[2]; cs[0] = g_sym[i].base; cs[1] = g_sym[i].prof; for(int k = 0; k < 2; k++) { string c = cs[k]; StringToUpper(c); if(StringLen(c) == 0) continue; bool dup = false; for(int j = 0; j < g_curCount; j++) if(g_cur[j] == c) { dup = true; break; } if(dup) continue; ArrayResize(g_cur, g_curCount + 1); g_cur[g_curCount] = c; g_curCount++; } } } //+------------------------------------------------------------------+ //| State persistence (restart-safe discipline / cooldowns / meta) | //+------------------------------------------------------------------+ void SaveState() { int h = FileOpen(StateFile(), FILE_WRITE | FILE_TXT | FILE_UNICODE); if(h == INVALID_HANDLE) { static bool warned = false; if(!warned) { LogErr("state save failed, err=" + IntegerToString(GetLastError())); warned = true; } return; } FileWrite(h, "V=1"); FileWrite(h, "MAGIC=" + IntegerToString(InpMagic)); FileWrite(h, "DAY=" + IntegerToString(g_dayStamp)); FileWrite(h, "DSEQ=" + DoubleToString(g_dayStartEquity, 2)); FileWrite(h, "PEAK=" + DoubleToString(g_equityPeak, 2)); FileWrite(h, "CONSEC=" + IntegerToString(g_consec)); FileWrite(h, "PAUSE=" + IntegerToString((long)g_pauseUntil)); FileWrite(h, "DDHALT=" + (g_ddHalt ? "1" : "0")); for(int L = 0; L < NLAYERS; L++) { string m = ""; int cnt = (int)MathMin(g_meta[L].count, META_CAP); for(int k = 0; k < cnt; k++) { int j = (g_meta[L].head - 1 - k + 2 * META_CAP) % META_CAP; m += IntegerToString(g_meta[L].res[j]); if(k < cnt - 1) m += ","; } FileWrite(h, StringFormat("M%d=%s", L, m)); FileWrite(h, StringFormat("T%d=%d:%d", L, g_meta[L].trades, g_meta[L].wins)); string cd = ""; for(int i = 0; i < g_symCount; i++) { cd += g_sym[i].name + ":" + IntegerToString(g_sym[i].cd[L]); if(i < g_symCount - 1) cd += ";"; } FileWrite(h, StringFormat("C%d=%s", L, cd)); } FileClose(h); } void ParseMeta(const int layer, const string v) { string xs[]; int n = StringSplit(v, ',', xs); for(int k = n - 1; k >= 0; k--) // saved newest-first -> push oldest-first MetaPush(layer, (int)StringToInteger(xs[k])); } void ParseTotals(const int layer, const string v) { string xs[]; if(StringSplit(v, ':', xs) != 2) return; g_meta[layer].trades = (int)StringToInteger(xs[0]); g_meta[layer].wins = (int)StringToInteger(xs[1]); } void ParseCooldowns(const int layer, const string v) { string xs[]; int n = StringSplit(v, ';', xs); for(int i = 0; i < n; i++) { string kv[]; if(StringSplit(xs[i], ':', kv) != 2) continue; int idx = FindSymbol(kv[0]); if(idx >= 0) g_sym[idx].cd[layer] = (int)StringToInteger(kv[1]); } } void LoadState() { int h = FileOpen(StateFile(), FILE_READ | FILE_TXT | FILE_UNICODE); if(h == INVALID_HANDLE) return; while(!FileIsEnding(h)) { string line = FileReadString(h); if(StringLen(line) == 0) continue; string kv[]; if(StringSplit(line, '=', kv) != 2) continue; string k = kv[0]; string v = kv[1]; if(k == "MAGIC") { if(StringToInteger(v) != InpMagic) { FileClose(h); return; } } else if(k == "DAY") g_dayStamp = (int)StringToInteger(v); else if(k == "DSEQ") g_dayStartEquity = StringToDouble(v); else if(k == "PEAK") g_equityPeak = StringToDouble(v); else if(k == "CONSEC") g_consec = (int)StringToInteger(v); else if(k == "PAUSE") g_pauseUntil = (datetime)StringToInteger(v); else if(k == "DDHALT") g_ddHalt = (v == "1"); else if(k == "M0") ParseMeta(LAYER_XS, v); else if(k == "M1") ParseMeta(LAYER_MR, v); else if(k == "T0") ParseTotals(LAYER_XS, v); else if(k == "T1") ParseTotals(LAYER_MR, v); else if(k == "C0") ParseCooldowns(LAYER_XS, v); else if(k == "C1") ParseCooldowns(LAYER_MR, v); } FileClose(h); Log("state restored from disk (day " + IntegerToString(g_dayStamp) + ")"); } // rebuild today's realized PnL per layer from the account history void ReconcileDayFromHistory() { MqlDateTime st; TimeToStruct(TimeCurrent(), st); datetime dayStart = TimeCurrent() - (datetime)(st.hour * 3600 + st.min * 60 + st.sec); if(!HistorySelect(dayStart, TimeCurrent() + 60)) return; int total = HistoryDealsTotal(); for(int i = 0; i < total; i++) { ulong d = HistoryDealGetTicket(i); if(d == 0) continue; long magic = HistoryDealGetInteger(d, DEAL_MAGIC); if(magic != LayerMagic(LAYER_XS) && magic != LayerMagic(LAYER_MR)) continue; int idx = FindSymbol(HistoryDealGetString(d, DEAL_SYMBOL)); if(idx < 0) continue; int layer = (magic == LayerMagic(LAYER_XS)) ? LAYER_XS : LAYER_MR; long entry = HistoryDealGetInteger(d, DEAL_ENTRY); if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT || entry == DEAL_ENTRY_OUT_BY) g_layerDayPnl[layer] += HistoryDealGetDouble(d, DEAL_PROFIT) + HistoryDealGetDouble(d, DEAL_SWAP) + HistoryDealGetDouble(d, DEAL_COMMISSION); } } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { g_tester = ((bool)MQLInfoInteger(MQL_TESTER)); g_optim = ((bool)MQLInfoInteger(MQL_OPTIMIZATION)); g_visual = ((bool)MQLInfoInteger(MQL_VISUAL_MODE)); g_hedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); g_initTime = TimeCurrent(); // ---- parameter validation --------------------------------------- if(InpMagic <= 0) { LogErr("InpMagic must be positive"); return INIT_PARAMETERS_INCORRECT; } if(InpXsMomPeriod < 1 || InpXsMomPeriod > 100) { LogErr("InpXsMomPeriod out of range"); return INIT_PARAMETERS_INCORRECT; } if(InpMrLookback < 20 || InpMrLookback > 500) { LogErr("InpMrLookback out of range"); return INIT_PARAMETERS_INCORRECT; } if(InpXsZEntry <= InpXsZExit) { LogErr("XS entry z must be greater than exit z"); return INIT_PARAMETERS_INCORRECT; } if(InpMrZEntry <= InpMrZExit) { LogErr("MR entry z must be greater than exit z"); return INIT_PARAMETERS_INCORRECT; } if(InpXsRiskPct <= 0 || InpMrRiskPct <= 0) { LogErr("risk percent must be positive"); return INIT_PARAMETERS_INCORRECT; } if(InpXsSlPct <= 0 || InpXsSlPct > 20 || InpMrSlPct <= 0 || InpMrSlPct > 20 || InpXsTpPct <= 0 || InpXsTpPct > 30 || InpMrTpPct <= 0 || InpMrTpPct > 30) { LogErr("SL/TP percent out of sensible range"); return INIT_PARAMETERS_INCORRECT; } if(InpDailyLossLimitPct <= 0 || InpMaxDdPct <= 0) { LogErr("discipline limits must be positive"); return INIT_PARAMETERS_INCORRECT; } if(InpMaxConsecLosses < 1 || InpPauseBars < 1) { LogErr("consec-loss / pause params invalid"); return INIT_PARAMETERS_INCORRECT; } if(InpSessionStartHour < 0 || InpSessionStartHour > 23 || InpSessionEndHour < 0 || InpSessionEndHour > 23) { LogErr("session hours out of range"); return INIT_PARAMETERS_INCORRECT; } if(InpFridayCloseHour < 0 || InpFridayCloseHour > 23){ LogErr("Friday close hour out of range"); return INIT_PARAMETERS_INCORRECT; } // ---- single-instance lock (same magic must not run twice) ------- if(!g_tester) { if(GlobalVariableCheck(LockGV())) { double v = GlobalVariableGet(LockGV()); if(v > 0 && (long)TimeCurrent() - (long)v < 20) { LogErr("another instance with the same magic is already running - init refused"); return INIT_FAILED; } } GlobalVariableSet(LockGV(), (double)TimeCurrent()); } if(!InitBasket()) { if(!g_tester) GlobalVariableDel(LockGV()); return INIT_FAILED; } LoadState(); if(g_dayStartEquity <= 0) g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); ReconcileDayFromHistory(); g_equityPeak = MathMax(g_equityPeak, AccountInfoDouble(ACCOUNT_EQUITY)); UpdateDiscipline(); // ---- warm data pass (no trading) --------------------------------- for(int i = 0; i < g_symCount; i++) RefreshSymbolData(i); ComputeCrossSection(); BindPositions(); EventSetTimer(1); Log(StringFormat("initialized: %d symbols, TF %s, magic %I64d (XS) / %I64d (MR), push=%s", g_symCount, EnumToString(InpTimeframe), InpMagic, InpMagic + 1, InpNotifyPush ? "on" : "off")); if(!g_hedging) Log("NETTING account detected: only one layer at a time per symbol (conflicting entries skipped)"); PanelInit(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { SaveState(); EventKillTimer(); PanelDestroy(); ReleaseBasket(); if(!g_tester) GlobalVariableDel(LockGV()); Log("deinitialized, reason=" + IntegerToString(reason)); } //+------------------------------------------------------------------+ //| Timer heartbeat: HUD refresh each second, scan cycle throttled | //+------------------------------------------------------------------+ void OnTimer() { if(g_symCount == 0) return; // instance lock heartbeat if(!g_tester) GlobalVariableSet(LockGV(), (double)TimeCurrent()); // connection transition notifications bool conn = ((int)TerminalInfoInteger(TERMINAL_CONNECTED) != 0); if(g_prevConn && !conn) Notify("AXIOM-Delta Forex: broker connection LOST"); if(!g_prevConn && conn && g_initTime > 0 && (long)TimeCurrent() - (long)g_initTime > 30) Notify("AXIOM-Delta Forex: broker connection restored"); g_prevConn = conn; PanelUpdate(); datetime now = TimeCurrent(); int scanSec = (int)MathMax(2, InpScanSeconds); if((long)(now - g_lastScan) >= (long)scanSec) { g_lastScan = now; ScanCycle(); } if((long)(now - g_lastSave) >= 60) { g_lastSave = now; SaveState(); } } //+------------------------------------------------------------------+ //| Chart events: dashboard minimize / expand | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && sparam == PFX + "BTN_MIN") { g_panelMin = !g_panelMin; ObjectSetInteger(0, sparam, OBJPROP_STATE, false); ObjectSetString(0, sparam, OBJPROP_TEXT, g_panelMin ? "+" : "-"); PanelUpdate(); ChartRedraw(); } } //+------------------------------------------------------------------+ //| Per-symbol data refresh (closed bars only, new-bar detection) | //| Returns true when a new bar formed on the symbol. | //+------------------------------------------------------------------+ bool RefreshSymbolData(const int idx) { string sym = g_sym[idx].name; int need = InpMrLookback + InpXsMomPeriod + 3; MqlRates r[]; ArraySetAsSeries(r, true); int copied = CopyRates(sym, InpTimeframe, 0, need, r); if(copied < InpMrLookback + InpXsMomPeriod + 2) { g_sym[idx].dataOk = false; g_sym[idx].state = SS_DATA; static datetime lastDataLog = 0; if(TimeCurrent() - lastDataLog > 300) { lastDataLog = TimeCurrent(); Log("history still loading: " + sym + " (" + IntegerToString(copied) + "/" + IntegerToString(need) + " bars)"); } return false; } g_sym[idx].dataOk = true; bool newBar = (r[0].time != g_sym[idx].lastBarTime); if(newBar) { if(g_sym[idx].lastBarTime > 0) { if(g_sym[idx].cd[LAYER_XS] > 0) g_sym[idx].cd[LAYER_XS]--; if(g_sym[idx].cd[LAYER_MR] > 0) g_sym[idx].cd[LAYER_MR]--; } g_sym[idx].lastBarTime = r[0].time; } // ---- MR: rolling z-score of close vs its own window -------------- double c[]; ArrayResize(c, InpMrLookback); for(int k = 0; k < InpMrLookback; k++) c[k] = r[1 + k].close; double mean = Mean(c, InpMrLookback); double sd = Sd(c, InpMrLookback); g_sym[idx].close1 = r[1].close; g_sym[idx].mrZ = (sd > 1e-12) ? (r[1].close - mean) / sd : 0.0; // ---- XS: momentum over InpXsMomPeriod closed bars ----------------- int mp = InpXsMomPeriod; if(copied >= mp + 2 && r[1 + mp].close > 1e-12) g_sym[idx].xsMom = r[1].close / r[1 + mp].close - 1.0; else g_sym[idx].xsMom = 0.0; // ---- ATR% (volatility filter) -------------------------------------- double ab[]; if(g_sym[idx].atrHandle != INVALID_HANDLE && CopyBuffer(g_sym[idx].atrHandle, 0, 1, 1, ab) == 1 && ab[0] > 0 && r[1].close > 0) g_sym[idx].atrPct = ab[0] / r[1].close * 100.0; return newBar; } //+------------------------------------------------------------------+ //| Cross-sectional statistics over the whole basket | //+------------------------------------------------------------------+ void ComputeCrossSection() { if(g_symCount < 3) { for(int i = 0; i < g_symCount; i++) g_sym[i].xsZ = 0.0; return; } double m[]; ArrayResize(m, g_symCount); for(int i = 0; i < g_symCount; i++) m[i] = g_sym[i].xsMom; double mean = Mean(m, g_symCount); double sd = Sd(m, g_symCount); for(int i = 0; i < g_symCount; i++) g_sym[i].xsZ = (sd > 1e-12) ? (m[i] - mean) / sd : 0.0; } //+------------------------------------------------------------------+ //| Forex filters | //+------------------------------------------------------------------+ bool SpreadOK(const int idx) { long sp = SymbolInfoInteger(g_sym[idx].name, SYMBOL_SPREAD); return (sp >= 0 && sp <= (long)InpMaxSpreadPoints); } bool SessionOK() { if(!InpSessionFilter) return true; MqlDateTime st; TimeToStruct(TimeCurrent(), st); int h = st.hour; if(InpSessionStartHour <= InpSessionEndHour) return (h >= InpSessionStartHour && h < InpSessionEndHour); return (h >= InpSessionStartHour || h < InpSessionEndHour); // overnight session } bool RolloverBlock() { if(!InpAvoidRollover) return false; MqlDateTime st; TimeToStruct(TimeCurrent(), st); if(st.hour == 23 && st.min >= 30) return true; if(st.hour == 0 && st.min < 30) return true; return false; } bool WeekendOrCloseTime() { MqlDateTime st; TimeToStruct(TimeCurrent(), st); if(st.day_of_week == 6) return true; // Saturday if(st.day_of_week == 5 && InpCloseBeforeWeekend && st.hour >= InpFridayCloseHour) return true; if(st.day_of_week == 0 && st.hour < 22) return true; // Sunday pre-open return false; } bool VolatilityOK(const int idx) { if(InpMinAtrPct <= 0 && InpMaxAtrPct <= 0) return true; double a = g_sym[idx].atrPct; if(a <= 0) return false; if(InpMinAtrPct > 0 && a < InpMinAtrPct) return false; if(InpMaxAtrPct > 0 && a > InpMaxAtrPct) return false; return true; } //+------------------------------------------------------------------+ //| Economic-calendar news filter (live only, graceful degradation) | //+------------------------------------------------------------------+ void RefreshNews() { g_newsCount = 0; if(!InpNewsFilter || g_tester) return; bool anyOk = false; datetime now = TimeCurrent(); for(int c = 0; c < g_curCount; c++) { MqlCalendarValue vals[]; ResetLastError(); if(CalendarValueHistory(vals, now - 3600, now + 21600, NULL, g_cur[c])) { anyOk = true; int cnt = ArraySize(vals); for(int k = 0; k < cnt && g_newsCount < 400; k++) { MqlCalendarEvent ev; if(!CalendarEventById(vals[k].event_id, ev)) continue; if(ev.importance != CALENDAR_IMPORTANCE_HIGH) continue; ArrayResize(g_newsCur, g_newsCount + 1); ArrayResize(g_newsTime, g_newsCount + 1); g_newsCur[g_newsCount] = g_cur[c]; g_newsTime[g_newsCount] = vals[k].time; g_newsCount++; } } } if(!anyOk) { if(g_newsOk) Log("economic calendar not available at this broker - news filter degraded to passive"); g_newsOk = false; } else g_newsOk = true; } bool NewsOK(const int idx) { if(!InpNewsFilter || g_tester || !g_newsOk || g_newsCount == 0) return true; datetime now = TimeCurrent(); long buf = (long)InpNewsBufferMin * 60; for(int k = 0; k < g_newsCount; k++) { if(g_newsCur[k] != g_sym[idx].base && g_newsCur[k] != g_sym[idx].prof) continue; long d = (long)now - (long)g_newsTime[k]; if(d < 0) d = -d; if(d <= buf) return false; } return true; } string NextNewsText() { if(!InpNewsFilter || g_tester || !g_newsOk) return "news off"; if(g_newsCount == 0) return "news n/a"; datetime now = TimeCurrent(); datetime best = 0; string cur = ""; for(int k = 0; k < g_newsCount; k++) { if(g_newsTime[k] <= now) continue; if(best == 0 || g_newsTime[k] < best) { best = g_newsTime[k]; cur = g_newsCur[k]; } } if(best == 0) return "news clear"; return "next news " + TimeToString(best, TIME_MINUTES) + " " + cur; } //+------------------------------------------------------------------+ //| Discipline engine (daily loss / drawdown / consec-loss pause) | //+------------------------------------------------------------------+ void UpdateDiscipline() { int ds = CurrentDayStamp(); if(ds != g_dayStamp) { g_dayStamp = ds; g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); g_layerDayPnl[LAYER_XS] = 0.0; g_layerDayPnl[LAYER_MR] = 0.0; if(g_dailyHalt) { g_dailyHalt = false; Notify("AXIOM-Delta Forex: new trading day - daily loss halt cleared"); } } double eq = AccountInfoDouble(ACCOUNT_EQUITY); if(eq <= 0) return; g_equityPeak = MathMax(g_equityPeak, eq); if(!g_dailyHalt && g_dayStartEquity > 0) { double dayPct = (eq / g_dayStartEquity - 1.0) * 100.0; if(dayPct <= -InpDailyLossLimitPct) { g_dailyHalt = true; Notify(StringFormat("AXIOM-Delta Forex: DAILY LOSS LIMIT hit (%.2f%%) - new entries halted", dayPct)); Log(StringFormat("daily loss halt: %.2f%%", dayPct)); } } if(!g_ddHalt && g_equityPeak > 0) { double ddPct = (1.0 - eq / g_equityPeak) * 100.0; if(ddPct >= InpMaxDdPct) { g_ddHalt = true; Notify(StringFormat("AXIOM-Delta Forex: MAX DRAWDOWN hit (%.2f%%) - halted until restart", ddPct)); Log(StringFormat("max drawdown halt: %.2f%%", ddPct)); } } } bool DisciplineAllowsEntry(string &reason) { if(((int)TerminalInfoInteger(TERMINAL_CONNECTED) == 0)) { reason = "DISCONNECTED"; return false; } if(((int)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) == 0) || ((int)MQLInfoInteger(MQL_TRADE_ALLOWED) == 0)) { reason = "AUTOTRADE-OFF"; return false; } if(g_ddHalt) { reason = "DD-HALT"; return false; } if(g_dailyHalt) { reason = "DAILY-LOSS-HALT"; return false; } if(TimeCurrent() < g_pauseUntil) { reason = "CONSEC-PAUSE"; return false; } if(TotalOpenRiskPct() >= InpMaxTotalRiskPct) { reason = "RISK-CAP"; return false; } double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); if(InpMinMarginLevel > 0 && ml > 0 && ml < InpMinMarginLevel) { reason = "LOW-MARGIN"; return false; } reason = ""; return true; } string EngineStateText() { if(((int)TerminalInfoInteger(TERMINAL_CONNECTED) == 0)) return "DISCONNECTED"; if(((int)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) == 0) || ((int)MQLInfoInteger(MQL_TRADE_ALLOWED) == 0)) return "AUTOTRADE OFF"; if(g_ddHalt) return "HALTED - MAX DRAWDOWN"; if(g_dailyHalt) return "HALTED - DAILY LOSS LIMIT"; if(TimeCurrent() < g_pauseUntil) return "PAUSED - CONSEC LOSSES"; if(WeekendOrCloseTime()) return "WEEKEND / PRE-CLOSE"; if(!g_warm) return "WARMING UP"; if(g_blockReason != "") return "BLOCKED - " + g_blockReason; return "SCANNING"; } void MarkAll(const int st) { for(int i = 0; i < g_symCount; i++) g_sym[i].state = st; } //+------------------------------------------------------------------+ //| Meta-label: adaptive sizing from rolling layer performance | //+------------------------------------------------------------------+ void MetaPush(const int layer, const int win) { g_meta[layer].res[g_meta[layer].head] = win; g_meta[layer].head = (g_meta[layer].head + 1) % META_CAP; if(g_meta[layer].count < META_CAP) g_meta[layer].count++; g_meta[layer].trades++; g_meta[layer].wins += win; } double MetaWinRate(const int layer) { int win = InpMetaWindow; if(win < 5) win = 5; if(win > META_CAP) win = META_CAP; int cnt = (int)MathMin(g_meta[layer].count, win); if(cnt < 10) return -1.0; // insufficient sample int wins = 0; for(int k = 0; k < cnt; k++) { int j = (g_meta[layer].head - 1 - k + 2 * META_CAP) % META_CAP; wins += g_meta[layer].res[j]; } return (double)wins / (double)cnt; } double MetaSizeMult(const int layer) { if(!InpMetaLabel) return 1.0; double wr = MetaWinRate(layer); if(wr < 0) return 1.0; if(wr <= 0.25) return 0.0; // stop adding new risk in a failing regime if(wr <= 0.35) return 0.5; // half size if(wr >= 0.55) return 1.25; // modest scale-up in a winning regime return 1.0; } string MetaWinRateText(const int layer) { double wr = MetaWinRate(layer); if(wr < 0) return "n/a"; return StringFormat("%.0f%%", wr * 100.0); } //+------------------------------------------------------------------+ //| Position sizing | //+------------------------------------------------------------------+ double CalcLots(const int idx, const double riskMoney, const double slDist) { string sym = g_sym[idx].name; double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE); if(tickSize <= 0.0 || tickValue <= 0.0 || slDist <= 0.0 || riskMoney <= 0.0) return 0.0; double lossPerLot = slDist / tickSize * tickValue; if(lossPerLot <= 0.0) return 0.0; double lots = riskMoney / lossPerLot; lots = NormLots(lots, g_sym[idx].volStep); if(lots < g_sym[idx].volMin) { double minRisk = g_sym[idx].volMin * lossPerLot; if(minRisk > riskMoney * 1.5) return 0.0; // min lot would exceed intended risk by >50% lots = g_sym[idx].volMin; } if(lots > g_sym[idx].volMax) lots = g_sym[idx].volMax; return lots; } bool MarginOK(const int idx, const int dir, const double lots) { string sym = g_sym[idx].name; double price = (dir > 0) ? SymbolInfoDouble(sym, SYMBOL_ASK) : SymbolInfoDouble(sym, SYMBOL_BID); if(price <= 0 || lots <= 0) return false; double m = 0.0; if(!OrderCalcMargin(dir > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, sym, lots, price, m)) return false; double freeM = AccountInfoDouble(ACCOUNT_MARGIN_FREE); return (m <= freeM * 0.8); } //+------------------------------------------------------------------+ //| Order execution (filling-mode aware, retry on transient errors) | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE_FILLING FillingMode(const string sym) { long fm = SymbolInfoInteger(sym, SYMBOL_FILLING_MODE); if((fm & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC; if((fm & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK; return ORDER_FILLING_RETURN; } bool RetcodeTransient(const uint rc) { return (rc == TRADE_RETCODE_REQUOTE || rc == TRADE_RETCODE_PRICE_CHANGED || rc == TRADE_RETCODE_PRICE_OFF || rc == TRADE_RETCODE_TIMEOUT); } bool RetcodeOk(const uint rc) { return (rc == TRADE_RETCODE_DONE || rc == TRADE_RETCODE_PLACED || rc == TRADE_RETCODE_DONE_PARTIAL); } bool SendMarket(const int layer, const int idx, const int dir, const double lots, const double sl, const double tp) { string sym = g_sym[idx].name; string cmt = "AXD-" + LayerTag(layer); g_trade.SetExpertMagicNumber((ulong)LayerMagic(layer)); g_trade.SetDeviationInPoints((ulong)InpSlippage); g_trade.SetTypeFilling(FillingMode(sym)); g_trade.SetAsyncMode(false); for(int attempt = 1; attempt <= 3; attempt++) { bool ok = (dir > 0) ? g_trade.Buy(lots, sym, 0.0, sl, tp, cmt) : g_trade.Sell(lots, sym, 0.0, sl, tp, cmt); uint rc = g_trade.ResultRetcode(); if(ok && RetcodeOk(rc)) { Log(StringFormat("%s %s %s %.2f lots @ %s sl=%s tp=%s", LayerTag(layer), sym, (dir > 0 ? "BUY" : "SELL"), lots, DoubleToString(g_trade.ResultPrice(), g_sym[idx].digits), DoubleToString(sl, g_sym[idx].digits), DoubleToString(tp, g_sym[idx].digits))); return true; } if(RetcodeTransient(rc)) { Sleep(250); continue; } LogErr(StringFormat("order failed %s %s rc=%u (%s)", LayerTag(layer), sym, rc, g_trade.ResultRetcodeDescription())); return false; } LogErr(StringFormat("order failed after retries %s %s", LayerTag(layer), sym)); return false; } bool CloseLayerPosition(const int layer, const int idx, const string why) { ulong ticket = (layer == LAYER_XS) ? g_sym[idx].xsTicket : g_sym[idx].mrTicket; if(ticket == 0) return false; g_trade.SetDeviationInPoints((ulong)InpSlippage); g_trade.SetAsyncMode(false); for(int attempt = 1; attempt <= 3; attempt++) { if(g_trade.PositionClose(ticket)) { uint rc = g_trade.ResultRetcode(); if(RetcodeOk(rc)) { g_sym[idx].cd[layer] = (layer == LAYER_XS) ? InpXsCooldown : InpMrCooldown; Log(StringFormat("%s close %s (%s)", LayerTag(layer), g_sym[idx].name, why)); return true; } } Sleep(250); } LogErr(StringFormat("close failed %s %s ticket=%I64u", LayerTag(layer), g_sym[idx].name, ticket)); return false; } void CloseAllOurs(const string why) { for(int i = 0; i < g_symCount; i++) { if(g_sym[i].xsDir != 0) CloseLayerPosition(LAYER_XS, i, why); if(g_sym[i].mrDir != 0) CloseLayerPosition(LAYER_MR, i, why); } } //+------------------------------------------------------------------+ //| Open a layer position (full filter + risk chain already passed) | //+------------------------------------------------------------------+ bool OpenLayerPosition(const int layer, const int idx, const int dir) { string sym = g_sym[idx].name; double riskPct = (layer == LAYER_XS) ? InpXsRiskPct : InpMrRiskPct; double slPct = (layer == LAYER_XS) ? InpXsSlPct : InpMrSlPct; double tpPct = (layer == LAYER_XS) ? InpXsTpPct : InpMrTpPct; double mult = MetaSizeMult(layer); if(mult <= 0.0) { Log("skip " + LayerTag(layer) + " " + sym + ": meta-label regime filter (win-rate too low)"); return false; } double ask = SymbolInfoDouble(sym, SYMBOL_ASK); double bid = SymbolInfoDouble(sym, SYMBOL_BID); double price = (dir > 0) ? ask : bid; if(price <= 0) return false; double slDist = price * slPct / 100.0; double stopsMin = (double)g_sym[idx].stopsLevel * g_sym[idx].point; double spread = ask - bid; if(stopsMin > 0 && slDist < stopsMin + spread) slDist = stopsMin + spread; // broker stops-level compliance double lots; if(InpFixedLots > 0) { lots = NormLots(InpFixedLots, g_sym[idx].volStep); if(lots < g_sym[idx].volMin) lots = g_sym[idx].volMin; } else { double eq = AccountInfoDouble(ACCOUNT_EQUITY); lots = CalcLots(idx, eq * riskPct / 100.0 * mult, slDist); if(lots <= 0.0) { Log("skip " + LayerTag(layer) + " " + sym + ": computed lot below tradable minimum for the set risk"); return false; } } for(int attempt = 0; attempt < 3 && !MarginOK(idx, dir, lots); attempt++) lots = NormLots(lots * 0.5, g_sym[idx].volStep); if(lots < g_sym[idx].volMin || !MarginOK(idx, dir, lots)) { Log("skip " + LayerTag(layer) + " " + sym + ": insufficient free margin"); return false; } double sl, tp; if(dir > 0) { sl = price - slDist; tp = price + price * tpPct / 100.0; } else { sl = price + slDist; tp = price - price * tpPct / 100.0; } sl = NormalizeDouble(sl, g_sym[idx].digits); tp = NormalizeDouble(tp, g_sym[idx].digits); return SendMarket(layer, idx, dir, lots, sl, tp); } //+------------------------------------------------------------------+ //| Position binding (reflect account truth every scan) | //+------------------------------------------------------------------+ void BindPositions() { g_layerPos[LAYER_XS] = 0; g_layerPos[LAYER_MR] = 0; for(int i = 0; i < g_symCount; i++) { g_sym[i].xsDir = 0; g_sym[i].mrDir = 0; g_sym[i].xsTicket = 0; g_sym[i].mrTicket = 0; g_sym[i].xsLots = 0.0; g_sym[i].mrLots = 0.0; g_sym[i].posPnl = 0.0; } int total = PositionsTotal(); for(int k = 0; k < total; k++) { ulong tk = PositionGetTicket(k); if(tk == 0) continue; string sym = PositionGetString(POSITION_SYMBOL); int idx = FindSymbol(sym); if(idx < 0) continue; long mg = PositionGetInteger(POSITION_MAGIC); if(mg != LayerMagic(LAYER_XS) && mg != LayerMagic(LAYER_MR)) continue; int L = (mg == LayerMagic(LAYER_XS)) ? LAYER_XS : LAYER_MR; int dir = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1 : -1; double vol = PositionGetDouble(POSITION_VOLUME); double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP); g_layerPos[L]++; if(L == LAYER_XS) { g_sym[idx].xsDir = dir; g_sym[idx].xsTicket = tk; g_sym[idx].xsLots = vol; } else { g_sym[idx].mrDir = dir; g_sym[idx].mrTicket = tk; g_sym[idx].mrLots = vol; } g_sym[idx].posPnl += pnl; } } double TotalOpenRiskPct() { double eq = AccountInfoDouble(ACCOUNT_EQUITY); if(eq <= 0) return 0.0; double risk = 0.0; for(int i = 0; i < g_symCount; i++) { double tickSize = SymbolInfoDouble(g_sym[i].name, SYMBOL_TRADE_TICK_SIZE); double tickVal = SymbolInfoDouble(g_sym[i].name, SYMBOL_TRADE_TICK_VALUE); if(tickSize <= 0 || tickVal <= 0) continue; for(int L = 0; L < NLAYERS; L++) { ulong tk = (L == LAYER_XS) ? g_sym[i].xsTicket : g_sym[i].mrTicket; if(tk == 0) continue; if(!PositionSelectByTicket(tk)) continue; double vol = PositionGetDouble(POSITION_VOLUME); double op = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); double slPct = (L == LAYER_XS) ? InpXsSlPct : InpMrSlPct; double dist = (sl > 0) ? MathAbs(op - sl) : op * slPct / 100.0; risk += dist / tickSize * tickVal * vol; } } return risk / eq * 100.0; } double AvgSpreadPoints() { if(g_symCount == 0) return 0.0; long s = 0; int n = 0; for(int i = 0; i < g_symCount; i++) { long sp = SymbolInfoInteger(g_sym[i].name, SYMBOL_SPREAD); if(sp >= 0) { s += sp; n++; } } return (n > 0) ? (double)s / n : 0.0; } //+------------------------------------------------------------------+ //| Signal exits + entry attempts | //+------------------------------------------------------------------+ void ManageExitsAll() { for(int i = 0; i < g_symCount; i++) { for(int L = 0; L < NLAYERS; L++) { int curDir = (L == LAYER_XS) ? g_sym[i].xsDir : g_sym[i].mrDir; if(curDir == 0) continue; double z = (L == LAYER_XS) ? g_sym[i].xsZ : g_sym[i].mrZ; double zx = (L == LAYER_XS) ? InpXsZExit : InpMrZExit; if(MathAbs(z) <= zx) CloseLayerPosition(L, i, "signal z-reversion"); } } } void TryEntries() { if(!g_warm) { g_blockReason = "WARMING UP"; MarkAll(SS_DATA); return; } string reason = ""; if(!DisciplineAllowsEntry(reason)) { g_blockReason = reason; MarkAll(SS_BLOCKED); return; } g_blockReason = ""; if(WeekendOrCloseTime()) { g_blockReason = "WEEKEND"; MarkAll(SS_BLOCKED); return; } bool rollover = RolloverBlock(); bool session = SessionOK(); for(int i = 0; i < g_symCount; i++) { if(!g_sym[i].dataOk) { g_sym[i].state = SS_DATA; continue; } if(g_sym[i].xsDir != 0 && g_sym[i].mrDir != 0) { g_sym[i].state = SS_OK; continue; } int why = SS_OK; if(!SpreadOK(i)) why = SS_SPREAD; else if(rollover || !session) why = SS_SESSION; else if(!VolatilityOK(i)) why = SS_VOLAT; else if(!NewsOK(i)) why = SS_NEWS; g_sym[i].state = why; if(why != SS_OK) continue; for(int L = 0; L < NLAYERS; L++) { if(L == LAYER_XS && !InpXsEnabled) continue; if(L == LAYER_MR && !InpMrEnabled) continue; if(g_sym[i].cd[L] > 0) { if(g_sym[i].xsDir == 0 && g_sym[i].mrDir == 0) g_sym[i].state = SS_COOLDOWN; continue; } int curDir = (L == LAYER_XS) ? g_sym[i].xsDir : g_sym[i].mrDir; if(curDir != 0) continue; if(!g_hedging) { int other = (L == LAYER_XS) ? g_sym[i].mrDir : g_sym[i].xsDir; if(other != 0) { g_sym[i].state = SS_NETTING; continue; } } if(g_layerPos[L] >= ((L == LAYER_XS) ? InpXsMaxPos : InpMrMaxPos)) { g_sym[i].state = SS_MAXPOS; continue; } double z = (L == LAYER_XS) ? g_sym[i].xsZ : g_sym[i].mrZ; double ze = (L == LAYER_XS) ? InpXsZEntry : InpMrZEntry; int dir = 0; if(z <= -ze) dir = 1; // stretched below basket/own mean -> BUY else if(z >= ze) dir = -1; // stretched above -> SELL if(dir == 0) continue; if(OpenLayerPosition(L, i, dir)) { g_layerPos[L]++; if(L == LAYER_XS) g_sym[i].xsDir = dir; else g_sym[i].mrDir = dir; g_sym[i].state = SS_OK; } } } } //+------------------------------------------------------------------+ //| Trade transactions: realized PnL, consecutive losses, meta | //+------------------------------------------------------------------+ void AddPending(const ulong posId, const string sym, const int layer, const double pnl) { for(int i = 0; i < g_pendCount; i++) if(g_pend[i].posId == posId) { g_pend[i].pnl += pnl; return; } ArrayResize(g_pend, g_pendCount + 1); g_pend[g_pendCount].posId = posId; g_pend[g_pendCount].sym = sym; g_pend[g_pendCount].layer = layer; g_pend[g_pendCount].pnl = pnl; g_pendCount++; } void HandleDealAdd(const ulong deal) { if(!HistoryDealSelect(deal)) return; string sym = HistoryDealGetString(deal, DEAL_SYMBOL); int idx = FindSymbol(sym); if(idx < 0) return; long magic = HistoryDealGetInteger(deal, DEAL_MAGIC); if(magic != LayerMagic(LAYER_XS) && magic != LayerMagic(LAYER_MR)) return; int layer = (magic == LayerMagic(LAYER_XS)) ? LAYER_XS : LAYER_MR; long entry = HistoryDealGetInteger(deal, DEAL_ENTRY); ulong posId = (ulong)HistoryDealGetInteger(deal, DEAL_POSITION_ID); double p = HistoryDealGetDouble(deal, DEAL_PROFIT) + HistoryDealGetDouble(deal, DEAL_SWAP) + HistoryDealGetDouble(deal, DEAL_COMMISSION); if(entry == DEAL_ENTRY_IN) { if(p != 0.0) AddPending(posId, sym, layer, p); // opening commission return; } if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT || entry == DEAL_ENTRY_OUT_BY) { g_layerDayPnl[layer] += p; AddPending(posId, sym, layer, p); } } bool PositionExistsById(const long posId) { int total = PositionsTotal(); for(int i = 0; i < total; i++) { ulong tk = PositionGetTicket(i); if(tk == 0) continue; if((long)PositionGetInteger(POSITION_IDENTIFIER) == posId) return true; } return false; } void FinalizeClosedPositions() { for(int i = g_pendCount - 1; i >= 0; i--) { if(PositionExistsById((long)g_pend[i].posId)) continue; double p = g_pend[i].pnl; int layer = g_pend[i].layer; MetaPush(layer, (p > 0) ? 1 : 0); if(p < 0) { g_consec++; Log(StringFormat("%s closed at LOSS %.2f (consec=%d)", LayerTag(layer), p, g_consec)); if(g_consec >= InpMaxConsecLosses) { g_pauseUntil = TimeCurrent() + (long)InpPauseBars * PeriodSeconds(InpTimeframe); Notify(StringFormat("AXIOM-Delta Forex: %d consecutive losses - paused until %s", g_consec, TimeToString(g_pauseUntil, TIME_DATE | TIME_MINUTES))); g_consec = 0; // fresh start after the pause } } else if(p > 0) { g_consec = 0; Log(StringFormat("%s closed at PROFIT %.2f", LayerTag(layer), p)); } g_pend[i] = g_pend[g_pendCount - 1]; g_pendCount--; ArrayResize(g_pend, g_pendCount); } } void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return; HandleDealAdd(trans.deal); } //+------------------------------------------------------------------+ //| Main scan cycle | //+------------------------------------------------------------------+ void ScanCycle() { UpdateDiscipline(); if(InpCloseBeforeWeekend && WeekendOrCloseTime()) CloseAllOurs("weekend"); if(g_dailyHalt && InpCloseOnDailyHalt) CloseAllOurs("daily-halt"); if(InpNewsFilter && !g_tester && (long)(TimeCurrent() - g_lastNewsRef) > 300) { RefreshNews(); g_lastNewsRef = TimeCurrent(); } for(int i = 0; i < g_symCount; i++) RefreshSymbolData(i); ComputeCrossSection(); BindPositions(); ManageExitsAll(); FinalizeClosedPositions(); TryEntries(); if(!g_warm) { bool all = true; for(int i = 0; i < g_symCount; i++) if(!g_sym[i].dataOk) { all = false; break; } if(all) { g_warm = true; Log("data warm-up complete - engine is live"); } } } //+------------------------------------------------------------------+ //| On-chart HUD (dashboard) | //| Shows: EA + broker connection status, AutoTrade state, engine | //| state, per-symbol monitor (z-scores, state, pos/PnL), layers, | //| discipline, account summary and the list of ACTIVE symbols. | //+------------------------------------------------------------------+ int LineH() { return InpFontSize + 7; } int RowY(const int row) { return TITLE_H + 6 + row * LineH(); } string PanelStateAbbr(const int idx) { if(!g_sym[idx].dataOk) return "DATA"; if(g_sym[idx].xsDir != 0 || g_sym[idx].mrDir != 0) return "ACTV"; switch(g_sym[idx].state) { case SS_COOLDOWN: return "CD"; case SS_SPREAD: return "SPRD"; case SS_NEWS: return "NEWS"; case SS_SESSION: return "SESS"; case SS_VOLAT: return "VOLA"; case SS_DATA: return "DATA"; case SS_MAXPOS: return "MAXP"; case SS_NETTING: return "NETT"; case SS_BLOCKED: return "HALT"; default: return "OK"; } } color EngineStateColor() { string s = EngineStateText(); if(StringFind(s, "HALTED") >= 0 || StringFind(s, "DISCONNECTED") >= 0 || StringFind(s, "AUTOTRADE") >= 0) return InpColBad; if(StringFind(s, "SCANNING") >= 0) return InpColGood; return InpColWarn; } string SessText() { if(WeekendOrCloseTime()) return "weekend"; if(InpSessionFilter) return StringFormat("%02d-%02d", InpSessionStartHour, InpSessionEndHour); return "24/5"; } string ActiveText() { string act = ""; int total = 0, included = 0; for(int i = 0; i < g_symCount; i++) { if(g_sym[i].xsDir == 0 && g_sym[i].mrDir == 0) continue; total++; string d = ""; if(g_sym[i].xsDir > 0) d += "L"; else if(g_sym[i].xsDir < 0) d += "S"; if(g_sym[i].mrDir > 0) d += "L"; else if(g_sym[i].mrDir < 0) d += "S"; string piece = g_sym[i].name + "-" + d + " "; if(StringLen(act) + StringLen(piece) <= 40) { act += piece; included++; } } if(total == 0) return "ACTIVE: none"; if(included < total) act += "+" + IntegerToString(total - included) + " "; return "ACTIVE: " + act; } // corner-aware placement of a text label void PlaceLabel(const string name, const int xLeft, const int yTop) { int W = InpPanelWidth; int H = g_panelH; long xd, yd, anch; switch(InpPanelCorner) { case CORNER_LEFT_UPPER: xd = InpPanelX + xLeft; yd = InpPanelY + yTop; anch = ANCHOR_LEFT_UPPER; break; case CORNER_RIGHT_UPPER: xd = InpPanelX + (W - xLeft); yd = InpPanelY + yTop; anch = ANCHOR_RIGHT_UPPER; break; case CORNER_LEFT_LOWER: xd = InpPanelX + xLeft; yd = InpPanelY + (H - yTop); anch = ANCHOR_LEFT_LOWER; break; default: // CORNER_RIGHT_LOWER xd = InpPanelX + (W - xLeft); yd = InpPanelY + (H - yTop); anch = ANCHOR_RIGHT_LOWER; break; } ObjectSetInteger(0, name, OBJPROP_CORNER, InpPanelCorner); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xd); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yd); ObjectSetInteger(0, name, OBJPROP_ANCHOR, anch); } void SetLabel(const string tag, const int xLeft, const int yTop, const string text, const color clr, const int fs, const string font) { string name = PFX + tag; if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_ZORDER, 10); } PlaceLabel(name, xLeft, yTop); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, (fs > 0) ? fs : InpFontSize); ObjectSetString(0, name, OBJPROP_FONT, (font == "") ? "Consolas" : font); ObjectSetString(0, name, OBJPROP_TEXT, text); } void EnsureRect(const string name, const int w, const int h) { if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, name, OBJPROP_ZORDER, 0); } ObjectSetInteger(0, name, OBJPROP_CORNER, InpPanelCorner); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, InpPanelX); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, InpPanelY); ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, InpColBg); ObjectSetInteger(0, name, OBJPROP_COLOR, InpColBorder); } void EnsureButton() { string name = PFX + "BTN_MIN"; if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_XSIZE, 20); ObjectSetInteger(0, name, OBJPROP_YSIZE, 18); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, InpColBg); ObjectSetInteger(0, name, OBJPROP_COLOR, InpColText); ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, InpColBorder); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, InpFontSize); ObjectSetString(0, name, OBJPROP_FONT, "Consolas"); ObjectSetString(0, name, OBJPROP_TEXT, "-"); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_ZORDER, 20); } long xd, yd; switch(InpPanelCorner) { case CORNER_LEFT_UPPER: case CORNER_LEFT_LOWER: xd = InpPanelX + InpPanelWidth - 26; break; default: xd = InpPanelX + 4; break; } if(InpPanelCorner == CORNER_LEFT_LOWER || InpPanelCorner == CORNER_RIGHT_LOWER) yd = InpPanelY + g_panelH - 21; else yd = InpPanelY + 3; ObjectSetInteger(0, name, OBJPROP_CORNER, InpPanelCorner); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xd); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yd); } void PanelInit() { if(!InpShowPanel) return; PanelUpdate(); ChartRedraw(); } void PanelDestroy() { ObjectsDeleteAll(0, PFX, -1, -1); } void PanelUpdate() { if(!InpShowPanel) return; if(g_tester && !g_visual) return; // ---- gather live status ------------------------------------------ bool conn = ((int)TerminalInfoInteger(TERMINAL_CONNECTED) != 0); long ping = TerminalInfoInteger(TERMINAL_PING_LAST); bool termTrade = ((int)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) != 0); bool eaTrade = ((int)MQLInfoInteger(MQL_TRADE_ALLOWED) != 0); bool tradeOn = termTrade && eaTrade; long login = AccountInfoInteger(ACCOUNT_LOGIN); string server = AccountInfoString(ACCOUNT_SERVER); double bal = AccountInfoDouble(ACCOUNT_BALANCE); double eq = AccountInfoDouble(ACCOUNT_EQUITY); double freeM = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); double dayPct = (g_dayStartEquity > 0) ? (eq / g_dayStartEquity - 1.0) * 100.0 : 0.0; double ddPct = (g_equityPeak > 0) ? (1.0 - eq / g_equityPeak) * 100.0 : 0.0; double openRisk = TotalOpenRiskPct(); double avgSpr = AvgSpreadPoints(); // ---- geometry ------------------------------------------------------ int W = InpPanelWidth; int shown = (int)MathMin(g_symCount, PANEL_MAXSYM); int totalRows = g_panelMin ? 5 : (5 + 1 + PANEL_MAXSYM + 1 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 1); g_panelH = TITLE_H + 10 + totalRows * LineH(); EnsureRect(PFX + "BG", W, g_panelH); EnsureButton(); bool full = !g_panelMin; int row = 0; // ---- title strip ---------------------------------------------------- SetLabel("TITLE", 8, 5, "AXIOM-DELTA FOREX v1.0", InpColTitle, InpFontSize + 2, "Segoe UI"); SetLabel("MODE", W - 66, 6, (g_optim ? "OPT" : (g_tester ? "TEST" : "LIVE")), (g_tester ? InpColWarn : InpColGood), InpFontSize - 1, "Consolas"); // ---- STATUS section --------------------------------------------------- SetLabel("S1", 8, RowY(row), "---- STATUS -------------------------------", InpColTitle, 0, ""); row++; SetLabel("R_EA", 8, RowY(row), StringFormat("EA RUNNING uptime %s", UptimeText()), InpColText, 0, ""); row++; SetLabel("R_BRK", 8, RowY(row), conn ? StringFormat("BROKER CONNECTED %d ms #%s @ %s", (int)ping, IntegerToString(login), server) : (string)"BROKER DISCONNECTED", conn ? InpColGood : InpColBad, 0, ""); row++; SetLabel("R_AT", 8, RowY(row), StringFormat("AUTOTRADE %s open %d (XS %d / MR %d)", (tradeOn ? "ON" : "OFF"), g_layerPos[LAYER_XS] + g_layerPos[LAYER_MR], g_layerPos[LAYER_XS], g_layerPos[LAYER_MR]), tradeOn ? InpColGood : InpColBad, 0, ""); row++; SetLabel("R_ENG", 8, RowY(row), "ENGINE " + EngineStateText(), EngineStateColor(), 0, ""); row++; // ---- SYMBOLS section --------------------------------------------------- SetLabel("S2", 8, RowY(row), full ? StringFormat("---- SYMBOLS (%d / active first) ------------", g_symCount) : "", InpColTitle, 0, ""); row++; // display order: symbols holding positions first int order[]; ArrayResize(order, g_symCount); int m = 0; for(int i = 0; i < g_symCount; i++) if(g_sym[i].xsDir != 0 || g_sym[i].mrDir != 0) order[m++] = i; for(int i = 0; i < g_symCount; i++) if(g_sym[i].xsDir == 0 && g_sym[i].mrDir == 0) order[m++] = i; for(int k = 0; k < PANEL_MAXSYM; k++) { string tagL = "SYML_" + IntegerToString(k); string tagR = "SYMR_" + IntegerToString(k); if(full && k < shown) { int i = order[k]; string xsS = InpXsEnabled ? ("XS" + FmtZ(g_sym[i].xsZ)) : "XS -- "; string mrS = InpMrEnabled ? ("MR" + FmtZ(g_sym[i].mrZ)) : "MR -- "; string left = StringFormat("%-9s %-7s %-7s %-4s", g_sym[i].name, xsS, mrS, PanelStateAbbr(i)); bool hasPos = (g_sym[i].xsDir != 0 || g_sym[i].mrDir != 0); color c = InpColText; if(hasPos) c = InpColGood; else if(g_sym[i].state == SS_SPREAD || g_sym[i].state == SS_NEWS || g_sym[i].state == SS_SESSION || g_sym[i].state == SS_VOLAT || g_sym[i].state == SS_MAXPOS || g_sym[i].state == SS_NETTING) c = InpColWarn; else if(g_sym[i].state == SS_COOLDOWN || g_sym[i].state == SS_DATA) c = InpColDim; SetLabel(tagL, 8, RowY(row), left, c, 0, ""); string right = ""; color rc = InpColText; if(hasPos) { string d = ""; if(g_sym[i].xsDir > 0) d += "L"; else if(g_sym[i].xsDir < 0) d += "S"; if(g_sym[i].mrDir > 0) d += "L"; else if(g_sym[i].mrDir < 0) d += "S"; double lots = g_sym[i].xsLots + g_sym[i].mrLots; right = StringFormat("%s %.2f %+.2f", d, lots, g_sym[i].posPnl); rc = (g_sym[i].posPnl >= 0) ? InpColGood : InpColBad; } SetLabel(tagR, W - 118, RowY(row), right, rc, 0, ""); } else { SetLabel(tagL, 8, RowY(row), "", InpColText, 0, ""); SetLabel(tagR, W - 118, RowY(row), "", InpColText, 0, ""); } row++; } SetLabel("R_MORE", 8, RowY(row), (full && g_symCount > shown) ? StringFormat("+ %d more (watch only)", g_symCount - shown) : "", InpColDim, 0, ""); row++; // ---- LAYERS section ------------------------------------------------------ SetLabel("S3", 8, RowY(row), full ? "---- LAYERS --------------------------------" : "", InpColTitle, 0, ""); row++; SetLabel("R_XS", 8, RowY(row), full ? StringFormat("XS pos %d/%d day %s wr %s size x%.2f", g_layerPos[LAYER_XS], InpXsMaxPos, FmtMoney(g_layerDayPnl[LAYER_XS]), MetaWinRateText(LAYER_XS), MetaSizeMult(LAYER_XS)) : "", (g_layerDayPnl[LAYER_XS] >= 0) ? InpColText : InpColBad, 0, ""); row++; SetLabel("R_MR", 8, RowY(row), full ? StringFormat("MR pos %d/%d day %s wr %s size x%.2f", g_layerPos[LAYER_MR], InpMrMaxPos, FmtMoney(g_layerDayPnl[LAYER_MR]), MetaWinRateText(LAYER_MR), MetaSizeMult(LAYER_MR)) : "", (g_layerDayPnl[LAYER_MR] >= 0) ? InpColText : InpColBad, 0, ""); row++; // ---- DISCIPLINE section ---------------------------------------------------- SetLabel("S4", 8, RowY(row), full ? "---- DISCIPLINE ----------------------------" : "", InpColTitle, 0, ""); row++; SetLabel("R_D1", 8, RowY(row), full ? StringFormat("day %s (lim -%.2f%%) dd %.2f%% (max %.2f%%)", FmtMoney(dayPct), InpDailyLossLimitPct, ddPct, InpMaxDdPct) : "", (dayPct <= -InpDailyLossLimitPct * 0.7) ? InpColWarn : InpColText, 0, ""); row++; string pauseTxt = (TimeCurrent() < g_pauseUntil) ? ("until " + TimeToString(g_pauseUntil, TIME_DATE | TIME_MINUTES)) : "-"; SetLabel("R_D2", 8, RowY(row), full ? StringFormat("consec %d/%d pause %s halt-flatten %s", g_consec, InpMaxConsecLosses, pauseTxt, (InpCloseOnDailyHalt ? "ON" : "OFF")) : "", InpColText, 0, ""); row++; // ---- ACCOUNT section ---------------------------------------------------------- SetLabel("S5", 8, RowY(row), full ? "---- ACCOUNT --------------------------------" : "", InpColTitle, 0, ""); row++; SetLabel("R_A1", 8, RowY(row), full ? StringFormat("bal %.2f eq %.2f free %.2f ML %s", bal, eq, freeM, (ml > 0 ? StringFormat("%.0f%%", ml) : "-")) : "", InpColText, 0, ""); row++; SetLabel("R_A2", 8, RowY(row), full ? StringFormat("open risk %.2f%% (cap %.2f%%) avg spread %.0f pt", openRisk, InpMaxTotalRiskPct, avgSpr) : "", (openRisk >= InpMaxTotalRiskPct * 0.8) ? InpColWarn : InpColText, 0, ""); row++; // ---- ACTIVE symbols + footer ----------------------------------------------------- SetLabel("R_ACT", 8, RowY(row), full ? ActiveText() : "", InpColTitle, 0, ""); row++; SetLabel("R_FT", 8, RowY(row), full ? StringFormat("scan %s %s sess %s fri-close %02d:00", (g_lastScan > 0 ? TimeToString(g_lastScan, TIME_SECONDS) : "-"), NextNewsText(), SessText(), InpFridayCloseHour) : "", InpColDim, 0, ""); row++; ChartRedraw(); } //+------------------------------------------------------------------+