Code: Select all
//@version=5
strategy("BOJ Risk Engine v3.0 - Institutional", overlay=true, calc_on_every_tick=true, initial_capital=100000)
// =========================================================================
// 1. INPUTS & DESK PARAMETERS
// =========================================================================
grp_anomaly = "1. Macro Anomaly & Cross-Asset"
lookback = input.int(100, title="Volatility Baseline (Bars)", group=grp_anomaly)
zThreshold = input.float(4.0, title="Primary Z-Score Trigger", group=grp_anomaly)
symCross = input.symbol("OANDA:GBPJPY", title="Correlated JPY Cross", group=grp_anomaly, tooltip="Monitored in background. If both pairs spike, it's a confirmed BOJ event.")
crossThresh = input.float(2.5, title="Cross-Asset Z-Score Minimum", group=grp_anomaly)
grp_timer = "2. Dynamic Cooldown & Reclaim"
maxCooldown = input.int(15, title="Initial Stand-Aside (Bars)", group=grp_timer)
dripThresh = input.float(2.0, title="Drip Reset Z-Score", group=grp_timer, tooltip="If Z-score hits this during cooldown, timer resets (Headline Drip defense).")
grp_eom = "3. Month-End & Session Tactics"
eomDayStart = input.int(26, title="Month-End Lockout Day", group=grp_eom, minval=1, maxval=31)
eomActive = input.bool(true, title="Enable Month-End Filter", group=grp_eom)
asianSess = input.session("0000-0900", title="BOJ Danger Zone (UTC)", group=grp_eom)
// =========================================================================
// 2. PRIMARY SYMBOL VOLATILITY (Z-SCORE)
// =========================================================================
trPrimary = ta.tr
meanPrimary = ta.sma(trPrimary, lookback)
stdDevPrimary = ta.stdev(trPrimary, lookback)
zPrimary = stdDevPrimary > 0 ? (trPrimary - meanPrimary) / stdDevPrimary : 0
// =========================================================================
// 3. CROSS-ASSET CORRELATION ENGINE
// =========================================================================
// Fetch the True Range of the secondary JPY cross in the background
trCross = request.security(symCross, timeframe.period, ta.tr)
meanCross = request.security(symCross, timeframe.period, ta.sma(ta.tr, lookback))
stdDevCross = request.security(symCross, timeframe.period, ta.stdev(ta.tr, lookback))
zCross = stdDevCross > 0 ? (trCross - meanCross) / stdDevCross : 0
// =========================================================================
// 4. THRESHOLD LOGIC & CONTEXT
// =========================================================================
inDangerZone = time(timeframe.period, asianSess, "UTC")
isMonthEnd = (dayofmonth >= eomDayStart) and eomActive
// Hyper-sensitivity during high-risk windows
activeZThresh = (inDangerZone and isMonthEnd) ? (zThreshold * 0.75) : zThreshold
// MACRO EVENT TRIGGER: Main symbol spikes AND secondary symbol confirms it.
isMacroSpike = (zPrimary > activeZThresh) and (zCross > crossThresh)
// =========================================================================
// 5. STATE MACHINE: DRIP-RESETS & RECLAIM STRUCTURE
// =========================================================================
var int cooldownCounter = 0
if isMacroSpike
cooldownCounter := maxCooldown // Initial Tape Bomb hits: Max Lockout
else if cooldownCounter > 0 and (zPrimary > dripThresh)
cooldownCounter := maxCooldown // Drip Headline hits: RESET timer to max
else if cooldownCounter > 0
cooldownCounter -= 1 // Tape is quiet: Count down
inCooldown = cooldownCounter > 0
// STRUCTURAL RECLAIM: Is the current ATR back below the 50-period average ATR?
shortAtr = ta.atr(14)
baselineAtr = ta.sma(shortAtr, 50)
hasReclaimed = shortAtr < baselineAtr
// =========================================================================
// 6. DYNAMIC SIZING ENGINE (Outputs to your strategy)
// =========================================================================
float riskMultiplier = 1.0
string regimeState = "NORMAL"
if isMonthEnd
riskMultiplier := 0.0
regimeState := "MONTH-END LOCK"
else if inCooldown or isMacroSpike
riskMultiplier := 0.0
regimeState := "STAND-ASIDE TIMER"
else if not inCooldown and not hasReclaimed
// Timer is done, but tape hasn't structurally compressed yet. Reduced size.
riskMultiplier := 0.5
regimeState := "RECLAIM / HALF-SIZE"
else
riskMultiplier := 1.0
regimeState := "ARMED / FULL SIZE"
// =========================================================================
// 7. EXECUTION & KILL-SWITCH
// =========================================================================
if isMacroSpike
strategy.close_all(comment="🚨 MACRO SHOCK: Z1=" + str.tostring(math.round(zPrimary,1)) + " Z2=" + str.tostring(math.round(zCross,1)))
alert("CLOSE_ALL", alert.freq_once_per_bar_close)
// Visuals
bgcolor(isMacroSpike ? color.new(color.red, 70) : na, title="Macro Spike")
bgcolor(inCooldown and not isMacroSpike ? color.new(color.orange, 85) : na, title="Cooldown/Drip")
bgcolor(riskMultiplier == 0.5 and not inCooldown ? color.new(color.blue, 90) : na, title="Reclaim Structure")
bgcolor(isMonthEnd and riskMultiplier == 0 ? color.new(color.yellow, 90) : na, title="Month End")
// =========================================================================
// 8. INSTITUTIONAL HUD (HEADS UP DISPLAY)
// =========================================================================
var table hud = table.new(position.top_right, 2, 6, border_width=1, border_color=color.gray)
if barstate.islast
table.cell(hud, 0, 0, "RISK DESK v3.0", text_color=color.white, bgcolor=color.black)
table.cell(hud, 1, 0, "METRICS", text_color=color.white, bgcolor=color.black)
// Z-Scores
table.cell(hud, 0, 1, "Primary Z-Score", text_color=color.white, bgcolor=color.gray)
table.cell(hud, 1, 1, str.tostring(math.round(zPrimary, 2)) + "σ", text_color=color.white, bgcolor=zPrimary > dripThresh ? color.orange : color.gray)
table.cell(hud, 0, 2, "Cross-Asset Z-Score", text_color=color.white, bgcolor=color.gray)
table.cell(hud, 1, 2, str.tostring(math.round(zCross, 2)) + "σ", text_color=color.white, bgcolor=zCross > crossThresh ? color.orange : color.gray)
// Timer Status
table.cell(hud, 0, 3, "Stand-Aside Timer", text_color=color.white, bgcolor=color.gray)
table.cell(hud, 1, 3, inCooldown ? "WAIT " + str.tostring(cooldownCounter) + " BARS" : "CLEAR", text_color=color.white, bgcolor=inCooldown ? color.red : color.green)
// Volatility Structure
table.cell(hud, 0, 4, "Tape Structure", text_color=color.white, bgcolor=color.gray)
table.cell(hud, 1, 4, hasReclaimed ? "COMPRESSED" : "EXPANDED", text_color=color.white, bgcolor=hasReclaimed ? color.green : color.orange)
// Sizing Output
color riskColor = riskMultiplier == 0 ? color.red : (riskMultiplier == 0.5 ? color.blue : color.green)
table.cell(hud, 0, 5, "Trade Regime", text_color=color.white, bgcolor=color.gray)
table.cell(hud, 1, 5, regimeState, text_color=color.white, bgcolor=riskColor)
// =========================================================================
// 9. EXAMPLE ENTRY LOGIC (Applying the Risk Multiplier)
// =========================================================================
// Standard moving average cross just for demonstration
emaFast = ta.ema(close, 9)
emaSlow = ta.ema(close, 21)
longCondition = ta.crossover(emaFast, emaSlow)
shortCondition = ta.crossunder(emaFast, emaSlow)
// Base position size is 2% of equity. We multiply this by the Risk Multiplier.
baseRiskPercentage = 2.0
adjustedRisk = baseRiskPercentage * riskMultiplier
// ONLY execute if riskMultiplier is greater than 0
if longCondition and riskMultiplier > 0
strategy.entry("Long", strategy.long, qty=(strategy.equity * (adjustedRisk/100)) / close)
if shortCondition and riskMultiplier > 0
strategy.entry("Short", strategy.short, qty=(strategy.equity * (adjustedRisk/100)) / close)