Page 3 of 4

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:08 pm
by PTScalper
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).

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:08 pm
by PTScalper
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.

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);
            }
        }
    }
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:08 pm
by PTScalper
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.

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);
        }
    }
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:09 pm
by PTScalper
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.

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:10 pm
by PTScalper
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

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);
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:10 pm
by PTScalper
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.

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);
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:11 pm
by PTScalper
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.

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:12 pm
by PTScalper
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.

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);
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:13 pm
by PTScalper
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.

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);
}

Re: Seconds after BOJ headlines on USDJPY: my stand-aside timer

Posted: Sun Sep 27, 2026 12:13 pm
by PTScalper
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.