Advertisement IC Markets

Event blackout module before NFP and CPI

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

MQL4-Specific Upgrades

Reverse-Iteration Liquidation: MT4 dynamically shifts the index of remaining orders when one is closed. The loop for(int i = OrdersTotal() - 1; i >= 0; i--) prevents the EA from skipping trades, a common bug that leaves orphan positions open during news spikes.

Pending Order Purge: Prop firms track slippage-induced breaches on pending orders exactly like market orders. The FlattenInventory() function explicitly hunts down and deletes OP_BUYLIMIT, OP_SELLLIMIT, OP_BUYSTOP, and OP_SELLSTOP orders.

Server Spam Prevention: The hasFlattenedThisCycle boolean ensures the EA executes the close commands exactly once at T-30. Without this, the EA would fire OrderClose() requests on every single tick for 45 minutes, resulting in an immediate broker block for API spam.

Magic Number Filtering: If you run multiple strategies on one account, you can isolate the blackout to a specific EA by assigning its Magic Number, or leave it at 0 to ruthlessly flatten everything on the current chart symbol regardless of origin.
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

To expand the hard-flatten protocol across the entire account, you must remove the symbol restriction. However, doing so introduces a critical MQL4 mechanical quirk: you can no longer use the native Bid and Ask variables.

In MT4, Bid and Ask only return the price for the specific chart the EA is attached to. If your EA is on EURUSD, and it tries to close a GBPJPY trade using the EURUSD Bid price, the broker server will reject it with an "Invalid Price" error (Error 129).

To fix this, we introduce MarketInfo(), which dynamically fetches the correct Bid/Ask price for whichever symbol the loop is currently targeting.

Here is the updated configuration block and the rewritten FlattenInventory() function. Replace these sections in your existing code:

1. Add the Global Input

Add this new toggle to your _grp3 inputs so you can switch between chart-only and account-wide liquidation without altering the code again.

Code: Select all

input string   _grp3 = "--- Execution Risk Controls ---";
input bool     FlattenAllSymbols   = true; // True = Account Wide, False = Current Chart Only
input bool     FlattenOnTrigger    = true; // Auto-flatten open & pending orders
input int      SlippagePoints      = 30;   // Maximum slippage tolerance (points)
input int      MagicNumberFilter   = 0;    // 0 = Flatten all, >0 = specific EA
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

2. Update the Flatten Logic

Replace your entire FlattenInventory() function with this multi-symbol compatible version:

Code: Select all

// =====================================================================
// GLOBAL HARD-FLATTEN PROTOCOL
// =====================================================================
void FlattenInventory() {
    if(!FlattenOnTrigger || hasFlattenedThisCycle) return;
    
    int totalOrders = OrdersTotal();
    if(totalOrders == 0) {
        hasFlattenedThisCycle = true;
        return;
    }

    // MANDATORY MT4 LOGIC: Iterate backwards
    for(int i = totalOrders - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            
            // Determine if the order matches our symbol constraint
            bool symbolMatch = FlattenAllSymbols ? true : (OrderSymbol() == Symbol());
            
            if(symbolMatch && (MagicNumberFilter == 0 || OrderMagicNumber() == MagicNumberFilter)) {
                
                int type = OrderType();
                bool result = false;
                string currentOrderSymbol = OrderSymbol();
                
                // CRITICAL: Dynamically pull the correct Bid/Ask for the target symbol.
                // You cannot use standard Bid/Ask variables for cross-chart liquidations.
                double targetBid = MarketInfo(currentOrderSymbol, MODE_BID);
                double targetAsk = MarketInfo(currentOrderSymbol, MODE_ASK);
                
                // Liquidate active market exposure
                if(type == OP_BUY) {
                    result = OrderClose(OrderTicket(), OrderLots(), targetBid, SlippagePoints, clrRed);
                } 
                else if(type == OP_SELL) {
                    result = OrderClose(OrderTicket(), OrderLots(), targetAsk, SlippagePoints, clrRed);
                }
                // Purge pending limit/stop orders
                else if(type > OP_SELL) {
                    result = OrderDelete(OrderTicket(), clrOrange);
                }
                
                if(!result) {
                    Print("Risk Protocol Error: Failed to close ticket #", OrderTicket(), 
                          " on ", currentOrderSymbol, ". Error code: ", GetLastError());
                }
            }
        }
    }
    
    // If we successfully cleared inventory (or if we only filtered for a specific magic number), flag it
    if(OrdersTotal() == 0 || MagicNumberFilter > 0) {
        hasFlattenedThisCycle = true;
        Print("Risk Protocol: Global inventory successfully flattened at T-", PreMins);
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

Key mechanical changes:

FlattenAllSymbols boolean check: The script dynamically evaluates whether to enforce OrderSymbol() == Symbol() or bypass it entirely.

MarketInfo(currentOrderSymbol, MODE_BID): This ensures that if the loop grabs an AUDCAD trade while attached to a EURUSD chart, it sends the exact, real-time AUDCAD bid price to the broker for liquidation, bypassing the "Invalid Price" rejection.

RefreshRates() removed: RefreshRates() only updates the native Bid/Ask variables of the host chart. Because we are now directly polling the server for the exact cross-pair quotes via MarketInfo(), RefreshRates() is no longer required in this loop.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

Transitioning this module to MetaTrader 5 (MQL5) requires a fundamental rewrite of the execution engine.

The biggest architectural difference between MT4 and MT5 is how they handle active trades vs. pending limits/stops.

MT4 treats everything as an "Order" (whether it's a running trade or a pending limit).

MT5 strictly separates them: A pending limit is an Order. Once triggered, it becomes a Position. Historical trades become Deals.

Because of this, a true global MT5 flatten protocol requires two separate backwards-iterating loops: one to hunt and kill active positions, and another to hunt and delete pending orders. We will also utilize MT5's standard #include <Trade\Trade.mqh> library, which safely calculates cross-symbol Bid/Ask prices for us automatically during liquidation.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

Here is the native, institutional-grade MQL5 version:

Tier-1 Event Risk Mitigation Module (Native MQL5)

Code: Select all

//+------------------------------------------------------------------+
//| Risk Mitigation: Event Blackout Module [Tier-1]                  |
//| Compiler: MQL5 (MetaTrader 5)                                    |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>

// Initialize the standard trade class for execution
CTrade trade;

// =====================================================================
// OPERATIONAL PARAMETERS (MQL5 supports native input groups)
// =====================================================================
input group "--- Event Time (Broker Server Time) ---"
input int      NewsHour_ServerTime = 15; // Release Hour (Match Market Watch)
input int      NewsMinute          = 30; // Release Minute

input group "--- Operational Window ---"
input int      PreMins             = 30; // T-Minus Moratorium (mins)
input int      PostMins            = 15; // Post-Release Stand-Aside (mins)

input group "--- Execution Risk Controls ---"
input bool     FlattenAllSymbols   = true; // True = Account Wide, False = Current Chart Only
input bool     FlattenOnTrigger    = true; // Auto-flatten open positions & pending orders
input ulong    SlippagePoints      = 30;   // Maximum slippage tolerance (points)
input ulong    MagicNumberFilter   = 0;    // 0 = Flatten all, >0 = specific EA Magic Number

// Internal state flag to prevent server spam during the blackout
bool hasFlattenedThisCycle = false; 

// =====================================================================
// CORE TEMPORAL LOGIC
// =====================================================================
bool IsBlackoutActive() {
    datetime currentServerTime = TimeCurrent();
    MqlDateTime tm;
    TimeToStruct(currentServerTime, tm);
    
    // Convert current time to absolute minutes of the current day for boundary math
    int currentMinsOfDay = tm.hour * 60 + tm.min;
    int newsMinsOfDay    = NewsHour_ServerTime * 60 + NewsMinute;
    
    int blackoutStart = newsMinsOfDay - PreMins;
    int blackoutEnd   = newsMinsOfDay + PostMins;
    
    if(currentMinsOfDay >= blackoutStart && currentMinsOfDay <= blackoutEnd) {
        return true;
    }
    
    // Reset the flatten flag once we are completely clear of the blackout window
    if(currentMinsOfDay > blackoutEnd) {
        hasFlattenedThisCycle = false; 
    }
    
    return false;
}

// =====================================================================
// GLOBAL HARD-FLATTEN PROTOCOL (MQL5 DUAL-LOOP ARCHITECTURE)
// =====================================================================
void FlattenInventory() {
    if(!FlattenOnTrigger || hasFlattenedThisCycle) return;
    
    bool allCleared = true;
    
    // Configure slippage for the CTrade object
    trade.SetDeviationInPoints(SlippagePoints);

    // ---------------------------------------------------------
    // LOOP 1: CLOSE ACTIVE MARKET POSITIONS
    // ---------------------------------------------------------
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong posTicket = PositionGetTicket(i);
        if(posTicket > 0) {
            string posSymbol = PositionGetString(POSITION_SYMBOL);
            ulong  posMagic  = PositionGetInteger(POSITION_MAGIC);
            
            bool symbolMatch = FlattenAllSymbols ? true : (posSymbol == _Symbol);
            
            if(symbolMatch && (MagicNumberFilter == 0 || posMagic == MagicNumberFilter)) {
                // CTrade automatically handles fetching the cross-chart Bid/Ask
                if(!trade.PositionClose(posTicket)) {
                    Print("Risk Protocol Error: Failed to close position #", posTicket, " on ", posSymbol, ". Code: ", GetLastError());
                    allCleared = false;
                }
            }
        }
    }

    // ---------------------------------------------------------
    // LOOP 2: DELETE PENDING ORDERS (Limits & Stops)
    // ---------------------------------------------------------
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        ulong ordTicket = OrderGetTicket(i);
        if(ordTicket > 0) {
            string ordSymbol = OrderGetString(ORDER_SYMBOL);
            ulong  ordMagic  = OrderGetInteger(ORDER_MAGIC);
            
            bool symbolMatch = FlattenAllSymbols ? true : (ordSymbol == _Symbol);
            
            if(symbolMatch && (MagicNumberFilter == 0 || ordMagic == MagicNumberFilter)) {
                if(!trade.OrderDelete(ordTicket)) {
                    Print("Risk Protocol Error: Failed to delete pending order #", ordTicket, " on ", ordSymbol, ". Code: ", GetLastError());
                    allCleared = false;
                }
            }
        }
    }
    
    // If both loops successfully cleared all targeted inventory, flag it
    if(allCleared && (PositionsTotal() == 0 && OrdersTotal() == 0 || MagicNumberFilter > 0)) {
        hasFlattenedThisCycle = true;
        Print("Risk Protocol: Global MT5 inventory successfully flattened at T-", PreMins);
    }
}

// =====================================================================
// VISUAL TELEMETRY
// =====================================================================
void UpdateStatusHUD(bool isRestricted) {
    string objName = "Blackout_HUD_MT5";
    
    if(ObjectFind(0, objName) < 0) {
        ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
        ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 15);
        ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 20);
        ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 11);
        ObjectSetString(0, objName, OBJPROP_FONT, "Trebuchet MS");
        ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
    }
    
    if(isRestricted) {
        ObjectSetString(0, objName, OBJPROP_TEXT, " MODULE ACTIVE: TIER-1 BLACKOUT ");
        ObjectSetInteger(0, objName, OBJPROP_COLOR, clrWhite);
        ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrMaroon); 
    } else {
        ObjectSetString(0, objName, OBJPROP_TEXT, " SYSTEM ARMED: CLEAR ");
        ObjectSetInteger(0, objName, OBJPROP_COLOR, clrLimeGreen);
        ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBlack);
    }
}

// =====================================================================
// EA TICK PROCESSOR
// =====================================================================
void OnTick() {
    bool blackoutState = IsBlackoutActive();
    
    UpdateStatusHUD(blackoutState);
    
    // 1. GATEKEEPER TRIGGER
    if(blackoutState) {
        // Execute dual-loop flatten protocol on the first tick of the T-30 window
        FlattenInventory();
        
        // Hard override: Return immediately. No EA entry logic can process below this line.
        return; 
    }
    
    // 2. STANDARD EXECUTION LOGIC GOES BELOW
    // Proceed with normal signal evaluation
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

MQL5-Specific Upgrades

CTrade Standard Library Integration: MQL5 requires you to build highly complex MqlTradeRequest arrays just to close a trade. The #include <Trade\Trade.mqh> standard library class acts as a wrapper, doing all the heavy lifting. Crucially, calling trade.PositionClose(ticket) automatically goes to the server, requests the exact cross-symbol Bid/Ask, and executes the close, completely neutralizing the MT4 cross-chart price errors we had to solve previously.

Dual-Loop Architecture: MT5 requires distinct PositionsTotal() and OrdersTotal() commands. We iterate backward through both arrays separately to ensure no index shifting causes an open trade to be skipped.

Data Types: Tickets and Magic Numbers in MQL5 are assigned the ulong data type (unsigned long integer) rather than MT4's standard int.

Native Input Groups: We get to use MQL5's native input group syntax, which creates beautifully organized dropdown folders in your EA's settings panel.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

Migrating this institutional risk framework to cTrader (cAlgo / C#) is where you truly experience the power of a modern API.

Unlike MetaTrader, which relies on archaic indexing loops and integer Magic Numbers, cTrader is built on C# and .NET. This allows us to use LINQ (Language Integrated Query) to elegantly filter and flatten cross-symbol positions, and it uses string-based Labels instead of Magic Numbers to track algorithmic trades.

Furthermore, cTrader's Positions and PendingOrders collections automatically handle all cross-symbol price polling under the hood, completely eliminating the "Invalid Price" errors prevalent in MetaTrader.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

Here is the native C# cBot integration for your Tier-1 Blackout Module.

Tier-1 Event Risk Mitigation Module (cTrader / C#)

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class EventBlackoutModule : Robot
    {
        // =====================================================================
        // OPERATIONAL PARAMETERS (Native cTrader Grouping)
        // =====================================================================
        [Parameter("Release Hour (Server Time)", Group = "Event Time", DefaultValue = 15, MinValue = 0, MaxValue = 23)]
        public int NewsHour_ServerTime { get; set; }

        [Parameter("Release Minute", Group = "Event Time", DefaultValue = 30, MinValue = 0, MaxValue = 59)]
        public int NewsMinute { get; set; }

        [Parameter("T-Minus Moratorium (mins)", Group = "Operational Window", DefaultValue = 30)]
        public int PreMins { get; set; }

        [Parameter("Post-Release Stand-Aside (mins)", Group = "Operational Window", DefaultValue = 15)]
        public int PostMins { get; set; }

        [Parameter("Flatten Account-Wide", Group = "Execution Risk Controls", DefaultValue = true)]
        public bool FlattenAllSymbols { get; set; }

        [Parameter("Auto-Flatten Inventory", Group = "Execution Risk Controls", DefaultValue = true)]
        public bool FlattenOnTrigger { get; set; }

        [Parameter("cBot Label Filter (Empty = All)", Group = "Execution Risk Controls", DefaultValue = "")]
        public string LabelFilter { get; set; }

        // Internal state flag to prevent server spam during the blackout
        private bool hasFlattenedThisCycle = false;

        // =====================================================================
        // CBOT TICK PROCESSOR
        // =====================================================================
        protected override void OnTick()
        {
            bool blackoutState = IsBlackoutActive();
            
            UpdateStatusHUD(blackoutState);

            // 1. GATEKEEPER TRIGGER
            if (blackoutState)
            {
                // Execute flatten protocol on the first tick of the T-30 window
                FlattenInventory();
                
                // Hard override: Return immediately. No cBot logic processes below this line.
                return; 
            }

            // 2. STANDARD EXECUTION LOGIC GOES BELOW
            // e.g., if (Rsi.Result.Last(1) < 30) ExecuteMarketOrder(...);
        }

        // =====================================================================
        // CORE TEMPORAL LOGIC
        // =====================================================================
        private bool IsBlackoutActive()
        {
            DateTime currentServerTime = Server.Time;
            
            // Convert current time to absolute minutes of the current day for boundary math
            int currentMinsOfDay = currentServerTime.Hour * 60 + currentServerTime.Minute;
            int newsMinsOfDay = NewsHour_ServerTime * 60 + NewsMinute;

            int blackoutStart = newsMinsOfDay - PreMins;
            int blackoutEnd = newsMinsOfDay + PostMins;

            if (currentMinsOfDay >= blackoutStart && currentMinsOfDay <= blackoutEnd)
            {
                return true;
            }

            // Reset the flatten flag once we are completely clear of the blackout window
            if (currentMinsOfDay > blackoutEnd)
            {
                hasFlattenedThisCycle = false; 
            }

            return false;
        }

        // =====================================================================
        // GLOBAL HARD-FLATTEN PROTOCOL
        // =====================================================================
        private void FlattenInventory()
        {
            if (!FlattenOnTrigger || hasFlattenedThisCycle) return;

            bool allCleared = true;

            // ---------------------------------------------------------
            // LOOP 1: CLOSE ACTIVE MARKET POSITIONS
            // ---------------------------------------------------------
            // We use .ToArray() to snapshot the collection and avoid "Collection Modified" exceptions.
            // LINQ gracefully filters for symbol and label constraints.
            var targetPositions = Positions.ToArray().Where(p => 
                (FlattenAllSymbols || p.SymbolName == Symbol.Name) &&
                (string.IsNullOrEmpty(LabelFilter) || p.Label == LabelFilter)
            );

            foreach (var pos in targetPositions)
            {
                var result = ClosePosition(pos);
                if (!result.IsSuccessful)
                {
                    Print($"Risk Protocol Error: Failed to close position {pos.Id}. Reason: {result.Error}");
                    allCleared = false;
                }
            }

            // ---------------------------------------------------------
            // LOOP 2: DELETE PENDING ORDERS (Limits & Stops)
            // ---------------------------------------------------------
            var targetOrders = PendingOrders.ToArray().Where(o => 
                (FlattenAllSymbols || o.SymbolName == Symbol.Name) &&
                (string.IsNullOrEmpty(LabelFilter) || o.Label == LabelFilter)
            );

            foreach (var order in targetOrders)
            {
                var result = CancelPendingOrder(order);
                if (!result.IsSuccessful)
                {
                    Print($"Risk Protocol Error: Failed to cancel order {order.Id}. Reason: {result.Error}");
                    allCleared = false;
                }
            }

            // If we successfully cleared all targeted inventory, flag it
            if (allCleared)
            {
                hasFlattenedThisCycle = true;
                Print($"Risk Protocol: Global cTrader inventory successfully flattened at T-{PreMins}");
            }
        }

        // =====================================================================
        // VISUAL TELEMETRY
        // =====================================================================
        private void UpdateStatusHUD(bool isRestricted)
        {
            string hudText = isRestricted ? "🚨 MODULE ACTIVE: TIER-1 BLACKOUT" : "✅ SYSTEM ARMED: CLEAR";
            Color hudColor = isRestricted ? Color.Red : Color.LimeGreen;

            // cTrader's native static text drawing anchored to the chart corner
            Chart.DrawStaticText("Blackout_HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, hudColor);
        }
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Event blackout module before NFP and CPI

Post by FTtrader »

cTrader / C# Specific Upgrades

LINQ Filtering: Unlike MetaTrader, where we had to manually check properties inside a for loop, cTrader uses C# LINQ (.Where(p => ...)). This allows us to instantly filter the entire account's portfolio down to only the exact symbols or labels we want to liquidate in a single, elegant line of code.

The .ToArray() Snapshot: Modifying a list while you are reading it usually causes a fatal crash. By appending .ToArray() to the Positions collection, we take a static snapshot of the inventory at that exact millisecond. We then loop through the snapshot and safely delete the live orders without breaking the index.

String Labels over Magic Numbers: MT4/MT5 requires you to memorize arbitrary integers (e.g., 8675309) for your strategies. cTrader uses human-readable strings. If you want this blackout module to only flatten trades opened by your momentum bot, you simply type "Momentum_v2" into the LabelFilter parameter. If you leave it empty (""), it ruthlessly flattens everything.

Native Trade Result Logging: result.IsSuccessful and result.Error automatically grab the server-side rejection reason if a trade fails to close, printing it directly to your cBot log for immediate audit tracking.
Post Reply