Page 2 of 3

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:26 pm
by PTScalper
The Micro-Lot Canary

Your protocol of requiring a human approval stage before the lot size leaves micro is the algorithmic equivalent of a canary deployment. It proves that the execution path—slippage tolerance, order rejection handling, and latency—functions in the real world without risking serious capital. When the bot passes this incubation phase without throwing array out-of-bounds errors or entering infinite order-send loops, only then do you scale the risk.

The Audit Trail

You are absolutely right about the journal. If you intervene, you own that outcome. "The robot did it" is not a post-mortem; it is an admission of poorly defined parameters or missing operational controls.

When the kill switch fires or you manually halt the system, the journal entry must identify exactly which risk gate triggered, why the regime shifted, or what infrastructure failed. The ultimate goal of algorithmic ops is to patch the code so that your specific manual intervention never has to happen again. You chose the settings, and you chose the kill policy—the bot is just executing your discipline at scale.

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:27 pm
by PTScalper
Here is an object-oriented MQL5 implementation of the risk gate hierarchy and hard kill-switch.

Because an autonomous system must protect itself before it hunts for setups, this CRiskManager class is designed to run at the very top of your OnTick() loop. If any operational or portfolio boundary is breached, it forcefully flattens the account, locks the execution state, and requires a human to restart the terminal.

Code: Select all

//+------------------------------------------------------------------+
//| CRiskManager.mqh                                                 |
//| Operational Risk & Kill Switch Controller                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\DealInfo.mqh>

class CRiskManager
  {
private:
   CTrade            m_trade;
   CPositionInfo     m_position;
   CDealInfo         m_deal;
   
   double            m_max_daily_loss_pct;
   int               m_max_spread_points;
   int               m_max_api_errors;
   
   int               m_consecutive_errors;
   bool              m_kill_switch_active;
   ulong             m_magic_number;

   // Calculates realized PnL for the current day + floating open PnL
   double            GetDailyPnL(void);
   // The nuclear option: close everything and halt
   void              FlattenAndHalt(string reason);

public:
                     CRiskManager(double max_loss_pct, int max_spread, int max_errors, ulong magic);
   
   // Master gatekeeper - run this before any trade logic
   bool              IsSafeToTrade(string symbol);
   
   // Call these from your execution scoreboard
   void              ReportApiError(void);
   void              ResetWatchdog(void);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRiskManager::CRiskManager(double max_loss_pct, int max_spread, int max_errors, ulong magic)
  {
   m_max_daily_loss_pct = max_loss_pct;
   m_max_spread_points  = max_spread;
   m_max_api_errors     = max_errors;
   m_magic_number       = magic;
   m_consecutive_errors = 0;
   m_kill_switch_active = false;
   
   m_trade.SetExpertMagicNumber(m_magic_number);
  }

//+------------------------------------------------------------------+
//| Main Risk Gate Evaluation                                        |
//+------------------------------------------------------------------+
bool CRiskManager::IsSafeToTrade(string symbol)
  {
   // 1. Check if we are already dead
   if(m_kill_switch_active) return false;

   // 2. System Watchdog: Connection & API Errors
   if(!TerminalInfoInteger(TERMINAL_CONNECTED))
     {
      Print("[WATCHDOG] Broker API disconnected. Pausing evaluation.");
      return false; // Don't flatten on momentary disconnect, just pause
     }
     
   if(m_consecutive_errors >= m_max_api_errors)
     {
      FlattenAndHalt("System Watchdog Tripped: API Error Limit Reached");
      return false;
     }

   // 3. Market Quality Gate: Spread / Liquidity check
   long current_spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
   if(current_spread > m_max_spread_points)
     {
      PrintFormat("[MARKET GATE] Spread (%d pts) exceeds limit (%d pts). Pausing.", current_spread, m_max_spread_points);
      return false;
     }

   // 4. Portfolio Circuit Breaker: Hard Daily Loss Limit
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double daily_pnl = GetDailyPnL();
   double max_loss_amount = balance * (m_max_daily_loss_pct / 100.0);
   
   if(daily_pnl <= -max_loss_amount)
     {
      FlattenAndHalt(StringFormat("Circuit Breaker: Daily PnL (%.2f) breached hard limit (%.2f)", daily_pnl, -max_loss_amount));
      return false;
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Nuclear Option: Close everything and permanently lock            |
//+------------------------------------------------------------------+
void CRiskManager::FlattenAndHalt(string reason)
  {
   Print("\n=======================================================");
   Print("[KILL SWITCH TRIGGERED] Reason: ", reason);
   Print("Initiating emergency position flattening...");
   
   m_kill_switch_active = true;
   
   // Loop backward to safely close positions by index
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(m_position.SelectByIndex(i))
        {
         if(m_position.Magic() == m_magic_number)
           {
            int retry = 0;
            while(!m_trade.PositionClose(m_position.Ticket()) && retry < 3)
              {
               Print("[WARNING] Failed to close position ", m_position.Ticket(), " Retrying...");
               Sleep(500); // Backoff before retry
               retry++;
              }
           }
        }
     }
     
   Print("[SYSTEM HALTED] Bot is locked. Human intervention required.");
   Print("=======================================================\n");
  }

//+------------------------------------------------------------------+
//| Helpers                                                          |
//+------------------------------------------------------------------+
void CRiskManager::ReportApiError(void) { m_consecutive_errors++; }
void CRiskManager::ResetWatchdog(void)  { m_consecutive_errors = 0; }

double CRiskManager::GetDailyPnL(void)
  {
   double pnl = 0.0;
   
   // 1. Floating PnL of open positions
   for(int i=0; i<PositionsTotal(); i++)
     {
      if(m_position.SelectByIndex(i) && m_position.Magic() == m_magic_number)
         pnl += m_position.Profit() + m_position.Swap() + m_position.Commission();
     }
     
   // 2. Realized PnL of closed deals for today
   datetime today_start = iTime(Symbol(), PERIOD_D1, 0);
   datetime now = TimeCurrent();
   
   if(HistorySelect(today_start, now))
     {
      for(int i=0; i<HistoryDealsTotal(); i++)
        {
         ulong deal_ticket = HistoryDealGetTicket(i);
         if(HistoryDealSelect(deal_ticket))
           {
            if(HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == m_magic_number &&
               HistoryDealGetInteger(deal_ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
              {
               pnl += HistoryDealGetDouble(deal_ticket, DEAL_PROFIT) + 
                      HistoryDealGetDouble(deal_ticket, DEAL_SWAP) + 
                      HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
              }
           }
        }
     }
   return pnl;
  }

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:28 pm
by PTScalper
Integration into your architecture

To use this, you instantiate the object globally and gate your logic inside OnTick():

Instantiation: CRiskManager Risk(3.0, 15, 5, 123456); sets a 3% hard daily loss limit, a 15-point spread ceiling, a 5-consecutive-error API watchdog, and binds to your EA's magic number.

The Gate: At the very top of OnTick(), you call if(!Risk.IsSafeToTrade(_Symbol)) return;. If this returns false, the EA aborts the tick instantly. It never even looks at your 15-minute price action arrays.

The Scoreboard Link: In your execution function, if OrderSend() fails due to an API timeout, requote, or invalid volume, you call Risk.ReportApiError(). If it succeeds, call Risk.ResetWatchdog().

The most critical part is the FlattenAndHalt retry loop. During a high-impact regime shift (like a flash crash), broker liquidity evaporates and the first PositionClose() request will often be rejected. The loop enforces a hard retry with a 500ms backoff to ensure the EA actually gets you flat before it permanently locks itself.

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:28 pm
by PTScalper
Integrating an HTTP GET request in MQL5 requires bypassing the terminal's security sandbox and managing network rate limits. The EA cannot pull data on every tick; it must cache the JSON locally, evaluate the time delta, and trigger the flatten command when the 30-minute window is breached.

Here is the architectural implementation for an autonomous news gate pulling from the community-standard ForexFactory JSON endpoint.

1. Terminal Security Prerequisite

Before WebRequest() will fire, you must explicitly whitelist the API endpoint in MetaTrader 5:

1.) Open Tools -> Options -> Expert Advisors.

2.) Check "Allow WebRequest for listed URL".

3.) Add [https://nfs.faireconomy.media](https://nfs.faireconomy.media) to the list. If you miss this, the request will fail with Error 4014.

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:29 pm
by PTScalper
2. The HTTP GET News Filter Class (MQL5)

This class isolates the network call, caches the calendar to prevent IP bans, and evaluates the current broker time against the 30-minute exclusion window.

Code: Select all

//+------------------------------------------------------------------+
//| CNewsFilter.mqh                                                  |
//| HTTP GET ForexFactory News Gate                                  |
//+------------------------------------------------------------------+
class CNewsFilter
  {
private:
   string            m_url;
   int               m_flat_before_sec;
   int               m_flat_after_sec;
   datetime          m_last_fetch_time;
   datetime          m_next_danger_time;
   
   bool              FetchCalendar(string target_currency);

public:
                     CNewsFilter(int mins_before, int mins_after);
   bool              IsSafeToTrade(string target_currency);
  };

CNewsFilter::CNewsFilter(int mins_before, int mins_after)
  {
   m_url = "https://nfs.faireconomy.media/ff_calendar_thisweek.json";
   m_flat_before_sec = mins_before * 60;
   m_flat_after_sec  = mins_after * 60;
   m_last_fetch_time = 0;
   m_next_danger_time = 0;
  }

bool CNewsFilter::FetchCalendar(string target_currency)
  {
   char post[], result[];
   string headers;
   
   ResetLastError();
   // 5000ms timeout. Must run asynchronously or inside a timer if used on UI thread
   int res = WebRequest("GET", m_url, "", NULL, 5000, post, 0, result, headers);
   
   if(res == -1)
     {
      int err = GetLastError();
      if(err == 4014) 
         Print("[ERROR] WebRequest denied. Add API to allowed URLs in MT5 Options.");
      else 
         Print("[ERROR] Calendar HTTP GET failed. Code: ", err);
      return false;
     }
     
   // Convert byte array to string. Strip the trailing null character if necessary.
   string json_data = CharArrayToString(result);
   
   // -------------------------------------------------------------------
   // JSON PARSING & TIMEZONE MATH 
   // 1. Use JAson.mqh to parse the array of event objects.
   // 2. Filter for "impact": "High" and "country": target_currency.
   // 3. Extract the "date" string (e.g., "2026-10-01T10:00:00-04:00").
   // 4. Convert ISO8601 to Broker Server Time and store in m_next_danger_time.
   // -------------------------------------------------------------------
   
   m_last_fetch_time = TimeCurrent(); // Cache timestamp to avoid rate-limiting
   return true;
  }

bool CNewsFilter::IsSafeToTrade(string target_currency)
  {
   // Refresh the calendar cache every 4 hours
   if(TimeCurrent() - m_last_fetch_time > 14400)
     {
      FetchCalendar(target_currency);
     }
     
   // Evaluate the flat window against Broker Server Time
   if(m_next_danger_time > 0)
     {
      datetime now = TimeCurrent();
      
      // Are we inside the danger window? (e.g., 30 mins before)
      if(now >= (m_next_danger_time - m_flat_before_sec) && 
         now <= (m_next_danger_time + m_flat_after_sec))
        {
         PrintFormat("[NEWS GATE] Flat filter active. High-impact event at %s", TimeToString(m_next_danger_time));
         return false;
        }
        
      // Event has passed and cooled off; reset to find the next one
      if(now > (m_next_danger_time + m_flat_after_sec))
        {
         m_next_danger_time = 0; 
         FetchCalendar(target_currency); 
        }
     }
     
   return true;
  }

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:29 pm
by PTScalper
3. Integration into the Risk Manager

To properly flatten the account 30 minutes before the event, you inject this into the IsSafeToTrade method of your main CRiskManager class. Because forex pairs involve two economies, you must check both the base and quote currencies.

Code: Select all

// Instantiate globally
CNewsFilter News(30, 30); // 30 mins before, 30 mins after

// Inside CRiskManager::IsSafeToTrade()
string base_currency = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
string quote_currency = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);

if(!News.IsSafeToTrade(base_currency) || !News.IsSafeToTrade(quote_currency))
  {
   if(PositionsTotal() > 0)
     {
      // The 30-minute mark was just breached. Kill open exposure.
      FlattenAndHalt("High-Impact News Approaching. Flattening exposure.");
     }
   return false; // Prevent new signals from executing
  }

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:29 pm
by PTScalper
Two Engineering Hurdles to Watch

The Timezone Trap: ForexFactory returns times in Eastern Time (EST/EDT) tagged with offsets like -04:00. Your MT5 TimeCurrent() is broker server time (usually EET/EEST, which is UTC+2 or UTC+3). If you do not calculate the exact delta between the JSON offset and your broker's server time, your 30-minute flat filter will fire hours late, directly during the news spike.

Blocking Calls: WebRequest() is a synchronous blocking function. If the API endpoint hangs, your entire EA halts until the 5000ms timeout hits. It is highly recommended to move FetchCalendar() out of OnTick() and into OnTimer(), allowing the HTTP request to process without delaying your 1-minute or 5-minute price action arrays.

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:30 pm
by PTScalper
MQL5 does not have a native parser for ISO 8601 strings, so you must extract the date and time components manually. Furthermore, calculating the broker offset requires extreme precision—if you use TimeCurrent() over the weekend when the market is closed, the lack of incoming ticks will completely break your offset math.

Here is the robust, production-ready function to parse the ForexFactory JSON timestamp (e.g., "2026-10-01T10:00:00-04:00") and convert it accurately to your broker's server time.

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:31 pm
by PTScalper
The Parsing Function (MQL5)

Code: Select all

//+------------------------------------------------------------------+
//| ISO 8601 to Broker Time Converter                                |
//+------------------------------------------------------------------+
datetime ParseISO8601ToBrokerTime(string iso_date)
  {
   // Expects format: "YYYY-MM-DDTHH:MM:SS-ZZ:ZZ" 
   // Example: "2026-10-01T10:00:00-04:00" or "2026-10-01T14:00:00Z"
   if(StringLen(iso_date) < 19) 
      return 0; // Malformed string guard

   // 1. Extract raw local time components from fixed string indices
   MqlDateTime dt;
   dt.year = (int)StringToInteger(StringSubstr(iso_date, 0, 4));
   dt.mon  = (int)StringToInteger(StringSubstr(iso_date, 5, 2));
   dt.day  = (int)StringToInteger(StringSubstr(iso_date, 8, 2));
   dt.hour = (int)StringToInteger(StringSubstr(iso_date, 11, 2));
   dt.min  = (int)StringToInteger(StringSubstr(iso_date, 14, 2));
   dt.sec  = (int)StringToInteger(StringSubstr(iso_date, 17, 2));

   // Convert struct to epoch seconds (treats the parsed time as raw epoch)
   datetime raw_time = StructToTime(dt);

   // 2. Parse the UTC offset from the string (e.g., "-04:00")
   int offset_seconds = 0;
   
   if(StringLen(iso_date) >= 24)
     {
      string sign_char = StringSubstr(iso_date, 19, 1);
      if(sign_char == "+" || sign_char == "-")
        {
         int sign = (sign_char == "+") ? 1 : -1;
         int tz_hour = (int)StringToInteger(StringSubstr(iso_date, 20, 2));
         int tz_min  = (int)StringToInteger(StringSubstr(iso_date, 23, 2));
         
         offset_seconds = sign * ((tz_hour * 3600) + (tz_min * 60));
        }
     }

   // 3. Normalize the raw time to absolute UTC
   // E.g., 10:00 - (-14400 seconds) = 14:00 UTC
   datetime utc_time = raw_time - offset_seconds;

   // 4. Calculate Broker Offset and Shift to Broker Server Time
   // CRITICAL: Use TimeTradeServer() instead of TimeCurrent() to prevent 
   // weekend tick-pause bugs where the delta calculates to -48 hours.
   int broker_offset = (int)(TimeTradeServer() - TimeGMT());
   datetime broker_time = utc_time + broker_offset;

   return broker_time;
  }

Re: My EA for autonomous forex scalping robot

Posted: Thu Oct 01, 2026 6:31 pm
by PTScalper
Why this architecture works:

Fixed-Index Extraction: Because ISO 8601 strings from APIs like ForexFactory are strictly formatted to the character, StringSubstr is mathematically faster and uses less CPU overhead than splitting the string into arrays or relying on regex patterns.

Absolute UTC Normalization: By extracting the timezone offset (-04:00) directly from the string and subtracting it in seconds, the function mathematically standardizes the event time to pure UTC (Greenwich Mean Time).

The TimeTradeServer() Override: This is the most common pitfall in algorithmic timezone math. Most developers calculate offset using TimeCurrent() - TimeGMT(). However, TimeCurrent() stops updating on Friday night when ticks stop arriving. TimeGMT() is driven by your PC clock and continues ticking. Over the weekend, that formula will incorrectly calculate your broker as being 48 hours behind UTC. Using TimeTradeServer() forces the terminal to calculate the live simulated server time based on your PC clock, ensuring the offset remains flawlessly accurate 24/7.