Advertisement IC Markets

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

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Why this is a Pro Setup:

It calculates Sigma (σ): By standardizing the tape movement via a Z-score, you don't need to adjust the multiplier for different timeframes. A 5-sigma event on a 1-minute chart is an anomaly. A 5-sigma event on an hourly chart is an anomaly. The math adapts to the volatility of the timeframe you place it on automatically.

The "Double Whammy" Sensitivity: Look at the triggerLevel variable. If it is the end of the month and you are in the Tokyo/London overlap session, the algorithm drops the required Z-score threshold by 20%. It actively "tightens the leash" when MoF intervention risk is historically at its highest.

The Cooldown Tracker: The HUD actively counts down the bars. If you set a 12-bar cooldown on a 5-minute chart, the table in the top right will literally say COOLDOWN (12), COOLDOWN (11), blocking all market access until the tape has spent an hour proving it has built the "reclaim structure" you mentioned.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

To reach Version 3.0, we must move past simply detecting a spike and start mimicking how an institutional trading desk actually operates.

In your first message, you laid out three advanced concepts that Version 2.0 didn't fully capture:

1.) "If headlines keep dripping, the timer resets."

2.) "When a pullback or reclaim structure appears, I may engage with reduced size."

3.) "Cross-asset confluence to distinguish a headline spike from a lasting leg."

Version 3.0 (The Institutional Desk Model) builds these exact mechanics directly into the algorithm.

The Major V3 Upgrades:

Cross-Asset Verification: V3 uses request.security to monitor a second Yen pair in the background (e.g., GBPJPY while you trade USDJPY). If your chart spikes but the other pair doesn't, it's just USD news. If both spike simultaneously, the engine confirms a BOJ macro-event and slams the kill-switch.

The "Drip Headline" Dynamic Reset: The cooldown timer is no longer static. If the algorithm detects elevated, secondary volatility during the cooldown period (the tape is still "dripping" news), the timer instantly resets back to maximum.

Structural Reclaim & Dynamic Sizing: Instead of a simple "On/Off" switch, V3 outputs a Risk Multiplier (1.0 for normal, 0.5 for reduced size, 0.0 for lockout). It only gives you the 1.0 (Full Size) green light when the timer expires and structural volatility has officially compressed back below its baseline.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Here is the V3 code.

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)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

How to interface with V3:

The riskMultiplier API: This is the most powerful addition. At the bottom of the script, notice how your position sizing is dynamically linked to the riskMultiplier variable.

If the timer is active, riskMultiplier is 0.0. No trades allowed.

If the timer expires, but the market ATR is still wildly above average, it assigns 0.5. Your strategy will automatically take the signal, but it will seamlessly calculate half-size.

When the market truly consolidates back to a normal structure (hasReclaimed is true), the multiplier becomes 1.0, restoring your normal clip size.

Cross-Asset Defense: By default, it watches OANDA:GBPJPY. If you trade USDJPY, and US CPI data prints, USDJPY will spike violently. However, GBPJPY likely won't care as much. Because the cross-asset secondary threshold won't trigger, the script knows it's a USD event, not a BOJ intervention, keeping your system calibrated to the correct macroeconomic cause.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Here is the direct translation of Version 1.0 from Pine Script to a C# cBot for cTrader.

Translating this to cTrader actually gives you a massive execution advantage over TradingView: True event separation. In Pine Script, combining tick-by-tick kill-switches with indicator crosses can lead to repainting errors. In cTrader, we can put the protective Kill-Switch inside the OnTick() method so it fires instantly in milliseconds, while safely locking the actual trading logic inside the OnBar() method so it only enters on confirmed structural closes.

Here is the complete cBot source code:

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class BOJMonthEndProtectorV1 : Robot
    {
        // =========================================================================
        // 1. INPUTS & SETTINGS
        // =========================================================================
        [Parameter("ATR Baseline Length", Group = "Intervention Protection", DefaultValue = 14)]
        public int AtrLength { get; set; }

        [Parameter("Spike Multiplier (ATR)", Group = "Intervention Protection", DefaultValue = 5.0, 
            DefaultValue = "Ejects all positions if a single candle's range exceeds this multiple of normal ATR.")]
        public double SpikeMultiplier { get; set; }

        [Parameter("Month-End Start Day", Group = "Intervention Protection", DefaultValue = 27, MinValue = 1, MaxValue = 31, 
            DefaultValue = "Blocks new entries on or after this day of the month.")]
        public int EomDayStart { get; set; }

        [Parameter("Fast EMA", Group = "Example Trading Logic", DefaultValue = 10)]
        public int FastMaPeriod { get; set; }

        [Parameter("Slow EMA", Group = "Example Trading Logic", DefaultValue = 20)]
        public int SlowMaPeriod { get; set; }
        
        [Parameter("Trade Volume (Units)", Group = "Example Trading Logic", DefaultValue = 1000)]
        public double TradeVolume { get; set; }

        // Core Indicators
        private AverageTrueRange _atr;
        private ExponentialMovingAverage _fastEma;
        private ExponentialMovingAverage _slowEma;
        
        private string _botLabel = "BOJ_Protector_V1";

        protected override void OnStart()
        {
            // Initialize indicators
            _atr = Indicators.AverageTrueRange(Bars, AtrLength, MovingAverageType.Simple);
            _fastEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastMaPeriod);
            _slowEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowMaPeriod);
        }

        // =========================================================================
        // 2. DEFENSE LOGIC & KILL-SWITCH (Evaluated every single tick)
        // =========================================================================
        protected override void OnTick()
        {
            // Measure the current candle's total excursion in real-time
            double currentRange = Bars.HighPrices.Last(0) - Bars.LowPrices.Last(0);
            
            // CRITICAL: We use Last(1) for the ATR so the current exploding candle 
            // doesn't instantly skew our baseline measurement.
            double baselineAtr = _atr.Result.Last(1);

            // Triggers
            bool isInterventionSpike = currentRange > (baselineAtr * SpikeMultiplier);
            bool isMonthEndDanger = Server.Time.Day >= EomDayStart;

            // KILL SWITCH EXECUTION
            if (isInterventionSpike)
            {
                // Find all open positions for THIS symbol opened by THIS bot
                var openPositions = Positions.FindAll(_botLabel, SymbolName);
                
                foreach (var position in openPositions)
                {
                    ClosePosition(position);
                }
                Print("🚨 BOJ SPIKE DETECTED: EJECTING POSITIONS 🚨");
            }

            // HUD / On-Chart Visuals
            if (isInterventionSpike)
                Chart.DrawStaticText("status_hud", "🚨 SPIKE DETECTED - EJECTING ALL", VerticalAlignment.Top, HorizontalAlignment.Right, Color.Red);
            else if (isMonthEndDanger)
                Chart.DrawStaticText("status_hud", "⚠️ MONTH-END LOCKOUT ACTIVE", VerticalAlignment.Top, HorizontalAlignment.Right, Color.Yellow);
            else
                Chart.DrawStaticText("status_hud", "✅ ENGINE GREEN / ARMED", VerticalAlignment.Top, HorizontalAlignment.Right, Color.LimeGreen);
        }

        // =========================================================================
        // 3. EXAMPLE ENTRY LOGIC (Evaluated on Bar Close for stability)
        // =========================================================================
        protected override void OnBar()
        {
            // Recalculate states for the bar close
            double currentRange = Bars.HighPrices.Last(0) - Bars.LowPrices.Last(0);
            double baselineAtr = _atr.Result.Last(1);
            
            bool isInterventionSpike = currentRange > (baselineAtr * SpikeMultiplier);
            bool isMonthEndDanger = Server.Time.Day >= EomDayStart;

            // Global permission switch
            bool canTrade = !isMonthEndDanger && !isInterventionSpike;

            if (canTrade)
            {
                // Simple Moving Average Crossover Logic
                // We use Last(1) and Last(2) to ensure the cross actually completed on the closed bar
                bool longCondition = _fastEma.Result.Last(1) > _slowEma.Result.Last(1) && 
                                     _fastEma.Result.Last(2) <= _slowEma.Result.Last(2);
                                     
                bool shortCondition = _fastEma.Result.Last(1) < _slowEma.Result.Last(1) && 
                                      _fastEma.Result.Last(2) >= _slowEma.Result.Last(2);

                if (longCondition)
                {
                    ClosePositions(TradeType.Sell); // Close opposite trades
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, TradeVolume, _botLabel, null, null);
                }
                else if (shortCondition)
                {
                    ClosePositions(TradeType.Buy); // Close opposite trades
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, TradeVolume, _botLabel, null, null);
                }
            }
        }

        // Helper method to close opposite positions before opening a new one
        private void ClosePositions(TradeType tradeType)
        {
            var positionsToClose = Positions.FindAll(_botLabel, SymbolName).Where(p => p.TradeType == tradeType);
            foreach (var position in positionsToClose)
            {
                ClosePosition(position);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Key Differences Between Pine Script and cTrader Architectures:

The .Last(1) Advantage: When a tape bomb hits, the current candle's ATR spikes. In standard scripts, this actually dilutes your kill-switch because the baseline average increases mid-candle. In this cBot, baselineAtr = _atr.Result.Last(1); explicitly measures against the previous closed bar's ATR, ensuring your multiplier logic remains mathematically sound while the current bar is exploding.

Symbol & Label Isolation: If you trade USDJPY and EURUSD on the same account, a BOJ intervention should only close your Yen pairs. The line Positions.FindAll(_botLabel, SymbolName) strictly limits the panic-eject sequence to trades opened by this specific bot on this specific chart, preserving your uncorrelated positions.

The Chart HUD: cTrader doesn't handle background shading quite as gracefully as TradingView. Instead, I built a static HUD (Chart.DrawStaticText) in the top right corner. It updates every tick, immediately snapping from a green ARMED status to a yellow LOCKOUT status when the server time crosses the 27th of the month.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

This is where cTrader’s C# architecture truly outshines Pine Script.

In TradingView, executing complex standard deviation math tick-by-tick can cause severe lag or repainting issues. In cTrader, we can structurally separate the math: we calculate the Mean and Standard Deviation baseline once per closed bar, and then we only calculate the live Z-Score tick-by-tick against that static baseline. This means your Kill-Switch evaluates in microseconds with zero lag.

Here is Version 2.0 (The Institutional Risk Engine) translated to a cBot.

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class BOJRiskEngineV2 : Robot
    {
        // =========================================================================
        // 1. INPUTS & RISK PARAMETERS
        // =========================================================================
        [Parameter("Rolling Window (Bars)", Group = "1. Anomaly Detection", DefaultValue = 100)]
        public int Lookback { get; set; }

        [Parameter("Z-Score Trigger (Sigma)", Group = "1. Anomaly Detection", DefaultValue = 4.5, 
            DefaultValue = "Fires if a candle's range is this many standard deviations above the mean.")]
        public double ZThreshold { get; set; }

        [Parameter("Cooldown Lockout (Bars)", Group = "2. The Stand-Aside Timer", DefaultValue = 12)]
        public int CooldownBars { get; set; }

        [Parameter("Month-End Start Day", Group = "3. Month-End Dynamics", DefaultValue = 26, MinValue = 1, MaxValue = 31)]
        public int EomDayStart { get; set; }

        [Parameter("Enable Month-End Filter", Group = "3. Month-End Dynamics", DefaultValue = true)]
        public bool EomActive { get; set; }

        [Parameter("Asian Session Start (UTC Hour)", Group = "4. Session Awareness", DefaultValue = 0)]
        public int AsianSessStart { get; set; }

        [Parameter("Asian Session End (UTC Hour)", Group = "4. Session Awareness", DefaultValue = 9)]
        public int AsianSessEnd { get; set; }

        [Parameter("Fast EMA", Group = "Example Strategy", DefaultValue = 9)]
        public int FastMaPeriod { get; set; }

        [Parameter("Slow EMA", Group = "Example Strategy", DefaultValue = 21)]
        public int SlowMaPeriod { get; set; }
        
        [Parameter("Trade Volume (Units)", Group = "Example Strategy", DefaultValue = 1000)]
        public double TradeVolume { get; set; }

        // =========================================================================
        // STATE VARIABLES
        // =========================================================================
        private double _baselineMean;
        private double _baselineStdDev;
        private int _cooldownBarsRemaining = 0;
        private string _botLabel = "BOJ_RiskEngine_V2";

        private ExponentialMovingAverage _fastEma;
        private ExponentialMovingAverage _slowEma;

        protected override void OnStart()
        {
            _fastEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastMaPeriod);
            _slowEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowMaPeriod);
            
            // Calculate initial baseline on startup
            CalculateBaselineMath();
        }

        // =========================================================================
        // 2. TICK-BY-TICK EXECUTION & KILL-SWITCH
        // =========================================================================
        protected override void OnTick()
        {
            if (Bars.Count < Lookback + 2) return; // Wait for enough data

            // 1. Calculate live True Range of the current unclosed candle
            double currentHigh = Bars.HighPrices.Last(0);
            double currentLow = Bars.LowPrices.Last(0);
            double prevClose = Bars.ClosePrices.Last(1);
            
            double currentTR = Math.Max(currentHigh, prevClose) - Math.Min(currentLow, prevClose);

            // 2. Calculate live Z-Score against the static baseline
            double currentZScore = _baselineStdDev > 0 ? (currentTR - _baselineMean) / _baselineStdDev : 0;

            // 3. Contextual Awareness & Dynamic Thresholds
            TimeSpan timeOfDay = Server.TimeInUtc.TimeOfDay;
            bool inDangerZone = timeOfDay >= TimeSpan.FromHours(AsianSessStart) && timeOfDay <= TimeSpan.FromHours(AsianSessEnd);
            bool inMonthEndLockout = Server.TimeInUtc.Day >= EomDayStart && EomActive;

            // Hyper-sensitivity: Lower the Z-Score required to trigger if in Danger Zone AND Month End
            double activeTriggerLevel = (inDangerZone && inMonthEndLockout) ? (ZThreshold * 0.8) : ZThreshold;

            bool isInterventionSpike = currentZScore > activeTriggerLevel;
            bool inCooldown = _cooldownBarsRemaining > 0;

            // 4. EJECT MECHANISM
            if (isInterventionSpike)
            {
                CloseAllPositions();
                _cooldownBarsRemaining = CooldownBars; // Reset timer to max
            }

            // 5. UPDATE HUD (Heads-Up Display)
            UpdateHUD(currentZScore, activeTriggerLevel, isInterventionSpike, inCooldown, inMonthEndLockout);
        }

        // =========================================================================
        // 3. BAR CLOSE LOGIC (Timer Countdown & Entry Logic)
        // =========================================================================
        protected override void OnBar()
        {
            if (Bars.Count < Lookback + 2) return;

            // 1. Recalculate the baseline math for the next candle's ticks
            CalculateBaselineMath();

            // 2. State Machine: Countdown the stand-aside timer
            if (_cooldownBarsRemaining > 0)
            {
                _cooldownBarsRemaining--;
            }

            // 3. GLOBAL TRADE PERMISSION
            bool inMonthEndLockout = Server.TimeInUtc.Day >= EomDayStart && EomActive;
            bool engineGreenLight = _cooldownBarsRemaining == 0 && !inMonthEndLockout;

            // 4. EXAMPLE ENTRY LOGIC
            if (engineGreenLight)
            {
                bool longCondition = _fastEma.Result.Last(1) > _slowEma.Result.Last(1) && 
                                     _fastEma.Result.Last(2) <= _slowEma.Result.Last(2);
                                     
                bool shortCondition = _fastEma.Result.Last(1) < _slowEma.Result.Last(1) && 
                                      _fastEma.Result.Last(2) >= _slowEma.Result.Last(2);

                if (longCondition)
                {
                    ClosePositions(TradeType.Sell);
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, TradeVolume, _botLabel);
                }
                else if (shortCondition)
                {
                    ClosePositions(TradeType.Buy);
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, TradeVolume, _botLabel);
                }
            }
        }

        // =========================================================================
        // 4. MATHEMATICAL ENGINE & HELPERS
        // =========================================================================
        private void CalculateBaselineMath()
        {
            // Calculates the Mean and Standard Deviation of the True Range 
            // over the last N closed bars.
            double sumTR = 0;
            double[] trHistory = new double[Lookback];

            for (int i = 1; i <= Lookback; i++)
            {
                // i = 1 is the most recently closed bar
                double high = Bars.HighPrices.Last(i);
                double low = Bars.LowPrices.Last(i);
                double prevC = Bars.ClosePrices.Last(i + 1);

                double tr = Math.Max(high, prevC) - Math.Min(low, prevC);
                trHistory[i - 1] = tr;
                sumTR += tr;
            }

            _baselineMean = sumTR / Lookback;

            double sumVariance = 0;
            for (int i = 0; i < Lookback; i++)
            {
                sumVariance += Math.Pow(trHistory[i] - _baselineMean, 2);
            }

            _baselineStdDev = Math.Sqrt(sumVariance / Lookback);
        }

        private void CloseAllPositions()
        {
            var positionsToClose = Positions.FindAll(_botLabel, SymbolName);
            if (positionsToClose.Length > 0)
            {
                foreach (var position in positionsToClose)
                {
                    ClosePosition(position);
                }
                Print("🚨 BOJ SPIKE DETECTED: EJECTING POSITIONS 🚨");
            }
        }

        private void ClosePositions(TradeType tradeType)
        {
            var positionsToClose = Positions.FindAll(_botLabel, SymbolName).Where(p => p.TradeType == tradeType);
            foreach (var position in positionsToClose)
            {
                ClosePosition(position);
            }
        }

        private void UpdateHUD(double currentZScore, double triggerLevel, bool isSpike, bool inCooldown, bool inMonthEnd)
        {
            string status;
            Color hudColor;

            if (isSpike)
            {
                status = "🚨 EJECTING / TAPE BOMB";
                hudColor = Color.Red;
            }
            else if (inMonthEnd)
            {
                status = "⚠️ MONTH-END LOCKOUT";
                hudColor = Color.Yellow;
            }
            else if (inCooldown)
            {
                status = $"⏳ COOLDOWN ({_cooldownBarsRemaining} BARS LEFT)";
                hudColor = Color.DarkOrange;
            }
            else
            {
                status = "✅ SYSTEM ARMED (GREEN)";
                hudColor = Color.LimeGreen;
            }

            string hudText = $"=== RISK DESK v2.0 ===\n" +
                             $"Live Shock: {Math.Round(currentZScore, 2)}σ / {Math.Round(triggerLevel, 2)}σ\n" +
                             $"State: {status}";

            Chart.DrawStaticText("RiskHUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, hudColor);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Institutional Upgrades in this cTrader Version:

Asynchronous Math Calculation: The CalculateBaselineMath() function uses manual arrays to calculate True Range history, Mean, and Standard Deviation. Crucially, this is attached to the OnBar() event. It only performs the heavy math once every 5 minutes (if on a 5m chart). The OnTick() loop only performs two lines of basic arithmetic to get the live Z-score.

The "Drip" Reset Mechanism: In OnTick, notice _cooldownBarsRemaining = CooldownBars;. Because this sits in the tick-loop, if the tape is in cooldown mode but the BOJ drops another headline and causes a second massive candle, the timer instantly gets kicked back to maximum.

Dynamic Thresholding via TimeSpan: cTrader’s Server.TimeInUtc.TimeOfDay makes filtering for the Asian/London overlap completely flawless. If activeTriggerLevel detects you are at month-end AND in Tokyo hours, it drops the required Sigma to trip the alarm by 20%, turning the algorithm hypersensitive right when intervention risk is highest.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

This is the pinnacle of retail-accessible algorithmic defense. Translating Version 3.0 to cTrader unleashes the true power of C# because we can seamlessly pull Background Symbol Data (Cross-Asset correlation) without the chart-lag that plagues Pine Script's request.security().

This cBot monitors your primary pair tick-by-tick, while simultaneously reading the order flow of a secondary pair in the background. It calculates structural reclaim thresholds natively and adjusts your lot sizes on the fly.

Here is the Institutional Risk Desk v3.0 translated perfectly into a cTrader cBot.

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class BOJRiskDesk_V3 : Robot
    {
        // =========================================================================
        // 1. INPUTS & DESK PARAMETERS
        // =========================================================================
        [Parameter("Volatility Baseline (Bars)", Group = "1. Macro Anomaly & Cross-Asset", DefaultValue = 100)]
        public int Lookback { get; set; }

        [Parameter("Primary Z-Score Trigger", Group = "1. Macro Anomaly & Cross-Asset", DefaultValue = 4.0)]
        public double ZThreshold { get; set; }

        [Parameter("Correlated JPY Cross", Group = "1. Macro Anomaly & Cross-Asset", DefaultValue = "GBPJPY")]
        public string SecondarySymbolName { get; set; }

        [Parameter("Cross-Asset Z-Score Min", Group = "1. Macro Anomaly & Cross-Asset", DefaultValue = 2.5)]
        public double CrossThreshold { get; set; }

        [Parameter("Initial Stand-Aside (Bars)", Group = "2. Dynamic Cooldown & Reclaim", DefaultValue = 15)]
        public int MaxCooldownBars { get; set; }

        [Parameter("Drip Reset Z-Score", Group = "2. Dynamic Cooldown & Reclaim", DefaultValue = 2.0, 
            DefaultValue = "Resets timer if secondary headlines drip during cooldown")]
        public double DripThreshold { get; set; }

        [Parameter("Month-End Lockout Day", Group = "3. Month-End & Session", DefaultValue = 26, MinValue = 1, MaxValue = 31)]
        public int EomDayStart { get; set; }

        [Parameter("Enable Month-End Filter", Group = "3. Month-End & Session", DefaultValue = true)]
        public bool EomActive { get; set; }

        [Parameter("Asian Session Start (UTC Hour)", Group = "3. Month-End & Session", DefaultValue = 0)]
        public int AsianSessStart { get; set; }

        [Parameter("Asian Session End (UTC Hour)", Group = "3. Month-End & Session", DefaultValue = 9)]
        public int AsianSessEnd { get; set; }

        [Parameter("Base Volume (Units)", Group = "4. Example Strategy", DefaultValue = 100000)]
        public double BaseVolume { get; set; }
        
        [Parameter("Fast EMA", Group = "4. Example Strategy", DefaultValue = 9)]
        public int FastMaPeriod { get; set; }

        [Parameter("Slow EMA", Group = "4. Example Strategy", DefaultValue = 21)]
        public int SlowMaPeriod { get; set; }

        // =========================================================================
        // STATE VARIABLES & COMPONENTS
        // =========================================================================
        private Bars _secondaryBars;
        private Symbol _secondarySymbol;

        private double _priMean, _priStdDev;
        private double _secMean, _secStdDev;
        
        private int _cooldownBarsRemaining = 0;
        private bool _hasReclaimed = true;
        private double _riskMultiplier = 1.0;
        private string _regimeState = "NORMAL";
        private string _botLabel = "RiskDesk_V3";

        // Indicators for Reclaim Structure & Entry Logic
        private AverageTrueRange _shortAtr;
        private SimpleMovingAverage _baselineAtr;
        private ExponentialMovingAverage _fastEma;
        private ExponentialMovingAverage _slowEma;

        protected override void OnStart()
        {
            // Initialize Background Cross-Asset Data
            _secondarySymbol = Symbols.GetSymbol(SecondarySymbolName);
            _secondaryBars = MarketData.GetBars(TimeFrame, SecondarySymbolName);

            // Initialize Reclaim Structure Indicators (ATR 14 vs 50 SMA of ATR)
            _shortAtr = Indicators.AverageTrueRange(Bars, 14, MovingAverageType.Simple);
            _baselineAtr = Indicators.SimpleMovingAverage(_shortAtr.Result, 50);

            // Entry Logic Indicators
            _fastEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastMaPeriod);
            _slowEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowMaPeriod);

            CalculateBaselines();
        }

        // =========================================================================
        // 2. TICK-BY-TICK ALERTS & CROSS-ASSET MATH
        // =========================================================================
        protected override void OnTick()
        {
            if (Bars.Count < Lookback + 2 || _secondaryBars.Count < Lookback + 2) return;

            // --- 1. Primary Live Z-Score ---
            double priTR = GetLiveTrueRange(Bars);
            double priZScore = _priStdDev > 0 ? (priTR - _priMean) / _priStdDev : 0;

            // --- 2. Secondary Live Z-Score ---
            double secTR = GetLiveTrueRange(_secondaryBars);
            double secZScore = _secStdDev > 0 ? (secTR - _secMean) / _secStdDev : 0;

            // --- 3. Dynamic Context ---
            TimeSpan time = Server.TimeInUtc.TimeOfDay;
            bool inDangerZone = time >= TimeSpan.FromHours(AsianSessStart) && time <= TimeSpan.FromHours(AsianSessEnd);
            bool isMonthEnd = Server.TimeInUtc.Day >= EomDayStart && EomActive;

            double activePriThresh = (inDangerZone && isMonthEnd) ? (ZThreshold * 0.75) : ZThreshold;

            // --- 4. The Macro Trigger ---
            // Triggers ONLY if the main symbol AND the background symbol spike together.
            bool isMacroSpike = (priZScore > activePriThresh) && (secZScore > CrossThreshold);

            // --- 5. Eject & Drip Reset State Machine ---
            if (isMacroSpike)
            {
                CloseAllPositions(priZScore, secZScore);
                _cooldownBarsRemaining = MaxCooldownBars; // Tape bomb: Max Lockout
            }
            else if (_cooldownBarsRemaining > 0 && priZScore > DripThreshold)
            {
                _cooldownBarsRemaining = MaxCooldownBars; // Drip headline: Reset timer
            }

            // Update the HUD display continuously
            UpdateHUD(priZScore, secZScore, isMacroSpike);
        }

        // =========================================================================
        // 3. BAR CLOSE: RECLAIM STRUCTURE & SIZING
        // =========================================================================
        protected override void OnBar()
        {
            if (Bars.Count < Lookback + 50 || _secondaryBars.Count < Lookback + 2) return;

            // 1. Recalculate Heavy Math for the next candle
            CalculateBaselines();

            // 2. Decrement Cooldown Timer
            if (_cooldownBarsRemaining > 0) _cooldownBarsRemaining--;
            bool inCooldown = _cooldownBarsRemaining > 0;

            // 3. Evaluate Structural Reclaim
            // Is current 14-period ATR back below the 50-period average of the ATR?
            _hasReclaimed = _shortAtr.Result.Last(1) < _baselineAtr.Result.Last(1);
            bool isMonthEnd = Server.TimeInUtc.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; // Cooldown finished, but tape hasn't compressed
                _regimeState = "RECLAIM / HALF-SIZE";
            }
            else
            {
                _riskMultiplier = 1.0;
                _regimeState = "ARMED / FULL SIZE";
            }

            // 5. ENTRY EXECUTION (Using Risk Multiplier)
            if (_riskMultiplier > 0)
            {
                bool longCondition = _fastEma.Result.Last(1) > _slowEma.Result.Last(1) && 
                                     _fastEma.Result.Last(2) <= _slowEma.Result.Last(2);
                                     
                bool shortCondition = _fastEma.Result.Last(1) < _slowEma.Result.Last(1) && 
                                      _fastEma.Result.Last(2) >= _slowEma.Result.Last(2);

                // Calculate the final lot size based on multiplier and broker lot-steps
                double targetVolume = BaseVolume * _riskMultiplier;
                double normalizedVolume = Symbol.NormalizeVolumeInUnits(targetVolume, RoundingMode.Down);

                if (normalizedVolume >= Symbol.VolumeInUnitsMin)
                {
                    if (longCondition)
                    {
                        ClosePositions(TradeType.Sell);
                        ExecuteMarketOrder(TradeType.Buy, SymbolName, normalizedVolume, _botLabel);
                    }
                    else if (shortCondition)
                    {
                        ClosePositions(TradeType.Buy);
                        ExecuteMarketOrder(TradeType.Sell, SymbolName, normalizedVolume, _botLabel);
                    }
                }
            }
        }

        // =========================================================================
        // 4. MATH & HELPERS
        // =========================================================================
        private double GetLiveTrueRange(Bars bars)
        {
            double high = bars.HighPrices.Last(0);
            double low = bars.LowPrices.Last(0);
            double prevClose = bars.ClosePrices.Last(1);
            return Math.Max(high, prevClose) - Math.Min(low, prevClose);
        }

        private void CalculateBaselines()
        {
            // Primary Symbol Math
            double priSumTR = 0, secSumTR = 0;
            double[] priTrHist = new double[Lookback];
            double[] secTrHist = new double[Lookback];

            for (int i = 1; i <= Lookback; i++)
            {
                // Primary
                double pH = Bars.HighPrices.Last(i), pL = Bars.LowPrices.Last(i), pC = Bars.ClosePrices.Last(i + 1);
                double pTr = Math.Max(pH, pC) - Math.Min(pL, pC);
                priTrHist[i - 1] = pTr;
                priSumTR += pTr;

                // Secondary
                double sH = _secondaryBars.HighPrices.Last(i), sL = _secondaryBars.LowPrices.Last(i), sC = _secondaryBars.ClosePrices.Last(i + 1);
                double sTr = Math.Max(sH, sC) - Math.Min(sL, sC);
                secTrHist[i - 1] = sTr;
                secSumTR += sTr;
            }

            _priMean = priSumTR / Lookback;
            _secMean = secSumTR / Lookback;

            double priVarSum = 0, secVarSum = 0;
            for (int i = 0; i < Lookback; i++)
            {
                priVarSum += Math.Pow(priTrHist[i] - _priMean, 2);
                secVarSum += Math.Pow(secTrHist[i] - _secMean, 2);
            }

            _priStdDev = Math.Sqrt(priVarSum / Lookback);
            _secStdDev = Math.Sqrt(secVarSum / Lookback);
        }

        private void CloseAllPositions(double pZ, double sZ)
        {
            var positions = Positions.FindAll(_botLabel, SymbolName);
            foreach (var pos in positions) ClosePosition(pos);
            if (positions.Length > 0)
                Print($"🚨 MACRO SHOCK EJECT: Pri Z={Math.Round(pZ,1)} | Sec Z={Math.Round(sZ,1)}");
        }

        private void ClosePositions(TradeType tradeType)
        {
            var positions = Positions.FindAll(_botLabel, SymbolName).Where(p => p.TradeType == tradeType);
            foreach (var pos in positions) ClosePosition(pos);
        }

        private void UpdateHUD(double pZ, double sZ, bool isSpike)
        {
            Color stateColor = _riskMultiplier == 0 ? Color.Red : (_riskMultiplier == 0.5 ? Color.DeepSkyBlue : Color.LimeGreen);
            if (Server.TimeInUtc.Day >= EomDayStart && EomActive) stateColor = Color.Yellow;

            string hud = $"--- INSTITUTIONAL RISK DESK v3.0 ---\n" +
                         $"Primary Z-Score  : {Math.Round(pZ, 2)}σ\n" +
                         $"Cross Z-Score    : {Math.Round(sZ, 2)}σ\n" +
                         $"Timer Status     : {(_cooldownBarsRemaining > 0 ? "WAIT " + _cooldownBarsRemaining : "CLEAR")}\n" +
                         $"Tape Structure   : {(_hasReclaimed ? "COMPRESSED" : "EXPANDED")}\n" +
                         $"Trade Regime     : {_regimeState}";

            Chart.DrawStaticText("RiskHUD", hud, VerticalAlignment.Top, HorizontalAlignment.Right, stateColor);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Pro cTrader Advancements in v3.0:

True Cross-Asset Logic (MarketData.GetBars): The script requests GBPJPY (or whatever secondary you assign) directly from the broker's server in the OnStart() method. In OnTick(), it evaluates the live unclosed True Range of both pairs simultaneously.

Built-in cTrader Chaining for Reclaim Math: To evaluate your "reclaim structure", the bot needs an average of an average. In cTrader C#, we pass the result of the ATR directly into the SimpleMovingAverage class (_baselineAtr = Indicators.SimpleMovingAverage(_shortAtr.Result, 50);). This keeps memory overhead incredibly low compared to doing this in Pine Script.

Volume Normalization Engine: The script takes your BaseVolume (e.g., 100,000 units / 1 Standard Lot) and multiplies it by the dynamic _riskMultiplier. If the tape hasn't reclaimed, it becomes 50,000. Crucially, it then wraps the result in Symbol.NormalizeVolumeInUnits(..., RoundingMode.Down). This ensures that even if you use odd math, cTrader will automatically round the lot size down to a format your specific broker accepts (micro lots, mini lots, etc.) without throwing an execution error.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply