Seconds after BOJ headlines on USDJPY: my stand-aside timer
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Here is Version 1.0 translated natively into both MetaTrader 4 (MQL4) and MetaTrader 5 (MQL5).
To make this work flawlessly in the MetaTrader environment, we must use a "New Bar" state machine. MetaTrader’s OnTick() function triggers on every single price change. We place the Spike Kill-Switch directly inside OnTick() so it fires in milliseconds, but we restrict the EMA Entry Logic to only execute once exactly when a new candle opens, preventing repainting and mid-candle false signals.
Here are the complete Expert Advisors (EAs).
To make this work flawlessly in the MetaTrader environment, we must use a "New Bar" state machine. MetaTrader’s OnTick() function triggers on every single price change. We place the Spike Kill-Switch directly inside OnTick() so it fires in milliseconds, but we restrict the EMA Entry Logic to only execute once exactly when a new candle opens, preventing repainting and mid-candle false signals.
Here are the complete Expert Advisors (EAs).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
MetaTrader 4 (MQL4) Version
MT4 is an older procedural language, so we use iATR and iMA directly, and we must manually manage order tickets and bid/ask pricing for execution.
MT4 is an older procedural language, so we use iATR and iMA directly, and we must manually manage order tickets and bid/ask pricing for execution.
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_Protector_V1_MT4.mq4 |
//+------------------------------------------------------------------+
#property strict
// --- INPUTS ---
input string Grp1 = "--- Defense Settings ---";
input int AtrLength = 14;
input double SpikeMultiplier = 5.0; // ATR Spike Multiplier
input int EomDayStart = 27; // Month-End Danger Start Day
input string Grp2 = "--- Trading Logic ---";
input int FastMaPeriod = 10;
input int SlowMaPeriod = 20;
input double LotSize = 0.1;
input int MagicNumber = 88881;
input int Slippage = 30; // Slippage in points
// --- STATE VARIABLES ---
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
lastBarTime = Time[0]; // Initialize with current bar time
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK (Calculated every millisecond) |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Calculate Real-Time Excursion
double currentRange = High[0] - Low[0];
// 2. Fetch the Baseline ATR (Shift 1 = Previous closed bar)
double baselineAtr = iATR(Symbol(), 0, AtrLength, 1);
// 3. Evaluate Triggers
bool isInterventionSpike = currentRange > (baselineAtr * SpikeMultiplier);
bool isMonthEndDanger = Day() >= EomDayStart;
// 4. THE KILL-SWITCH (Real-Time Eject)
if(isInterventionSpike)
{
CloseAllPositions();
}
// 5. UPDATE HUD (Chart Comment)
string statusText = "\n=== BOJ PROTECTOR v1 ===";
if(isInterventionSpike)
statusText += "\n🚨 SPIKE DETECTED - EJECTING ALL!";
else if(isMonthEndDanger)
statusText += "\n⚠️ MONTH-END LOCKOUT ACTIVE";
else
statusText += "\n✅ ENGINE GREEN / ARMED";
statusText += "\nLive Range: " + DoubleToStr(currentRange / Point, 0) + " pts";
statusText += "\nATR Limit: " + DoubleToStr((baselineAtr * SpikeMultiplier) / Point, 0) + " pts";
Comment(statusText);
// 6. BAR CLOSE / NEW BAR LOGIC (For Entries)
if(Time[0] != lastBarTime)
{
lastBarTime = Time[0]; // Update state to current bar
OnBarClose(isInterventionSpike, isMonthEndDanger);
}
}
//+------------------------------------------------------------------+
//| ENTRY LOGIC (Executes exactly once per closed candle) |
//+------------------------------------------------------------------+
void OnBarClose(bool isSpike, bool isEom)
{
bool canTrade = !isSpike && !isEom;
if(canTrade)
{
double fast1 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double fast2 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
double slow1 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slow2 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
bool longCondition = fast1 > slow1 && fast2 <= slow2;
bool shortCondition = fast1 < slow1 && fast2 >= slow2;
if(longCondition)
{
CloseOppositePositions(OP_SELL);
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage, 0, 0, "BOJ Long", MagicNumber, 0, clrBlue);
}
else if(shortCondition)
{
CloseOppositePositions(OP_BUY);
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage, 0, 0, "BOJ Short", MagicNumber, 0, clrRed);
}
}
}
//+------------------------------------------------------------------+
//| HELPER: Close all trades belonging to this bot |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrGray);
}
}
}
}
void CloseOppositePositions(int tradeTypeToClose)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == tradeTypeToClose)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrGray);
}
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
MetaTrader 5 (MQL5) Version
MT5 uses an asynchronous Object-Oriented environment. We must load indicator "Handles" in OnInit() and copy their buffers into arrays inside OnTick(). To handle execution safely, we use the standard #include <Trade\Trade.mqh> library, which eliminates MT4's clumsy Bid/Ask/Slippage manual coding.
MT5 uses an asynchronous Object-Oriented environment. We must load indicator "Handles" in OnInit() and copy their buffers into arrays inside OnTick(). To handle execution safely, we use the standard #include <Trade\Trade.mqh> library, which eliminates MT4's clumsy Bid/Ask/Slippage manual coding.
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_Protector_V1_MT5.mq5 |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>
// --- INPUTS ---
input group "Defense Settings"
input int AtrLength = 14;
input double SpikeMultiplier = 5.0;
input int EomDayStart = 27;
input group "Trading Logic"
input int FastMaPeriod = 10;
input int SlowMaPeriod = 20;
input double LotSize = 0.1;
input ulong MagicNumber = 88881;
// --- STATE & OBJECTS ---
CTrade trade;
datetime lastBarTime;
// Indicator Handles
int atrHandle;
int fastMaHandle;
int slowMaHandle;
// Data Buffers
double atrBuffer[];
double fastMaBuffer[];
double slowMaBuffer[];
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
lastBarTime = iTime(_Symbol, _Period, 0);
// Initialize Indicator Handles
atrHandle = iATR(_Symbol, _Period, AtrLength);
fastMaHandle = iMA(_Symbol, _Period, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowMaHandle = iMA(_Symbol, _Period, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
// Setup array orientation (Index 0 = most recent)
ArraySetAsSeries(atrBuffer, true);
ArraySetAsSeries(fastMaBuffer, true);
ArraySetAsSeries(slowMaBuffer, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Calculate Real-Time Excursion
double currentHigh = iHigh(_Symbol, _Period, 0);
double currentLow = iLow(_Symbol, _Period, 0);
double currentRange = currentHigh - currentLow;
// 2. Fetch the Baseline ATR
if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) <= 0) return; // Shift 1
double baselineAtr = atrBuffer[0];
// 3. Evaluate Triggers
MqlDateTime dt;
TimeCurrent(dt); // Get current server time struct
bool isInterventionSpike = currentRange > (baselineAtr * SpikeMultiplier);
bool isMonthEndDanger = dt.day >= EomDayStart;
// 4. THE KILL-SWITCH (Real-Time Eject)
if(isInterventionSpike)
{
CloseAllPositions();
}
// 5. UPDATE HUD (Chart Comment)
string statusText = "\n=== BOJ PROTECTOR v1 (MT5) ===";
if(isInterventionSpike)
statusText += "\n🚨 SPIKE DETECTED - EJECTING ALL!";
else if(isMonthEndDanger)
statusText += "\n⚠️ MONTH-END LOCKOUT ACTIVE";
else
statusText += "\n✅ ENGINE GREEN / ARMED";
statusText += "\nLive Range: " + DoubleToString(currentRange / _Point, 0) + " pts";
statusText += "\nATR Limit: " + DoubleToString((baselineAtr * SpikeMultiplier) / _Point, 0) + " pts";
Comment(statusText);
// 6. BAR CLOSE / NEW BAR LOGIC
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime; // Update state
OnBarClose(isInterventionSpike, isMonthEndDanger);
}
}
//+------------------------------------------------------------------+
//| ENTRY LOGIC |
//+------------------------------------------------------------------+
void OnBarClose(bool isSpike, bool isEom)
{
bool canTrade = !isSpike && !isEom;
if(canTrade)
{
// Copy 3 bars of data (indices 0, 1, 2)
if(CopyBuffer(fastMaHandle, 0, 0, 3, fastMaBuffer) <= 0) return;
if(CopyBuffer(slowMaHandle, 0, 0, 3, slowMaBuffer) <= 0) return;
bool longCondition = fastMaBuffer[1] > slowMaBuffer[1] && fastMaBuffer[2] <= slowMaBuffer[2];
bool shortCondition = fastMaBuffer[1] < slowMaBuffer[1] && fastMaBuffer[2] >= slowMaBuffer[2];
if(longCondition)
{
CloseOppositePositions(POSITION_TYPE_SELL);
trade.Buy(LotSize, _Symbol, 0, 0, 0, "BOJ Long");
}
else if(shortCondition)
{
CloseOppositePositions(POSITION_TYPE_BUY);
trade.Sell(LotSize, _Symbol, 0, 0, 0, "BOJ Short");
}
}
}
//+------------------------------------------------------------------+
//| HELPER: Close all trades belonging to this bot |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
// Must loop backward when deleting/closing to avoid shifting indices
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
trade.PositionClose(ticket);
}
}
}
void CloseOppositePositions(ENUM_POSITION_TYPE tradeTypeToClose)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == tradeTypeToClose)
{
trade.PositionClose(ticket);
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Pro MetaTrader Architecture Notes:
The Reverse for Loop: Look at CloseAllPositions() in both versions. for(int i = OrdersTotal() - 1; i >= 0; i--). If you have 3 trades open (Indices 0, 1, 2) and you close Index 0, the remaining trades instantly shift down (Index 1 becomes 0). If you loop forward, you will skip trades and the Kill-Switch will fail. Looping backwards guarantees all positions are executed.
The HUD Comment() Output: MetaTrader does not natively support the cleanly bordered Tables of cTrader/PineScript natively without complex Object libraries. Comment() is the fastest, lowest-latency way to display your system state natively in the top left corner of your chart.
The .Last(1) MT Translation: Look at CopyBuffer(atrHandle, 0, 1, 1, atrBuffer). The 1, 1 means "Start at index 1 (the last closed bar), and copy 1 bar." This mirrors the safety mechanism preventing the current exploding tape bomb from corrupting your baseline measurement mid-candle.
The Reverse for Loop: Look at CloseAllPositions() in both versions. for(int i = OrdersTotal() - 1; i >= 0; i--). If you have 3 trades open (Indices 0, 1, 2) and you close Index 0, the remaining trades instantly shift down (Index 1 becomes 0). If you loop forward, you will skip trades and the Kill-Switch will fail. Looping backwards guarantees all positions are executed.
The HUD Comment() Output: MetaTrader does not natively support the cleanly bordered Tables of cTrader/PineScript natively without complex Object libraries. Comment() is the fastest, lowest-latency way to display your system state natively in the top left corner of your chart.
The .Last(1) MT Translation: Look at CopyBuffer(atrHandle, 0, 1, 1, atrBuffer). The 1, 1 means "Start at index 1 (the last closed bar), and copy 1 bar." This mirrors the safety mechanism preventing the current exploding tape bomb from corrupting your baseline measurement mid-candle.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Here is Version 2.0 (The Institutional Risk Engine) translated natively into both MetaTrader 4 and MetaTrader 5.
To replicate the high-performance architecture of the cTrader version, these MetaTrader EAs offload the heavy math. The Standard Deviation and Mean of the True Range are calculated only once per closed bar. The OnTick() loop only performs two lines of basic arithmetic to get the live Z-Score. This ensures your Kill-Switch reacts in microseconds without lagging the MT4/MT5 terminal.
MetaTrader 4 (MQL4) Version
To replicate the high-performance architecture of the cTrader version, these MetaTrader EAs offload the heavy math. The Standard Deviation and Mean of the True Range are calculated only once per closed bar. The OnTick() loop only performs two lines of basic arithmetic to get the live Z-Score. This ensures your Kill-Switch reacts in microseconds without lagging the MT4/MT5 terminal.
MetaTrader 4 (MQL4) Version
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_RiskEngine_V2_MT4.mq4 |
//+------------------------------------------------------------------+
#property strict
// --- 1. INPUTS & RISK PARAMETERS ---
input string Grp1 = "--- Anomaly Detection ---";
input int Lookback = 100; // Rolling Window (Bars)
input double ZThreshold = 4.5; // Z-Score Trigger (Sigma)
input string Grp2 = "--- Stand-Aside Timer ---";
input int CooldownBars = 12; // Cooldown Lockout (Bars)
input string Grp3 = "--- Month-End Dynamics ---";
input int EomDayStart = 26; // Month-End Lockout Day
input bool EomActive = true; // Enable Month-End Filter
input string Grp4 = "--- Session Awareness (Server Time) ---";
input int AsianSessStart = 0; // Danger Zone Start Hour
input int AsianSessEnd = 9; // Danger Zone End Hour
input string Grp5 = "--- Example Strategy ---";
input int FastMaPeriod = 9;
input int SlowMaPeriod = 21;
input double LotSize = 0.1;
input int MagicNumber = 88882;
input int Slippage = 30;
// --- STATE VARIABLES ---
datetime lastBarTime = 0;
double baselineMean = 0;
double baselineStdDev = 0;
int cooldownBarsRemaining = 0;
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
if(Bars < Lookback + 2) return(INIT_FAILED);
lastBarTime = Time[0];
CalculateBaselines();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK (Microsecond Execution Loop) |
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars < Lookback + 2) return;
// 1. Calculate live True Range of current unclosed candle
double currentTR = MathMax(High[0], Close[1]) - MathMin(Low[0], Close[1]);
// 2. Calculate live Z-Score
double currentZScore = 0;
if(baselineStdDev > 0)
currentZScore = (currentTR - baselineMean) / baselineStdDev;
// 3. Dynamic Context
int currentHour = Hour();
bool inDangerZone = (currentHour >= AsianSessStart && currentHour <= AsianSessEnd);
bool inMonthEndLockout = (Day() >= EomDayStart) && EomActive;
// Hyper-sensitivity: Lower threshold if in Danger Zone AND Month End
double activeTriggerLevel = (inDangerZone && inMonthEndLockout) ? (ZThreshold * 0.8) : ZThreshold;
bool isInterventionSpike = currentZScore > activeTriggerLevel;
bool inCooldown = cooldownBarsRemaining > 0;
// 4. THE KILL-SWITCH
if(isInterventionSpike)
{
CloseAllPositions();
cooldownBarsRemaining = CooldownBars; // Drip reset timer
}
// 5. UPDATE HUD
UpdateHUD(currentZScore, activeTriggerLevel, isInterventionSpike, inCooldown, inMonthEndLockout);
// 6. BAR CLOSE LOGIC (State Machine & Entries)
if(Time[0] != lastBarTime)
{
lastBarTime = Time[0];
OnBarClose();
}
}
//+------------------------------------------------------------------+
//| BAR CLOSE: MATH, TIMER & ENTRY LOGIC |
//+------------------------------------------------------------------+
void OnBarClose()
{
// 1. Recalculate Heavy Math for the new candle
CalculateBaselines();
// 2. Decrement Timer
if(cooldownBarsRemaining > 0) cooldownBarsRemaining--;
// 3. Trade Permission
bool inMonthEndLockout = (Day() >= EomDayStart) && EomActive;
bool engineGreenLight = (cooldownBarsRemaining == 0) && !inMonthEndLockout;
// 4. Entry Logic
if(engineGreenLight)
{
double fast1 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double fast2 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
double slow1 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slow2 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
bool longCondition = fast1 > slow1 && fast2 <= slow2;
bool shortCondition = fast1 < slow1 && fast2 >= slow2;
if(longCondition)
{
CloseOppositePositions(OP_SELL);
int t = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage, 0, 0, "BOJ V2", MagicNumber, 0, clrBlue);
}
else if(shortCondition)
{
CloseOppositePositions(OP_BUY);
int t = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage, 0, 0, "BOJ V2", MagicNumber, 0, clrRed);
}
}
}
//+------------------------------------------------------------------+
//| MATHEMATICAL ENGINE |
//+------------------------------------------------------------------+
void CalculateBaselines()
{
double sumTR = 0;
double trHistory[];
ArrayResize(trHistory, Lookback);
for(int i = 1; i <= Lookback; i++)
{
double tr = MathMax(High[i], Close[i+1]) - MathMin(Low[i], Close[i+1]);
trHistory[i-1] = tr;
sumTR += tr;
}
baselineMean = sumTR / Lookback;
double sumVariance = 0;
for(int i = 0; i < Lookback; i++)
{
sumVariance += MathPow(trHistory[i] - baselineMean, 2);
}
baselineStdDev = MathSqrt(sumVariance / Lookback);
}
//+------------------------------------------------------------------+
//| HELPER FUNCTIONS |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double cp = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), cp, Slippage, clrNONE);
}
}
}
void CloseOppositePositions(int type)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
{
double cp = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), cp, Slippage, clrNONE);
}
}
}
void UpdateHUD(double z, double thresh, bool isSpike, bool inCooldown, bool inMonthEnd)
{
string status = "";
if(isSpike) status = "🚨 EJECTING / TAPE BOMB";
else if(inMonthEnd) status = "⚠️ MONTH-END LOCKOUT";
else if(inCooldown) status = "⏳ COOLDOWN (" + IntegerToString(cooldownBarsRemaining) + " BARS)";
else status = "✅ SYSTEM ARMED (GREEN)";
string hud = "\n=== RISK DESK v2.0 (MT4) ===\n" +
"Live Shock: " + DoubleToStr(z, 2) + "σ / " + DoubleToStr(thresh, 2) + "σ\n" +
"State: " + status;
Comment(hud);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
MetaTrader 5 (MQL5) Version
MT5's strict array handling makes calculating the rolling True Range extremely fast. We use CopyHigh, CopyLow, and CopyClose directly in the CalculateBaselines function to build the historical Z-Score geometry without relying on sluggish indicator handles.
MT5's strict array handling makes calculating the rolling True Range extremely fast. We use CopyHigh, CopyLow, and CopyClose directly in the CalculateBaselines function to build the historical Z-Score geometry without relying on sluggish indicator handles.
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_RiskEngine_V2_MT5.mq5 |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>
// --- 1. INPUTS & RISK PARAMETERS ---
input group "1. Anomaly Detection"
input int Lookback = 100; // Rolling Window (Bars)
input double ZThreshold = 4.5; // Z-Score Trigger (Sigma)
input group "2. Stand-Aside Timer"
input int CooldownBars = 12; // Cooldown Lockout (Bars)
input group "3. Month-End Dynamics"
input int EomDayStart = 26; // Month-End Lockout Day
input bool EomActive = true; // Enable Month-End Filter
input group "4. Session Awareness (Server Time)"
input int AsianSessStart = 0; // Danger Zone Start Hour
input int AsianSessEnd = 9; // Danger Zone End Hour
input group "5. Example Strategy"
input int FastMaPeriod = 9;
input int SlowMaPeriod = 21;
input double LotSize = 0.1;
input ulong MagicNumber = 88882;
// --- STATE VARIABLES ---
CTrade trade;
datetime lastBarTime;
double baselineMean = 0;
double baselineStdDev = 0;
int cooldownBarsRemaining = 0;
int fastMaHandle;
int slowMaHandle;
double fastMaBuffer[];
double slowMaBuffer[];
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
lastBarTime = iTime(_Symbol, _Period, 0);
fastMaHandle = iMA(_Symbol, _Period, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowMaHandle = iMA(_Symbol, _Period, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
ArraySetAsSeries(fastMaBuffer, true);
ArraySetAsSeries(slowMaBuffer, true);
CalculateBaselines();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK (Microsecond Execution Loop) |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Calculate live True Range of current unclosed candle
double h[], l[], c[];
if(CopyHigh(_Symbol, _Period, 0, 1, h) <= 0) return;
if(CopyLow(_Symbol, _Period, 0, 1, l) <= 0) return;
if(CopyClose(_Symbol, _Period, 1, 1, c) <= 0) return;
double currentTR = MathMax(h[0], c[0]) - MathMin(l[0], c[0]);
// 2. Calculate live Z-Score
double currentZScore = 0;
if(baselineStdDev > 0)
currentZScore = (currentTR - baselineMean) / baselineStdDev;
// 3. Dynamic Context
MqlDateTime dt;
TimeCurrent(dt);
bool inDangerZone = (dt.hour >= AsianSessStart && dt.hour <= AsianSessEnd);
bool inMonthEndLockout = (dt.day >= EomDayStart) && EomActive;
// Hyper-sensitivity
double activeTriggerLevel = (inDangerZone && inMonthEndLockout) ? (ZThreshold * 0.8) : ZThreshold;
bool isInterventionSpike = currentZScore > activeTriggerLevel;
bool inCooldown = cooldownBarsRemaining > 0;
// 4. THE KILL-SWITCH
if(isInterventionSpike)
{
CloseAllPositions();
cooldownBarsRemaining = CooldownBars; // Drip reset timer
}
// 5. UPDATE HUD
UpdateHUD(currentZScore, activeTriggerLevel, isInterventionSpike, inCooldown, inMonthEndLockout);
// 6. BAR CLOSE LOGIC
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
OnBarClose();
}
}
//+------------------------------------------------------------------+
//| BAR CLOSE: MATH, TIMER & ENTRY LOGIC |
//+------------------------------------------------------------------+
void OnBarClose()
{
// 1. Recalculate Heavy Math for the new candle
CalculateBaselines();
// 2. Decrement Timer
if(cooldownBarsRemaining > 0) cooldownBarsRemaining--;
// 3. Trade Permission
MqlDateTime dt;
TimeCurrent(dt);
bool inMonthEndLockout = (dt.day >= EomDayStart) && EomActive;
bool engineGreenLight = (cooldownBarsRemaining == 0) && !inMonthEndLockout;
// 4. Entry Logic
if(engineGreenLight)
{
if(CopyBuffer(fastMaHandle, 0, 0, 3, fastMaBuffer) <= 0) return;
if(CopyBuffer(slowMaHandle, 0, 0, 3, slowMaBuffer) <= 0) return;
bool longCondition = fastMaBuffer[1] > slowMaBuffer[1] && fastMaBuffer[2] <= slowMaBuffer[2];
bool shortCondition = fastMaBuffer[1] < slowMaBuffer[1] && fastMaBuffer[2] >= slowMaBuffer[2];
if(longCondition)
{
CloseOppositePositions(POSITION_TYPE_SELL);
trade.Buy(LotSize, _Symbol, 0, 0, 0, "BOJ V2");
}
else if(shortCondition)
{
CloseOppositePositions(POSITION_TYPE_BUY);
trade.Sell(LotSize, _Symbol, 0, 0, 0, "BOJ V2");
}
}
}
//+------------------------------------------------------------------+
//| MATHEMATICAL ENGINE |
//+------------------------------------------------------------------+
void CalculateBaselines()
{
double h[], l[], c[];
// Copy Lookback amount of data, starting from index 1 (the last closed bar)
if(CopyHigh(_Symbol, _Period, 1, Lookback, h) <= 0) return;
if(CopyLow(_Symbol, _Period, 1, Lookback, l) <= 0) return;
// For previous close, we need to shift by 1 more, so index 2, count Lookback
if(CopyClose(_Symbol, _Period, 2, Lookback, c) <= 0) return;
double sumTR = 0;
double trHistory[];
ArrayResize(trHistory, Lookback);
for(int i = 0; i < Lookback; i++)
{
double tr = MathMax(h[i], c[i]) - MathMin(l[i], c[i]);
trHistory[i] = tr;
sumTR += tr;
}
baselineMean = sumTR / Lookback;
double sumVariance = 0;
for(int i = 0; i < Lookback; i++)
{
sumVariance += MathPow(trHistory[i] - baselineMean, 2);
}
baselineStdDev = MathSqrt(sumVariance / Lookback);
}
//+------------------------------------------------------------------+
//| HELPER FUNCTIONS |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
trade.PositionClose(ticket);
}
}
}
void CloseOppositePositions(ENUM_POSITION_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
{
trade.PositionClose(ticket);
}
}
}
void UpdateHUD(double z, double thresh, bool isSpike, bool inCooldown, bool inMonthEnd)
{
string status = "";
if(isSpike) status = "🚨 EJECTING / TAPE BOMB";
else if(inMonthEnd) status = "⚠️ MONTH-END LOCKOUT";
else if(inCooldown) status = "⏳ COOLDOWN (" + IntegerToString(cooldownBarsRemaining) + " BARS)";
else status = "✅ SYSTEM ARMED (GREEN)";
string hud = "\n=== RISK DESK v2.0 (MT5) ===\n" +
"Live Shock: " + DoubleToString(z, 2) + "σ / " + DoubleToString(thresh, 2) + "σ\n" +
"State: " + status;
Comment(hud);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Crucial Note on MetaTrader Server Time
Unlike TradingView (where you can cleanly force UTC regardless of your broker), MetaTrader relies heavily on the broker's Server Time.
Hour() in MT4 and TimeCurrent() in MT5 pull the Broker's Server Time.
Most FX brokers operate on EET (Eastern European Time), which is UTC+2 (or UTC+3 during Daylight Savings).
The Tokyo session overlap usually begins around 00:00 UTC. Therefore, if your broker is UTC+3, you will want to adjust your EA Inputs so AsianSessStart = 3 and AsianSessEnd = 12 to ensure the algorithm enters high-sensitivity mode at the correct macroeconomic hour.
Unlike TradingView (where you can cleanly force UTC regardless of your broker), MetaTrader relies heavily on the broker's Server Time.
Hour() in MT4 and TimeCurrent() in MT5 pull the Broker's Server Time.
Most FX brokers operate on EET (Eastern European Time), which is UTC+2 (or UTC+3 during Daylight Savings).
The Tokyo session overlap usually begins around 00:00 UTC. Therefore, if your broker is UTC+3, you will want to adjust your EA Inputs so AsianSessStart = 3 and AsianSessEnd = 12 to ensure the algorithm enters high-sensitivity mode at the correct macroeconomic hour.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Here is Version 3.0 (The Institutional Desk Model) built natively for both MetaTrader 4 (MQL4) and MetaTrader 5 (MQL5).
Version 3.0 implements the three institutional pillars:
Cross-Asset Verification: Pulls secondary Yen pair data (e.g., GBPJPY) in the background. It only classifies a move as a macro intervention if both pairs spike simultaneously.
Dynamic Stand-Aside & Headline Drip Reset: The timer counts down on bar closes, but instantly resets to maximum if secondary volatility breaches the drip threshold mid-cooldown.
Structural Reclaim & Dynamic Lot Sizing: Automatically shifts execution size (0.0 for lockout, 0.5 for post-timer uncompressed tape, 1.0 for normal market conditions).
MetaTrader 4 (MQL4) Version
In MT4, cross-symbol data is retrieved via iHigh(), iLow(), and iClose(). Make sure your secondary symbol (e.g., GBPJPY) is visible in your Market Watch window, or MT4 will not stream its tick data.
Version 3.0 implements the three institutional pillars:
Cross-Asset Verification: Pulls secondary Yen pair data (e.g., GBPJPY) in the background. It only classifies a move as a macro intervention if both pairs spike simultaneously.
Dynamic Stand-Aside & Headline Drip Reset: The timer counts down on bar closes, but instantly resets to maximum if secondary volatility breaches the drip threshold mid-cooldown.
Structural Reclaim & Dynamic Lot Sizing: Automatically shifts execution size (0.0 for lockout, 0.5 for post-timer uncompressed tape, 1.0 for normal market conditions).
MetaTrader 4 (MQL4) Version
In MT4, cross-symbol data is retrieved via iHigh(), iLow(), and iClose(). Make sure your secondary symbol (e.g., GBPJPY) is visible in your Market Watch window, or MT4 will not stream its tick data.
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_RiskDesk_V3_MT4.mq4 |
//+------------------------------------------------------------------+
#property strict
// --- 1. INPUTS & DESK PARAMETERS ---
input string Grp1 = "--- 1. Macro Anomaly & Cross-Asset ---";
input int Lookback = 100; // Volatility Baseline (Bars)
input double ZThreshold = 4.0; // Primary Z-Score Trigger (Sigma)
input string SecondarySymbol = "GBPJPY"; // Correlated JPY Cross
input double CrossThreshold = 2.5; // Cross-Asset Z-Score Minimum
input string Grp2 = "--- 2. Dynamic Cooldown & Reclaim ---";
input int MaxCooldownBars = 15; // Initial Stand-Aside (Bars)
input double DripThreshold = 2.0; // Drip Reset Z-Score
input string Grp3 = "--- 3. Month-End & Session ---";
input int EomDayStart = 26; // Month-End Lockout Day
input bool EomActive = true; // Enable Month-End Filter
input int AsianSessStart = 0; // BOJ Danger Zone Start (Server Hour)
input int AsianSessEnd = 9; // BOJ Danger Zone End (Server Hour)
input string Grp4 = "--- 4. Strategy & Execution ---";
input double BaseLot = 0.1; // Normal Base Lot Size
input int FastMaPeriod = 9;
input int SlowMaPeriod = 21;
input int MagicNumber = 88883;
input int Slippage = 30;
// --- STATE VARIABLES ---
datetime lastBarTime = 0;
double priMean = 0, priStdDev = 0;
double secMean = 0, secStdDev = 0;
int cooldownBarsRemaining = 0;
bool hasReclaimed = true;
double riskMultiplier = 1.0;
string regimeState = "NORMAL";
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
if(Bars < Lookback + 50) return(INIT_FAILED);
lastBarTime = Time[0];
CalculateBaselines();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK (Microsecond Cross-Asset Evaluation) |
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars < Lookback + 50) return;
// 1. Primary Live True Range & Z-Score
double priTR = MathMax(High[0], Close[1]) - MathMin(Low[0], Close[1]);
double priZScore = (priStdDev > 0) ? (priTR - priMean) / priStdDev : 0;
// 2. Secondary Live True Range & Z-Score
double secH0 = iHigh(SecondarySymbol, 0, 0);
double secL0 = iLow(SecondarySymbol, 0, 0);
double secC1 = iClose(SecondarySymbol, 0, 1);
double secTR = MathMax(secH0, secC1) - MathMin(secL0, secC1);
double secZScore = (secStdDev > 0) ? (secTR - secMean) / secStdDev : 0;
// 3. Dynamic Threshold Context
int currentHour = Hour();
bool inDangerZone = (currentHour >= AsianSessStart && currentHour <= AsianSessEnd);
bool isMonthEnd = (Day() >= EomDayStart) && EomActive;
double activePriThresh = (inDangerZone && isMonthEnd) ? (ZThreshold * 0.75) : ZThreshold;
// 4. MACRO EVENT TRIGGER (Both pairs must confirm)
bool isMacroSpike = (priZScore > activePriThresh) && (secZScore > CrossThreshold);
// 5. State Machine: Eject & Drip Reset
if(isMacroSpike)
{
CloseAllPositions();
cooldownBarsRemaining = MaxCooldownBars; // Tape Bomb: Full lockout
}
else if(cooldownBarsRemaining > 0 && priZScore > DripThreshold)
{
cooldownBarsRemaining = MaxCooldownBars; // Drip headline: Reset timer
}
// 6. Update HUD
UpdateHUD(priZScore, secZScore, isMacroSpike);
// 7. Bar Close Event Handler
if(Time[0] != lastBarTime)
{
lastBarTime = Time[0];
OnBarClose();
}
}
//+------------------------------------------------------------------+
//| BAR CLOSE: MATH, TIMER, RECLAIM & SIZING |
//+------------------------------------------------------------------+
void OnBarClose()
{
// 1. Recalculate Baselines for Primary and Cross
CalculateBaselines();
// 2. Decrement Cooldown Timer
if(cooldownBarsRemaining > 0) cooldownBarsRemaining--;
bool inCooldown = cooldownBarsRemaining > 0;
// 3. Evaluate Structural Reclaim (ATR 14 vs 50 SMA of ATR 14)
double shortAtr = iATR(Symbol(), 0, 14, 1);
double sumAtr = 0;
for(int i = 1; i <= 50; i++) sumAtr += iATR(Symbol(), 0, 14, i);
double baselineAtr = sumAtr / 50.0;
hasReclaimed = (shortAtr < baselineAtr);
bool isMonthEnd = (Day() >= EomDayStart) && EomActive;
// 4. Dynamic Risk Sizing Engine
if(isMonthEnd)
{
riskMultiplier = 0.0;
regimeState = "MONTH-END LOCK";
}
else if(inCooldown)
{
riskMultiplier = 0.0;
regimeState = "STAND-ASIDE TIMER";
}
else if(!inCooldown && !hasReclaimed)
{
riskMultiplier = 0.5; // Timer clear, but tape still expanded: Half-Size
regimeState = "RECLAIM / HALF-SIZE";
}
else
{
riskMultiplier = 1.0;
regimeState = "ARMED / FULL SIZE";
}
// 5. Strategy Execution with Dynamic Lots
if(riskMultiplier > 0)
{
double fast1 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double fast2 = iMA(Symbol(), 0, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
double slow1 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slow2 = iMA(Symbol(), 0, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
bool longCondition = fast1 > slow1 && fast2 <= slow2;
bool shortCondition = fast1 < slow1 && fast2 >= slow2;
double finalLot = NormalizeLots(BaseLot * riskMultiplier);
if(finalLot > 0)
{
if(longCondition)
{
CloseOppositePositions(OP_SELL);
int t = OrderSend(Symbol(), OP_BUY, finalLot, Ask, Slippage, 0, 0, "Desk Long", MagicNumber, 0, clrBlue);
}
else if(shortCondition)
{
CloseOppositePositions(OP_BUY);
int t = OrderSend(Symbol(), OP_SELL, finalLot, Bid, Slippage, 0, 0, "Desk Short", MagicNumber, 0, clrRed);
}
}
}
}
//+------------------------------------------------------------------+
//| MATHEMATICAL ENGINE |
//+------------------------------------------------------------------+
void CalculateBaselines()
{
double sumPriTR = 0, sumSecTR = 0;
double priHist[], secHist[];
ArrayResize(priHist, Lookback);
ArrayResize(secHist, Lookback);
for(int i = 1; i <= Lookback; i++)
{
// Primary History
double pTR = MathMax(High[i], Close[i+1]) - MathMin(Low[i], Close[i+1]);
priHist[i-1] = pTR;
sumPriTR += pTR;
// Secondary History
double sH = iHigh(SecondarySymbol, 0, i);
double sL = iLow(SecondarySymbol, 0, i);
double sC = iClose(SecondarySymbol, 0, i+1);
double sTR = MathMax(sH, sC) - MathMin(sL, sC);
secHist[i-1] = sTR;
sumSecTR += sTR;
}
priMean = sumPriTR / Lookback;
secMean = sumSecTR / Lookback;
double priVarSum = 0, secVarSum = 0;
for(int i = 0; i < Lookback; i++)
{
priVarSum += MathPow(priHist[i] - priMean, 2);
secVarSum += MathPow(secHist[i] - secMean, 2);
}
priStdDev = MathSqrt(priVarSum / Lookback);
secStdDev = MathSqrt(secVarSum / Lookback);
}
//+------------------------------------------------------------------+
//| HELPERS |
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
double step = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double normalized = MathFloor(lots / step) * step;
if(normalized < minLot) return 0.0;
if(normalized > maxLot) normalized = maxLot;
return normalized;
}
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double cp = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), cp, Slippage, clrNONE);
}
}
}
void CloseOppositePositions(int type)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
{
double cp = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), cp, Slippage, clrNONE);
}
}
}
void UpdateHUD(double pZ, double sZ, bool isSpike)
{
string timerStr = (cooldownBarsRemaining > 0) ? ("WAIT " + IntegerToString(cooldownBarsRemaining) + " BARS") : "CLEAR";
string reclaimStr = hasReclaimed ? "COMPRESSED" : "EXPANDED";
string hud = "\n=== INSTITUTIONAL RISK DESK v3.0 (MT4) ===\n" +
"Primary Z-Score : " + DoubleToStr(pZ, 2) + "σ\n" +
"Cross Z-Score : " + DoubleToStr(sZ, 2) + "σ (" + SecondarySymbol + ")\n" +
"Stand-Aside : " + timerStr + "\n" +
"Tape Structure : " + reclaimStr + "\n" +
"Trading Regime : " + regimeState + " (" + DoubleToStr(riskMultiplier, 1) + "x)\n";
Comment(hud);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
MetaTrader 5 (MQL5) Version
In MT5, the secondary symbol is subscribed automatically via SymbolSelect(), and historical arrays are pulled directly via memory-efficient CopyHigh, CopyLow, and CopyClose operations.
In MT5, the secondary symbol is subscribed automatically via SymbolSelect(), and historical arrays are pulled directly via memory-efficient CopyHigh, CopyLow, and CopyClose operations.
Code: Select all
//+------------------------------------------------------------------+
//| BOJ_RiskDesk_V3_MT5.mq5 |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>
// --- 1. INPUTS & DESK PARAMETERS ---
input group "1. Macro Anomaly & Cross-Asset"
input int Lookback = 100; // Volatility Baseline (Bars)
input double ZThreshold = 4.0; // Primary Z-Score Trigger (Sigma)
input string SecondarySymbol = "GBPJPY"; // Correlated JPY Cross
input double CrossThreshold = 2.5; // Cross-Asset Z-Score Minimum
input group "2. Dynamic Cooldown & Reclaim"
input int MaxCooldownBars = 15; // Initial Stand-Aside (Bars)
input double DripThreshold = 2.0; // Drip Reset Z-Score
input group "3. Month-End & Session"
input int EomDayStart = 26; // Month-End Lockout Day
input bool EomActive = true; // Enable Month-End Filter
input int AsianSessStart = 0; // BOJ Danger Zone Start (Server Hour)
input int AsianSessEnd = 9; // BOJ Danger Zone End (Server Hour)
input group "4. Strategy & Execution"
input double BaseLot = 0.1; // Base Lot Size
input int FastMaPeriod = 9;
input int SlowMaPeriod = 21;
input ulong MagicNumber = 88883;
// --- STATE VARIABLES ---
CTrade trade;
datetime lastBarTime;
double priMean = 0, priStdDev = 0;
double secMean = 0, secStdDev = 0;
int cooldownBarsRemaining = 0;
bool hasReclaimed = true;
double riskMultiplier = 1.0;
string regimeState = "NORMAL";
// Indicator Handles & Buffers
int atrHandle;
int fastMaHandle;
int slowMaHandle;
double atrBuffer[];
double fastMaBuffer[];
double slowMaBuffer[];
//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
lastBarTime = iTime(_Symbol, _Period, 0);
// Ensure the secondary cross symbol is active in Market Watch
if(!SymbolSelect(SecondarySymbol, true))
{
Print("Failed to select secondary symbol: ", SecondarySymbol);
return(INIT_FAILED);
}
atrHandle = iATR(_Symbol, _Period, 14);
fastMaHandle = iMA(_Symbol, _Period, FastMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowMaHandle = iMA(_Symbol, _Period, SlowMaPeriod, 0, MODE_EMA, PRICE_CLOSE);
ArraySetAsSeries(atrBuffer, true);
ArraySetAsSeries(fastMaBuffer, true);
ArraySetAsSeries(slowMaBuffer, true);
CalculateBaselines();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EXPERT TICK |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Primary Live True Range
double pH[], pL[], pC[];
if(CopyHigh(_Symbol, _Period, 0, 1, pH) <= 0 || CopyLow(_Symbol, _Period, 0, 1, pL) <= 0 || CopyClose(_Symbol, _Period, 1, 1, pC) <= 0) return;
double priTR = MathMax(pH[0], pC[0]) - MathMin(pL[0], pC[0]);
double priZScore = (priStdDev > 0) ? (priTR - priMean) / priStdDev : 0;
// 2. Secondary Live True Range
double sH[], sL[], sC[];
if(CopyHigh(SecondarySymbol, _Period, 0, 1, sH) <= 0 || CopyLow(SecondarySymbol, _Period, 0, 1, sL) <= 0 || CopyClose(SecondarySymbol, _Period, 1, 1, sC) <= 0) return;
double secTR = MathMax(sH[0], sC[0]) - MathMin(sL[0], sC[0]);
double secZScore = (secStdDev > 0) ? (secTR - secMean) / secStdDev : 0;
// 3. Dynamic Thresholds
MqlDateTime dt;
TimeCurrent(dt);
bool inDangerZone = (dt.hour >= AsianSessStart && dt.hour <= AsianSessEnd);
bool isMonthEnd = (dt.day >= EomDayStart) && EomActive;
double activePriThresh = (inDangerZone && isMonthEnd) ? (ZThreshold * 0.75) : ZThreshold;
// 4. MACRO EVENT TRIGGER
bool isMacroSpike = (priZScore > activePriThresh) && (secZScore > CrossThreshold);
// 5. State Machine: Eject & Drip Reset
if(isMacroSpike)
{
CloseAllPositions();
cooldownBarsRemaining = MaxCooldownBars;
}
else if(cooldownBarsRemaining > 0 && priZScore > DripThreshold)
{
cooldownBarsRemaining = MaxCooldownBars;
}
// 6. Update HUD
UpdateHUD(priZScore, secZScore, isMacroSpike);
// 7. Bar Close Event Handler
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
OnBarClose();
}
}
//+------------------------------------------------------------------+
//| BAR CLOSE: MATH, TIMER, RECLAIM & SIZING |
//+------------------------------------------------------------------+
void OnBarClose()
{
// 1. Recalculate Baselines
CalculateBaselines();
// 2. Decrement Timer
if(cooldownBarsRemaining > 0) cooldownBarsRemaining--;
bool inCooldown = cooldownBarsRemaining > 0;
// 3. Structural Reclaim Check (ATR 14 vs 50 SMA of ATR 14)
if(CopyBuffer(atrHandle, 0, 1, 50, atrBuffer) >= 50)
{
double shortAtr = atrBuffer[0];
double sumAtr = 0;
for(int i = 0; i < 50; i++) sumAtr += atrBuffer[i];
double baselineAtr = sumAtr / 50.0;
hasReclaimed = (shortAtr < baselineAtr);
}
MqlDateTime dt;
TimeCurrent(dt);
bool isMonthEnd = (dt.day >= EomDayStart) && EomActive;
// 4. Dynamic Risk Sizing Engine
if(isMonthEnd)
{
riskMultiplier = 0.0;
regimeState = "MONTH-END LOCK";
}
else if(inCooldown)
{
riskMultiplier = 0.0;
regimeState = "STAND-ASIDE TIMER";
}
else if(!inCooldown && !hasReclaimed)
{
riskMultiplier = 0.5; // Post-timer, elevated volatility: Half-Size
regimeState = "RECLAIM / HALF-SIZE";
}
else
{
riskMultiplier = 1.0;
regimeState = "ARMED / FULL SIZE";
}
// 5. Strategy Execution with Dynamic Lots
if(riskMultiplier > 0)
{
if(CopyBuffer(fastMaHandle, 0, 0, 3, fastMaBuffer) <= 0) return;
if(CopyBuffer(slowMaHandle, 0, 0, 3, slowMaBuffer) <= 0) return;
bool longCondition = fastMaBuffer[1] > slowMaBuffer[1] && fastMaBuffer[2] <= slowMaBuffer[2];
bool shortCondition = fastMaBuffer[1] < slowMaBuffer[1] && fastMaBuffer[2] >= slowMaBuffer[2];
double finalLot = NormalizeLots(BaseLot * riskMultiplier);
if(finalLot > 0)
{
if(longCondition)
{
CloseOppositePositions(POSITION_TYPE_SELL);
trade.Buy(finalLot, _Symbol, 0, 0, 0, "Desk Long");
}
else if(shortCondition)
{
CloseOppositePositions(POSITION_TYPE_BUY);
trade.Sell(finalLot, _Symbol, 0, 0, 0, "Desk Short");
}
}
}
}
//+------------------------------------------------------------------+
//| MATHEMATICAL ENGINE |
//+------------------------------------------------------------------+
void CalculateBaselines()
{
double pH[], pL[], pC[];
double sH[], sL[], sC[];
// Copy Lookback bars starting from shift 1 (closed bars)
if(CopyHigh(_Symbol, _Period, 1, Lookback, pH) <= 0 || CopyLow(_Symbol, _Period, 1, Lookback, pL) <= 0 || CopyClose(_Symbol, _Period, 2, Lookback, pC) <= 0) return;
if(CopyHigh(SecondarySymbol, _Period, 1, Lookback, sH) <= 0 || CopyLow(SecondarySymbol, _Period, 1, Lookback, sL) <= 0 || CopyClose(SecondarySymbol, _Period, 2, Lookback, sC) <= 0) return;
double sumPriTR = 0, sumSecTR = 0;
double priHist[], secHist[];
ArrayResize(priHist, Lookback);
ArrayResize(secHist, Lookback);
for(int i = 0; i < Lookback; i++)
{
double pTR = MathMax(pH[i], pC[i]) - MathMin(pL[i], pC[i]);
priHist[i] = pTR;
sumPriTR += pTR;
double sTR = MathMax(sH[i], sC[i]) - MathMin(sL[i], sC[i]);
secHist[i] = sTR;
sumSecTR += sTR;
}
priMean = sumPriTR / Lookback;
secMean = sumSecTR / Lookback;
double priVarSum = 0, secVarSum = 0;
for(int i = 0; i < Lookback; i++)
{
priVarSum += MathPow(priHist[i] - priMean, 2);
secVarSum += MathPow(secHist[i] - secMean, 2);
}
priStdDev = MathSqrt(priVarSum / Lookback);
secStdDev = MathSqrt(secVarSum / Lookback);
}
//+------------------------------------------------------------------+
//| HELPERS |
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double normalized = MathFloor(lots / step) * step;
if(normalized < minLot) return 0.0;
if(normalized > maxLot) normalized = maxLot;
return normalized;
}
void CloseAllPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
trade.PositionClose(ticket);
}
}
}
void CloseOppositePositions(ENUM_POSITION_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
{
trade.PositionClose(ticket);
}
}
}
void UpdateHUD(double pZ, double sZ, bool isSpike)
{
string timerStr = (cooldownBarsRemaining > 0) ? ("WAIT " + IntegerToString(cooldownBarsRemaining) + " BARS") : "CLEAR";
string reclaimStr = hasReclaimed ? "COMPRESSED" : "EXPANDED";
string hud = "\n=== INSTITUTIONAL RISK DESK v3.0 (MT5) ===\n" +
"Primary Z-Score : " + DoubleToString(pZ, 2) + "σ\n" +
"Cross Z-Score : " + DoubleToString(sZ, 2) + "σ (" + SecondarySymbol + ")\n" +
"Stand-Aside : " + timerStr + "\n" +
"Tape Structure : " + reclaimStr + "\n" +
"Trading Regime : " + regimeState + " (" + DoubleToString(riskMultiplier, 1) + "x)\n";
Comment(hud);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer
Crucial Deployment Directives:
Broker Suffix Match: If your broker uses symbol suffixes (such as USDJPY.pro, USDJPYm, or USDJPY.raw), make sure the input SecondarySymbol exactly matches that naming convention (e.g., set it to GBPJPY.pro).
Market Watch Visibility: In MT4, background historical functions (iHigh, iLow) return 0.0 if the secondary pair is not present in your Market Watch window. Open your Market Watch (Ctrl + M) and ensure GBPJPY is visible.
Volume Rounding Mechanics: Notice the NormalizeLots() helper function. It uses MathFloor divided by LOTSTEP. If your BaseLot is 0.05 and half-size mode triggers (0.025), a simple cast could cause broker rejection on accounts that only accept 0.01 steps. NormalizeLots rounds down to the broker's minimum valid increment to ensure reliable execution.
Broker Suffix Match: If your broker uses symbol suffixes (such as USDJPY.pro, USDJPYm, or USDJPY.raw), make sure the input SecondarySymbol exactly matches that naming convention (e.g., set it to GBPJPY.pro).
Market Watch Visibility: In MT4, background historical functions (iHigh, iLow) return 0.0 if the secondary pair is not present in your Market Watch window. Open your Market Watch (Ctrl + M) and ensure GBPJPY is visible.
Volume Rounding Mechanics: Notice the NormalizeLots() helper function. It uses MathFloor divided by LOTSTEP. If your BaseLot is 0.05 and half-size mode triggers (0.025), a simple cast could cause broker rejection on accounts that only accept 0.01 steps. NormalizeLots rounds down to the broker's minimum valid increment to ensure reliable execution.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.