Page 2 of 3
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:18 pm
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.
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:19 pm
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
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:19 pm
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);
}
}
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:19 pm
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.
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:20 pm
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.
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:20 pm
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
}
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:21 pm
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.
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:22 pm
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.
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:22 pm
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);
}
}
}
Re: Event blackout module before NFP and CPI
Posted: Fri Sep 18, 2026 8:23 pm
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.