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