Advertisement IC Markets

Scalping US100 and US500

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Scalping US100 and US500

Post by PTScalper »

2. MetaTrader 5 (MQL5) Single-Chart Multi-Symbol Scanner

In MT5, using an Expert Advisor (OnTimer) rather than a single-symbol OnCalculate() indicator is the institutional standard for multi-symbol scanning: OnCalculate only wakes up when the host chart receives a tick, whereas OnTimer() polls US500, US100, DE40, and XAUUSD deterministically every second even if your host chart is quiet.

Create a new Expert Advisor in MetaEditor 5 named MultiSymbolQuantScannerPro.mq5:

Code: Select all

//+------------------------------------------------------------------+
//|                                   MultiSymbolQuantScannerPro.mq5 |
//|   Single-Chart Multi-Asset M15 Liquidity Sweep Scanner & HUD     |
//|   Simultaneously Monitors: US500, US100, DE40, XAUUSD            |
//+------------------------------------------------------------------+
#property copyright "Quant Multi-Asset Architecture"
#property version   "3.00"
#property strict

//--- Input Parameters
input group "1. Multi-Symbol Watchlist"
input string   InpSymbolsCsv        = "US500,US100,DE40,XAUUSD"; // Comma-Separated Tickers

input group "2. Hybrid Money Management (Mobile Alert Lot Calc)"
input double   InpSeriesCapital     = 5000.0;  // Series Starting Capital ($)
input double   InpBaseRiskPct       = 1.0;     // Base Risk Per Trade (%)
input double   InpExpProfitMult     = 1.5;     // Accrued Profit Scaling Factor

input group "3. Adaptive ATR(14) & Microstructure Filters"
input double   InpMinSweepAtr       = 0.10;    // Min Sweep Depth (x M15 ATR)
input double   InpMaxBarAtr         = 2.80;    // Max Candle Size Filter (x M15 ATR)
input double   InpSlBufferAtr       = 0.20;    // Stop-Loss Buffer (x M15 ATR)
input double   InpMinWickPct        = 32.0;    // Min Rejection Wick (% of Bar)
input bool     InpRequireVolSpike   = true;    // Require Tick Volume > 1.15x SMA(20)

input group "4. Notifications"
input bool     InpDesktopAlert      = true;    // Desktop Alert Popup
input bool     InpMobilePush        = true;    // Send MT5 Mobile Push Notification

//--- Asset Profile Enum
enum ENUM_ASSET_PROFILE
{
   PROFILE_US_INDEX,
   PROFILE_EU_DAX,
   PROFILE_GOLD_COMEX
};

//--- Per-Symbol State Structure
struct SymbolSlot
{
   string             name;
   bool               active;
   ENUM_ASSET_PROFILE profile;
   int                atrHandle;
   int                h4EmaHandle;
   datetime           lastBarTime;
   datetime           sessionAnchor;
   double             rangeHigh;
   double             rangeLow;
   double             pdHigh;
   double             pdLow;
   bool               shortFired;
   bool               longFired;
   string             statusText;
   string             lastSignal;
};

SymbolSlot g_slots[];
int        g_slotCount = 0;

//+------------------------------------------------------------------+
//| Resolve Broker Symbol Name Automatically                         |
//+------------------------------------------------------------------+
string ResolveBrokerTicker(string baseTicker)
{
   StringTrimLeft(baseTicker);
   StringTrimRight(baseTicker);
   
   string suffixes[] = {"", ".cash", ".r", "_i", ".pro"};
   for(int i = 0; i < ArraySize(suffixes); i++)
   {
      string cand = baseTicker + suffixes[i];
      if(SymbolSelect(cand, true)) return cand;
   }
   
   // Fallback for GER40 vs DE40
   if(StringFind(baseTicker, "DE40") >= 0)
   {
      if(SymbolSelect("GER40", true))      return "GER40";
      if(SymbolSelect("GER40.cash", true)) return "GER40.cash";
   }
   return "";
}

//+------------------------------------------------------------------+
//| Classify Symbol into Session Profile                             |
//+------------------------------------------------------------------+
ENUM_ASSET_PROFILE ClassifyProfile(string sym)
{
   string u = sym;
   StringToUpper(u);
   if(StringFind(u, "DE40") >= 0 || StringFind(u, "GER40") >= 0 || StringFind(u, "DAX") >= 0)
      return PROFILE_EU_DAX;
   if(StringFind(u, "XAU") >= 0 || StringFind(u, "GOLD") >= 0)
      return PROFILE_GOLD_COMEX;
   return PROFILE_US_INDEX;
}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   string rawList[];
   int count = StringSplit(InpSymbolsCsv, ',', rawList);
   ArrayResize(g_slots, count);
   g_slotCount = 0;
   
   for(int i = 0; i < count; i++)
   {
      string resolved = ResolveBrokerTicker(rawList[i]);
      if(resolved == "")
      {
         PrintFormat("[Scanner Warning] Ticker '%s' not found in MarketWatch.", rawList[i]);
         continue;
      }
      
      int idx = g_slotCount++;
      g_slots[idx].name          = resolved;
      g_slots[idx].active        = true;
      g_slots[idx].profile       = ClassifyProfile(resolved);
      g_slots[idx].atrHandle     = iATR(resolved, PERIOD_M15, 14);
      g_slots[idx].h4EmaHandle   = iMA(resolved, PERIOD_H4, 50, 0, MODE_EMA, PRICE_CLOSE);
      g_slots[idx].lastBarTime   = 0;
      g_slots[idx].sessionAnchor = 0;
      g_slots[idx].rangeHigh     = -DBL_MAX;
      g_slots[idx].rangeLow      =  DBL_MAX;
      g_slots[idx].pdHigh        = -DBL_MAX;
      g_slots[idx].pdLow         =  DBL_MAX;
      g_slots[idx].shortFired    = false;
      g_slots[idx].longFired     = false;
      g_slots[idx].statusText    = "INITIALIZING";
      g_slots[idx].lastSignal    = "None";
   }
   
   ArrayResize(g_slots, g_slotCount);
   EventSetTimer(1); // Poll all 4 symbols every 1 second
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   for(int i = 0; i < g_slotCount; i++)
   {
      if(g_slots[i].atrHandle   != INVALID_HANDLE) IndicatorRelease(g_slots[i].atrHandle);
      if(g_slots[i].h4EmaHandle != INVALID_HANDLE) IndicatorRelease(g_slots[i].h4EmaHandle);
   }
   Comment("");
}

//+------------------------------------------------------------------+
//| Automatic US & EU DST Offset Calculators                         |
//+------------------------------------------------------------------+
int GetUsOffsetSec(datetime utc)
{
   MqlDateTime dt; TimeToStruct(utc, dt);
   MqlDateTime m1 = {dt.year, 3, 1, 7, 0, 0, 0, 0};
   datetime tm1 = StructToTime(m1); TimeToStruct(tm1, m1);
   datetime dstStart = tm1 + (((7 - m1.day_of_week) % 7 + 7) * 86400);
   
   MqlDateTime n1 = {dt.year, 11, 1, 6, 0, 0, 0, 0};
   datetime tn1 = StructToTime(n1); TimeToStruct(tn1, n1);
   datetime dstEnd = tn1 + (((7 - n1.day_of_week) % 7) * 86400);
   return (utc >= dstStart && utc < dstEnd) ? (-4 * 3600) : (-5 * 3600);
}

int GetEuOffsetSec(datetime utc)
{
   MqlDateTime dt; TimeToStruct(utc, dt);
   MqlDateTime m31 = {dt.year, 3, 31, 1, 0, 0, 0, 0};
   datetime tm31 = StructToTime(m31); TimeToStruct(tm31, m31);
   datetime dstStart = tm31 - (m31.day_of_week * 86400);
   
   MqlDateTime o31 = {dt.year, 10, 31, 1, 0, 0, 0, 0};
   datetime to31 = StructToTime(o31); TimeToStruct(to31, o31);
   datetime dstEnd = to31 - (o31.day_of_week * 86400);
   return (utc >= dstStart && utc < dstEnd) ? (2 * 3600) : (1 * 3600);
}

int GetServerOffsetSec()
{
   return (int)MathRound((double)(TimeTradeServer() - TimeGMT()) / 1800.0) * 1800;
}

//+------------------------------------------------------------------+
//| Compute Asset-Specific Session Boundaries in Broker Server Time  |
//+------------------------------------------------------------------+
void GetAssetSessionServerTime(ENUM_ASSET_PROFILE prof, datetime barSrv, int srvOffSec,
                               datetime &outRangeStartSrv, datetime &outKillOpenSrv,
                               datetime &outKillEndSrv, datetime &outAnchor)
{
   datetime barUtc = barSrv - srvOffSec;
   int nyOff = GetUsOffsetSec(barUtc);
   int euOff = GetEuOffsetSec(barUtc);
   
   if(prof == PROFILE_EU_DAX)
   {
      datetime cetTime = barUtc + euOff;
      MqlDateTime dtCet; TimeToStruct(cetTime, dtCet);
      int curHour = dtCet.hour;
      dtCet.hour = 0; dtCet.min = 0; dtCet.sec = 0;
      datetime cetMid = StructToTime(dtCet);
      
      if(curHour < 13)
      {
         outAnchor        = cetMid;
         outRangeStartSrv = (cetMid + 1 * 3600) - euOff + srvOffSec;        // 01:00 - 08:00 CET
         outKillOpenSrv   = (cetMid + 8 * 3600) - euOff + srvOffSec;        // 08:00 CET Xetra Open
         outKillEndSrv    = (cetMid + 10 * 3600 + 1800) - euOff + srvOffSec;// 10:30 CET
      }
      else
      {
         outAnchor        = cetMid + 12 * 3600;
         outRangeStartSrv = (cetMid + 8 * 3600) - euOff + srvOffSec;        // 08:00 CET EU Range
         datetime nyTime  = barUtc + nyOff;
         MqlDateTime dtNy; TimeToStruct(nyTime, dtNy);
         dtNy.hour = 0; dtNy.min = 0; dtNy.sec = 0;
         datetime nyMid   = StructToTime(dtNy);
         outKillOpenSrv   = (nyMid + 9 * 3600 + 1800) - nyOff + srvOffSec;  // 09:30 NY Open
         outKillEndSrv    = (nyMid + 11 * 3600 + 1800) - nyOff + srvOffSec; // 11:30 NY
      }
      return;
   }
   
   datetime nyTime = barUtc + nyOff;
   MqlDateTime dtNy; TimeToStruct(nyTime, dtNy);
   dtNy.hour = 0; dtNy.min = 0; dtNy.sec = 0;
   datetime nyMid = StructToTime(dtNy);
   outAnchor = nyMid;
   
   if(prof == PROFILE_GOLD_COMEX)
   {
      outRangeStartSrv = (nyMid + 3 * 3600) - nyOff + srvOffSec;            // 03:00 NY
      outKillOpenSrv   = (nyMid + 8 * 3600 + 900) - nyOff + srvOffSec;      // 08:15 NY (COMEX Open)
      outKillEndSrv    = (nyMid + 11 * 3600 + 1800) - nyOff + srvOffSec;    // 11:30 NY
   }
   else
   {
      outRangeStartSrv = (nyMid + 3 * 3600) - nyOff + srvOffSec;            // 03:00 NY
      outKillOpenSrv   = (nyMid + 9 * 3600 + 1800) - nyOff + srvOffSec;     // 09:30 NY Cash Open
      outKillEndSrv    = (nyMid + 12 * 3600) - nyOff + srvOffSec;           // 12:00 NY
   }
}

//+------------------------------------------------------------------+
//| Hybrid Position Size Calculator Per Symbol                       |
//+------------------------------------------------------------------+
double CalcSymbolLots(string sym, double stopPts, double &outRiskUsd)
{
   double eq = AccountInfoDouble(ACCOUNT_EQUITY);
   if(eq <= 0) eq = InpSeriesCapital;
   
   double profit   = MathMax(0.0, eq - InpSeriesCapital);
   double baseRisk = InpSeriesCapital * (InpBaseRiskPct / 100.0);
   double expRisk  = profit * ((InpBaseRiskPct * InpExpProfitMult) / 100.0);
   outRiskUsd      = (eq < InpSeriesCapital) ? (eq * (InpBaseRiskPct / 100.0)) : (baseRisk + expRisk);
   
   double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
   double tickVal  = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
   double step     = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
   double minLot   = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
   double maxLot   = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
   
   if(tickSize <= 0 || tickVal <= 0 || stopPts <= 0) return minLot;
   double lossPerLot = (stopPts / tickSize) * tickVal;
   double rawLots    = (lossPerLot > 0) ? (outRiskUsd / lossPerLot) : minLot;
   double stepped    = MathFloor(rawLots / step) * step;
   return MathMax(minLot, MathMin(maxLot, stepped));
}

//+------------------------------------------------------------------+
//| Timer Loop: Poll All Symbols & Render Unified Dashboard          |
//+------------------------------------------------------------------+
void OnTimer()
{
   int srvOffSec = GetServerOffsetSec();
   datetime nowSrv = TimeTradeServer();
   
   string clusterSymbols = "";
   int    clusterCount   = 0;
   
   for(int s = 0; s < g_slotCount; s++)
   {
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      if(CopyRates(g_slots[s].name, PERIOD_M15, 0, 50, rates) < 35) continue;
      
      datetime closedBarSrv = rates[1].time;
      datetime rStart, kOpen, kEnd, anchor;
      GetAssetSessionServerTime(g_slots[s].profile, closedBarSrv, srvOffSec, rStart, kOpen, kEnd, anchor);
      
      // Reset Session State on new session anchor
      if(anchor != g_slots[s].sessionAnchor)
      {
         g_slots[s].sessionAnchor = anchor;
         g_slots[s].shortFired    = false;
         g_slots[s].longFired     = false;
         
         double pdH[1], pdL[1];
         g_slots[s].pdHigh = (CopyHigh(g_slots[s].name, PERIOD_D1, 1, 1, pdH) > 0) ? pdH[0] : -DBL_MAX;
         g_slots[s].pdLow  = (CopyLow(g_slots[s].name,  PERIOD_D1, 1, 1, pdL) > 0) ? pdL[0] :  DBL_MAX;
      }
      
      // Accumulate Pre-Killzone Range High & Low
      double rH = -DBL_MAX, rL = DBL_MAX;
      for(int j = 1; j < 50; j++)
      {
         if(rates[j].time < rStart) break;
         if(rates[j].time >= rStart && rates[j].time < kOpen)
         {
            if(rates[j].high > rH) rH = rates[j].high;
            if(rates[j].low  < rL) rL = rates[j].low;
         }
      }
      g_slots[s].rangeHigh = rH;
      g_slots[s].rangeLow  = rL;
      
      // Update Live Status
      if(nowSrv >= rStart && nowSrv < kOpen)      g_slots[s].statusText = "BUILDING RANGE";
      else if(nowSrv >= kOpen && nowSrv < kEnd)   g_slots[s].statusText = "KILLZONE ACTIVE";
      else                                        g_slots[s].statusText = "STANDBY";
      
      // Only evaluate closed bar once per M15 interval
      if(rates[0].time == g_slots[s].lastBarTime) continue;
      g_slots[s].lastBarTime = rates[0].time;
      
      if(closedBarSrv < kOpen || closedBarSrv >= kEnd) continue;
      if(rH <= rL) continue;
      
      // Fetch ATR(14) and H4 EMA(50)
      double atrBuf[1], h4Ema[1], h4Close[1];
      if(CopyBuffer(g_slots[s].atrHandle, 0, 1, 1, atrBuf) <= 0) continue;
      double atr = atrBuf[0];
      
      bool h4Bull = false, h4Bear = false;
      if(CopyBuffer(g_slots[s].h4EmaHandle, 0, 0, 1, h4Ema) > 0 &&
         CopyClose(g_slots[s].name, PERIOD_H4, 0, 1, h4Close) > 0)
      {
         h4Bull = (h4Close[0] > h4Ema[0]);
         h4Bear = (h4Close[0] < h4Ema[0]);
      }
      
      double high  = rates[1].high;
      double low   = rates[1].low;
      double open  = rates[1].open;
      double close = rates[1].close;
      double rng   = high - low;
      
      if(rng <= 0 || rng > atr * InpMaxBarAtr) continue;
      
      double volSum = 0;
      for(int v = 2; v <= 21; v++) volSum += (double)rates[v].tick_volume;
      if(InpRequireVolSpike && (double)rates[1].tick_volume < (volSum / 20.0) * 1.15) continue;
      
      double minSwp = atr * InpMinSweepAtr;
      double slBuf  = atr * InpSlBufferAtr;
      
      // --- BEARISH SWEEP ---
      if(!g_slots[s].shortFired)
      {
         bool swpRH = (high >= rH + minSwp && close < rH);
         bool swpPH = (g_slots[s].pdHigh > 0 && high >= g_slots[s].pdHigh + minSwp && close < g_slots[s].pdHigh);
         if(swpRH || swpPH)
         {
            double uWickPct = ((high - MathMax(open, close)) / rng) * 100.0;
            if(uWickPct >= InpMinWickPct)
            {
               g_slots[s].shortFired = true;
               string pool  = (swpRH && swpPH) ? "DUAL(PMH+PDH)" : (swpPH ? "PDH-SWEEP" : "PMH-SWEEP");
               string grade = h4Bear ? "A+ [H4 Trend]" : "B [Counter-H4]";
               double sl    = high + slBuf;
               double stop  = sl - close;
               double tp1   = close - (stop * 1.5);
               double tp2   = MathMax(rL, close - (stop * 3.0));
               
               SendScannerAlert(s, "SHORT", pool, grade, close, sl, tp1, tp2, stop);
            }
         }
      }
      
      // --- BULLISH SWEEP ---
      if(!g_slots[s].longFired)
      {
         bool swpRL = (low <= rL - minSwp && close > rL);
         bool swpPL = (g_slots[s].pdLow < DBL_MAX && low <= g_slots[s].pdLow - minSwp && close > g_slots[s].pdLow);
         if(swpRL || swpPL)
         {
            double lWickPct = ((MathMin(open, close) - low) / rng) * 100.0;
            if(lWickPct >= InpMinWickPct)
            {
               g_slots[s].longFired = true;
               string pool  = (swpRL && swpPL) ? "DUAL(PML+PDL)" : (swpPL ? "PDL-SWEEP" : "PML-SWEEP");
               string grade = h4Bull ? "A+ [H4 Trend]" : "B [Counter-H4]";
               double sl    = low - slBuf;
               double stop  = close - sl;
               double tp1   = close + (stop * 1.5);
               double tp2   = MathMin(rH, close + (stop * 3.0));
               
               SendScannerAlert(s, "LONG", pool, grade, close, sl, tp1, tp2, stop);
            }
         }
      }
   }
   
   RenderMultiAssetHud();
}

//+------------------------------------------------------------------+
//| Format & Dispatch Mobile Alert                                   |
//+------------------------------------------------------------------+
void SendScannerAlert(int idx, string dir, string pool, string grade, double ent, double sl, double tp1, double tp2, double stopPts)
{
   string sym     = g_slots[idx].name;
   int    digits  = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
   double riskUsd = 0.0;
   double lots    = CalcSymbolLots(sym, stopPts, riskUsd);
   
   g_slots[idx].lastSignal = StringFormat("%s @ %s (%s)", dir, DoubleToString(ent, digits), pool);
   
   string msg = StringFormat("[%s M15 %s] %s | %s | Lot: %.2f ($%.0f) | Ent: %s | SL: %s | TP1: %s | TP2: %s",
                             sym, dir, pool, grade, lots, riskUsd,
                             DoubleToString(ent, digits), DoubleToString(sl, digits),
                             DoubleToString(tp1, digits), DoubleToString(tp2, digits));
                             
   if(InpDesktopAlert) Alert(msg);
   if(InpMobilePush)   SendNotification(msg);
}

//+------------------------------------------------------------------+
//| Render Unified Multi-Symbol Matrix on Host Chart                 |
//+------------------------------------------------------------------+
void RenderMultiAssetHud()
{
   string hud = "====================================================================================\n";
   hud += StringFormat(" MULTI-ASSET M15 QUANT SCANNER | UTC: %s | Equity: $%.0f\n",
                       TimeToString(TimeGMT(), TIME_SECONDS), AccountInfoDouble(ACCOUNT_EQUITY));
   hud += "====================================================================================\n";
   hud += "SYMBOL     | SESSION STATE    | M15 ATR  | RANGE HIGH / LOW      | LAST SIGNAL\n";
   hud += "------------------------------------------------------------------------------------\n";
   
   for(int i = 0; i < g_slotCount; i++)
   {
      double atrBuf[1];
      double atr = (CopyBuffer(g_slots[i].atrHandle, 0, 0, 1, atrBuf) > 0) ? atrBuf[0] : 0.0;
      int d = (int)SymbolInfoInteger(g_slots[i].name, SYMBOL_DIGITS);
      string rngStr = (g_slots[i].rangeHigh > g_slots[i].rangeLow)
                      ? (DoubleToString(g_slots[i].rangeHigh, d) + " / " + DoubleToString(g_slots[i].rangeLow, d))
                      : "Waiting Window";
                      
      hud += StringFormat("%-10s | %-16s | %-8.2f | %-21s | %s\n",
                          g_slots[i].name, g_slots[i].statusText, atr, rngStr, g_slots[i].lastSignal);
   }
   hud += "====================================================================================\n";
   Comment(hud);
}
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Scalping US100 and US500

Post by PTScalper »

3. Deployment & Mobile Execution Checklist

MarketWatch Pre-Check: Ensure US500, US100, DE40, and XAUUSD are visible inside your MarketWatch list so the terminal keeps their M15/H4/D1 bar caches synchronized in memory.

Single Host Chart: Open a single clean M15 chart (e.g., US500), hide the grid (Ctrl+G), and attach the scanner. You do not need to open separate charts for the other three symbols.

Handling Simultaneous Signals: If US500 and US100 both push an M15 sweep alert on the same 09:45 or 10:00 NY candle close, pick the instrument showing the A+ H4-aligned grade and the deeper rejection wick percentage—or split the lot size in half across both—to keep your total series risk strictly within your 1%–2% risk cap.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply