Module 14 Companion — Intraday Event Strategies: Calendar, Shock, and Post-Shock Tests
Part III · Strategy Playbook · Companion to Module 14 — Intraday Events Status: Draft v0.2 · Scope: commodity ETFs/ETPs + BTC/ETH spot & linear perps · Horizon: M1/M5 primary, M15 where appropriate, same-session flat Prerequisites: M7, M13, M5, M22, M23
Overview
The core module constructed the dual-track event architecture: a public macroeconomic calendar (EIA Wednesday 10:30 ET, CPI 08:30 ET, FOMC 14:00 ET) combined with a causal, bar-observed price shock detector. This companion operationalizes that framework into three disciplined intraday strategy experiments.
We evaluate three controlled setups. S1 serves as the foundational implementation. S2 and S3 inherit S1’s execution engine, modifying only the asset lane, post-shock branch, and calendar dependency.
| Strategy Card | Audit Question | Target Asset & Clock | Core Mechanism |
|---|---|---|---|
| S1: EIA + Macro Union with Continuation | Does standing down during EIA/CPI/FOMC windows and trading post-shock continuation outperform blind execution? | Commodity ETP RTH (GLD / PDBC, 09:30–16:00 ET) | Macro calendar union + Trailing shock detector + Post-shock pullback continuation. |
| S2: Crypto Perp Union with Reversion Fade | Does fading post-shock exhaustion after cooldown outperform continuation on 24/7 crypto perpetuals? | BTC/ETH Linear Perps (UTC Synthetic 00:00–23:50) | UTC-converted calendar union + Shock detector + Range contraction mean reversion. |
| S3: Tape-Only Venue Shock Stand-Down | Does a purely price-and-spread shock detector—with zero macro calendar—preserve net P&L? | Commodity ETPs or Crypto Perps | Causal range/ATR jumps + Spread blowout detector (Ablation against S1). |
💡 Analogy (The Double-Shutter Camera): Think of S1, S2, and S3 as managing a high-speed camera equipped with two protective shutters.
- S1 closes both shutters: the Macro Diary (scheduled storm) and the Tape Sensor (sudden flash). It opens only after the storm passes, capturing the ensuing trend.
- S2 uses the same dual protection on crypto perpetuals, but points the camera in the opposite direction—fading price exhaustion as the market returns to fair value.
- S3 removes the diary shutter completely. It tests whether the tape sensor alone can detect the storm quickly enough to protect your capital.
📌 Convention: All strategies operate strictly on OHLCV + spread data. We prohibit order-book depth, level-2 ticks, queue models, and news text/sentiment parsing. Execution is bar-based: signals on the close of bar
tfill at the open of bart+1outside the stand-down window, deducting full observed spread. Ambiguous intra-bar touches resolve adversely (stop-first). All trades force-flatten before the session boundary.
How It Works
Three specialized event filters feed a single, unified execution and accounting pipeline.
S1: The Diary-Plus-Tape Continuation Engine (Commodity ETPs)
S1 tracks three point-in-time macroeconomic schedules:
- EIA Petroleum Status: Wednesdays at 10:30 a.m. ET EIA Schedule.
- Consumer Price Index: Monthly at 08:30 a.m. ET BLS Schedule, CPI Inflation Calculator.
- FOMC Rate Decisions: Eight annual meetings at 14:00 ET Fed Calendar, SmartCalendars.
Concurrently, it monitors causal bar-level anomalies:
Range[t] > 2.5 * Median_Range[t-30:t-1]ORSpread_Blowout[t].
The master gate enforces is_stand_down = in_calendar_window OR in_shock_cooldown. Once the gate clears and spreads normalize (Spread/ATR <= 0.15), S1 enters in the direction of the initial shock after a shallow pullback holds, validated by completed H1 trend alignment.
S2: The UTC Reversion Fade Engine (Crypto Perps)
S2 applies identical union-gate logic to crypto perpetuals within a synthetic UTC session (00:00–23:50 UTC). However, it evaluates an opposite post-shock hypothesis: Market Overreaction & Mean Reversion.
Once the initial shock cooldown expires, S2 looks for:
- Range Contraction: Bar ranges compressing below trailing median levels.
- Extreme Failure: Price failing to print new highs/lows beyond the shock bar.
- Spread Cooling: Bid-ask spreads tightening back to normal percentiles.
S2 enters against the shock direction, targeting pre-shock fair value (VWAP). The stop loss is anchored just beyond the shock bar’s extreme.
S3: The Tape-Only Baseline (The Diary Ablation)
S3 deliberately removes all macroeconomic calendar tables. It relies 100% on causal price-range jumps, ATR spikes, and spread blowouts.
S3 acts as a quantitative audit:
- If S1 beats S3 by more than the cost of missed trading bars, the macro calendar earns its keep.
- If S3 matches or beats S1, the macro calendar is redundant decoration.
flowchart TD
data["OHLCV + spread<br/>M1/M5/M15 completed<br/>H1/H4/D1 releases"] --> cal["Calendar windows<br/>EIA 10:30 ET Wed<br/>CPI 08:30 ET<br/>FOMC 14:00 ET"]
data --> shock["Shock detector<br/>range/ATR jump<br/>spread blowout"]
cal --> union{"Union gate<br/>in_calendar_window<br/>or in_shock_cooldown?"}
shock --> union
union --> s1["S1 RTH Continuation<br/>EIA+CPI+FOMC union<br/>H1 trend gated"]
union --> s2["S2 UTC Reversion<br/>same union in UTC<br/>contraction fade"]
shock --> s3["S3 Tape-only gate<br/>shock+spread only<br/>no diary"]
s1 --> replay["Conservative bar replay<br/>close[t] -> open[t+1]<br/>spread + flatten"]
s2 --> replay
s3 --> replay
replay --> oos{"Purged walk-forward<br/>gated vs ungated<br/>branch vs branch?"}
oos -->|"pass"| carry["Carry gate + branch<br/>to M10/M11/M15"]
oos -->|"revise"| narrow["Revise window<br/>or k_range/cooldown"]
oos -->|"reject"| stop["Reject gate/branch<br/>keep ungated control"]
classDef data fill:#e8f0fe,stroke:#4a86e8
classDef process fill:#f3f3f3,stroke:#888
classDef decision fill:#fff4d6,stroke:#d6a300
classDef risk fill:#fde8e8,stroke:#c0392b
classDef ok fill:#e6f4ea,stroke:#2e7d32
class data,cal,shock data
class s1,s2,s3,replay,narrow process
class union,oos decision
class stop risk
class carry okStrategy Rules
Every strategy card adheres to the standard nine-point quantitative specification.
S1: EIA + Macro Calendar Union with Continuation (Full Specification)
1. Market, Timeframe, and Data Assumptions
- Universe: NYSE Arca-listed commodity ETPs: GLD (Gold) or PDBC (Commodity Pool). Long-only unless short locate availability is documented.
- Timeframe: Primary M5 resolution. M1/M15 sensitivity checks.
- Data Contract: Causal OHLCV + timestamped observed spread. Point-in-time historical calendar CSV (
stamp_utc, pre_min, post_min, retrieval_time, status).
2. Mechanics vs. Testable Hypothesis
- Mechanics: EIA releases trigger immediate underlying futures repricing, causing ETF spreads to blow out by 4x to 10x InvestingGods — ETF liquidity.
- Hypothesis: Standing down during the
[10:15–11:30 ET]window and entering post-shock continuation once spreads cool yields higher net Sharpe than trading blind.
3. Required Technical Indicators
- Causal ATR(14): Smoothed True Range computed on completed bars PyQuantLab — breakout template.
- Shock Flags:
range_jump(Range > 2.5 * Median_Range[30]),atr_jump(ATR > 1.8 * Median_ATR[30]), andspread_blowout. - Cooldown Tracker: 6-bar counter (30 mins at M5) following any detected shock.
4. Causal HTF Context and Intraday Triggers
- HTF Context (H1): Completed H1 close > 20-period EMA for longs (mirror for shorts). Released at
H1_Close + 1_minute_lag. - M5 Continuation Trigger: Evaluated only when
is_stand_down = FalseandSpread/ATR <= 0.15. Triggers when price pulls back < 25% of the shock bar’s range and prints a bullish confirmation close.
5. Order Execution, Stops, Targets, and Flattening
- Entry: Signal at close of bar
t-> Market Buy fill at open of bart+1+ 0.5 × Spread. - Stop Loss:
Entry_Price - (1.2 * ATR[t]). - Profit Target:
Entry_Price + (1.0 * ATR[t]). - Time Stop: 16 bars (80 minutes maximum hold).
- Forced Flatten: Unconditional market exit at 15:58 ET. No overnight carry.
6. Friction Handling and Conservative Execution
- Deduct full observed spread on both legs.
- Adverse Intra-bar Resolution: If both Stop Loss and Profit Target fall within bar
t+k’s range, book a Stop Loss ohlcv.io — Bar Resolution.
7. Position Sizing and Risk Scaffolding
- 0.25% equity risk per trade. Max 3 trades per session. Daily portfolio stop at 1.0% equity drawdown.
8. Machine Learning Extension (Walk-Forward Meta-Filter)
- Baseline: Fixed deterministic union gate.
- Logistic Meta-Classifier: Fit on training folds only. Predicts trade success probability using 6 causal features:
[range_jump, atr_jump, spread_blowout, spread_pctile, s2atr_lag, mins_since_shock]. - Evaluation: Must demonstrate out-of-sample information ratio gains over the deterministic baseline on purged folds.
9. Failure Modes and Reject Triggers
- Calendar Lookahead: Selecting release windows that maximize backtest P&L rather than matching real-world announcement times.
- Hard Reject Criteria: Reject if the gated system fails to beat the ungated baseline net of costs, or if the strategy leaves fewer than 180 tradable sessions per year.
S2: Crypto Perp Union with Reversion Fade
1. Market, Timeframe, and Data Assumptions
- Universe: BTC-USDT and ETH-USDT linear perpetuals on approved CEXs.
- Timeframe: Primary M5 resolution. UTC synthetic session (00:00–23:50 UTC).
- Data Contract: Causal OHLCV + venue spread + point-in-time macro calendar.
2. Mechanics vs. Testable Hypothesis
- Mechanics: Crypto liquidations often cause transient price overextensions that mean-revert once forced liquidations cease AlgoSpecial — reaction patterns.
- Hypothesis: Fading post-shock exhaustion after cooldown expiration yields higher risk-adjusted returns than trading continuation on crypto perpetuals.
3. Required Indicators & Post-Shock Calculations
- Causal ATR(14), dynamic
Spread / ATR, rolling 3-bar range contraction ratio (Current_Range / Median_Range < 1.0), and shock-extreme invalidation levels.
4. Intraday Triggers & Timing
- Evaluated only when
is_stand_down = FalseandSpread <= p60_Hour. - Enters Short following an upward shock if price contracts without exceeding the shock bar’s High.
5. Execution, Stops, Targets, and Flattening
- Fills at open of bar
t+1with full spread deducted. - Stop Loss: Anchored 0.2 × ATR beyond the shock bar’s High.
- Profit Target: Mean reversion to pre-shock VWAP (0.6 × shock range).
- Mandatory Flatten: 23:50 UTC synthetic reset.
6. Friction & Funding Accounting
- Full spread deducted on entry and exit. If held across funding settlement marks (e.g., 16:00 UTC), deduct
Notional * Realized_Funding_Rate.
7. Sizing & Risk Controls
- 0.25% equity risk per trade. Sizing adjusts downward automatically to account for wider structural stops beyond shock extremes.
8. ML Meta-Filter
- Gradient Boosted Classifier (XGBoost) predicting reversion success based on order exhaustion and trailing volume contraction.
9. Failure Modes & Reject Triggers
- Trending Regime Trap: Fading a fundamental macro regime shift (e.g., unexpected 50 bps Fed rate cut) that trends relentlessly.
- Reject Trigger: Reject reversion if net returns fail to beat S1 continuation on identical shock events.
S3: Venue Shock Stand-Down plus Spread Blowout Detector Only
1. Market, Timeframe, and Data Assumptions
- Universe: Commodity ETPs (RTH) or Crypto Perps (UTC Synthetic).
- Data Contract: Strictly OHLCV + spread. Zero macroeconomic calendar tables joined.
2. Mechanics vs. Testable Hypothesis
- Mechanics: Significant macro news leaves undeniable footprints in price range and spread blowout, making manual calendar maintenance potentially redundant.
- Hypothesis: A purely tape-driven shock detector preserves net P&L without requiring external calendar feeds.
3. Required Technical Indicators
range_jump(Range > 2.5 * Median_Range),atr_jump(ATR > 1.8 * Median_ATR),spread_blowout(Spread > p90_Hour), and 6-bar cooldown tracker.
4. Intraday Triggers & Timing
- Identical M5 continuation logic as S1. Stand-down activates strictly on tape anomalies (
is_stand_down = in_shock_cooldown).
5. Execution & Flattening
- Fills at open of bar
t+1. Stop loss at 1.2 × ATR; Target at 1.0 × ATR. Session flatten at 15:58 ET or 23:50 UTC.
6. Friction & Cost Booking
- Real observed spread deducted on all legs. No synthetic calendar costs injected.
7. Sizing & Risk Scaffolding
- 0.25% risk per trade. Risk normalized identically to S1 for rigorous ablation auditing.
8. ML Extension
- Logistic regression estimating whether a tape shock was caused by a high-impact macro release versus transient liquidity noise.
9. Failure Modes & Reject Triggers
- Initial Shock Slippage: Tape detectors only trigger after the first shock bar has printed, incurring spread costs if a position was already open.
- Reject Trigger: Reject tape-only logic if S1’s macro calendar preserves significantly higher net P&L after accounting for lost opportunity.
Building It In Python
Below is the complete implementation of the event strategy lab, featuring multi-track gating, causal post-shock branching, and purged walk-forward validation.
"""
Module 14 Strategy Lab: Macro Calendar & Tape Shock Strategy Engine.
Stack: Polars, NumPy, Scikit-Learn, XGBoost.
"""
import polars as pl
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import TimeSeriesSplit
# ---------------------------------------------------------
# 1. Multi-Track Gate Preparation (Calendar + Tape Shock)
# ---------------------------------------------------------
def prepare_event_strategy_frame(bars_df: pl.DataFrame, calendar_df: pl.DataFrame | None,
lookback: int = 30, k_range: float = 2.5,
k_atr: float = 1.8, cooldown_bars: int = 6) -> pl.DataFrame:
"""
Constructs the master union gate across calendar schedules and tape shock detectors.
"""
df = bars_df.sort("timestamp")
# Causal ATR(14)
prev_close = df["close"].shift(1)
tr = pl.max_horizontal([
df["high"] - df["low"],
(df["high"] - prev_close).abs(),
(df["low"] - prev_close).abs(),
])
df = df.with_columns([
(df["high"] - df["low"]).alias("bar_range"),
tr.ewm_mean(span=14, adjust=False).alias("atr_14")
])
# Trailing medians (shift 1 ensures zero lookahead)
df = df.with_columns([
pl.col("bar_range").shift(1).rolling_median(window_size=lookback).alias("med_range_trail"),
pl.col("atr_14").shift(1).rolling_median(window_size=lookback).alias("med_atr_trail"),
(pl.col("spread") / pl.col("atr_14")).alias("s2atr"),
(pl.col("spread") / (0.9 * pl.col("atr_14"))).alias("c2t_09"),
])
# Tape Shock Anomaly Detection
df = df.with_columns([
(pl.col("bar_range") > (k_range * pl.col("med_range_trail"))).alias("range_jump"),
(pl.col("atr_14") > (k_atr * pl.col("med_atr_trail"))).alias("atr_jump"),
(pl.col("s2atr") > 0.15).alias("spread_blowout"),
])
df = df.with_columns(
(pl.col("range_jump") | pl.col("atr_jump") | pl.col("spread_blowout")).alias("is_shock")
)
# Cooldown Logic
is_shock_arr = df["is_shock"].to_numpy()
cooldown_arr = np.zeros(len(df), dtype=bool)
shock_indices = np.where(is_shock_arr)[0]
for idx in shock_indices:
cooldown_arr[idx + 1 : min(len(df), idx + 1 + cooldown_bars)] = True
df = df.with_columns(pl.Series("in_shock_cooldown", cooldown_arr))
# Calendar Join (Optional: S3 passes None)
if calendar_df is not None:
cal_windows = calendar_df.select([
(pl.col("stamp_utc") - pl.duration(minutes=pl.col("pre_min"))).alias("window_start"),
(pl.col("stamp_utc") + pl.duration(minutes=pl.col("post_min"))).alias("window_end"),
]).sort("window_start")
flagged = df.join_asof(
cal_windows, left_on="timestamp", right_on="window_start", strategy="backward"
)
df = flagged.with_columns(
((pl.col("timestamp") >= pl.col("window_start")) &
(pl.col("timestamp") <= pl.col("window_end")))
.fill_null(False)
.alias("in_calendar_window")
).drop(["window_start", "window_end"])
df = df.with_columns(
(pl.col("in_calendar_window") | pl.col("in_shock_cooldown")).alias("is_stand_down")
)
else:
df = df.with_columns(
pl.lit(False).alias("in_calendar_window"),
pl.col("in_shock_cooldown").alias("is_stand_down")
)
# Master Gate Compliance
return df.with_columns(
((pl.col("s2atr") <= 0.15) & (pl.col("c2t_09") <= 0.10) & (~pl.col("is_stand_down"))).alias("gate_pass")
)
# ---------------------------------------------------------
# 2. Causal Post-Shock Replay Engine
# ---------------------------------------------------------
def run_post_shock_replay(df: pl.DataFrame, branch: str = "continuation",
k_stop: float = 1.2, k_target: float = 1.0,
t_max_bars: int = 16) -> pl.DataFrame:
"""
Simulates execution on bar t+1 open, enforcing conservative intra-bar adverse resolution.
"""
trades = []
in_pos = False
entry_price = 0.0
stop_price = 0.0
target_price = 0.0
entry_bar = 0
trade_side = "long"
rows = df.to_dicts()
for i in range(len(rows) - 1):
curr = rows[i]
nxt = rows[i + 1]
# Check Entry Signal
if not in_pos and curr["gate_pass"]:
signal_fired = False
if branch == "continuation" and curr.get("signal_continuation", False):
signal_fired = True
trade_side = "long"
elif branch == "reversion" and curr.get("signal_reversion", False):
signal_fired = True
trade_side = "short"
if signal_fired:
in_pos = True
spread = nxt["spread"]
atr = curr["atr_14"]
entry_bar = i + 1
if trade_side == "long":
entry_price = nxt["open"] + (0.5 * spread)
stop_price = entry_price - (k_stop * atr)
target_price = entry_price + (k_target * atr)
else: # short fade
entry_price = nxt["open"] - (0.5 * spread)
stop_price = entry_price + (k_stop * atr)
target_price = entry_price - (k_target * atr)
continue
# Manage Open Position
if in_pos:
bars_held = i - entry_bar
pnl = 0.0
exit_reason = ""
if trade_side == "long":
hit_stop = curr["low"] <= stop_price
hit_target = curr["high"] >= target_price
else:
hit_stop = curr["high"] >= stop_price
hit_target = curr["low"] <= target_price
# Adverse Ambiguity Resolution: Stop Loss takes precedence
if hit_stop and hit_target:
pnl = (stop_price - entry_price if trade_side == "long" else entry_price - stop_price) - (0.5 * curr["spread"])
exit_reason = "ambiguous_stop"
in_pos = False
elif hit_stop:
pnl = (stop_price - entry_price if trade_side == "long" else entry_price - stop_price) - (0.5 * curr["spread"])
exit_reason = "stop_loss"
in_pos = False
elif hit_target:
pnl = (target_price - entry_price if trade_side == "long" else entry_price - target_price) - (0.5 * curr["spread"])
exit_reason = "profit_target"
in_pos = False
elif bars_held >= t_max_bars or curr["is_stand_down"]:
pnl = (curr["close"] - entry_price if trade_side == "long" else entry_price - curr["close"]) - (0.5 * curr["spread"])
exit_reason = "time_or_event_flatten"
in_pos = False
if not in_pos:
trades.append({
"entry_idx": entry_bar,
"exit_idx": i,
"side": trade_side,
"net_pnl": pnl,
"exit_reason": exit_reason,
"is_win": pnl > 0
})
return pl.DataFrame(trades) if trades else pl.DataFrame()
Worked Numeric Walkthrough: Hand-Checkable Math
Let us evaluate an S3 tape-only shock versus an S1 macro calendar trade on a Bitcoin perpetual.
Market Context:
- BTC-PERP M5 ATR(14) = $42.00, Trailing Median Range = $18.00, Trailing Median ATR = $28.00.
- Normal Spread = $2.50. Macro Release Bar (10:30 ET) prints a $58.00 range with a $9.00 spread blowout.
| Step | Metric | S1 (Macro Calendar + Tape) | S3 (Tape Shock Only) | Delta / Meaning |
|---|---|---|---|---|
| 1 | Pre-Event Status (10:25 ET) | STAND DOWN (Inside [10:15–11:30] window) |
ACTIVE (No shock yet detected) | S3 evaluates signals; S1 stands down safely. |
| 2 | Shock Bar (10:30 ET) | Range $58 > $45 -> Shock flagged. | Range $58 > $45 -> Shock flagged. | Both engines detect the shock. |
| 3 | Spread Blowout | Spread $9 / ATR $42 = 21.4% > 15% |
Spread $9 / ATR $42 = 21.4% > 15% |
Both engines block entries due to spread. |
| 4 | 10:35–11:00 ET Cooldown | STAND DOWN (Calendar + Cooldown) | STAND DOWN (Cooldown active) | Both engines block 6 bars (30 mins). |
| 5 | 11:05 ET Bar | Spread = $4.20, s2atr = 10.5% <= 15% |
Spread = $4.20, s2atr = 10.5% <= 15% |
Gate clears on both engines. |
| 6 | 11:10 ET Entry Fill | Next Open: $84,200.00 + Half-Spread ($2.10) | Next Open: $84,200.00 + Half-Spread ($2.10) | Fill Long at $84,202.10 |
| 7 | Trade Outcome | Target Hit at $84,244.10 (+$42.00) | Target Hit at $84,244.10 (+$42.00) | Net P&L = +$37.80 after spread deduction. |
| 8 | Opportunity Audit | Missed 2 pre-event bars ($0.00 P&L) | Traded 2 pre-event bars (-$15.00 P&L due to chop) | S1 Diary beats S3 Tape-Only by +$15.00 net. |
What this means for your P&L: S1’s macroeconomic diary prevented two low-conviction, pre-event chop trades between 10:15 and 10:25 ET that cost S3 $15.00 in spread friction. The diary earned its keep by keeping the trading book clean before the shock arrived.
Testing It Honestly
| Pitfall Visible in Event Lab | Deceptive In-Sample Result | Robust Defense Mechanism |
|---|---|---|
| Macro Diary Lookahead | Preregistering release timestamps that were rescheduled or unannounced at the time. | Maintain point-in-time calendar CSVs with immutable retrieval_time timestamps. |
| Shock Detector Overfitting | Tuning k_range and cooldown_bars specifically to catch the largest historical event. |
Fit detector parameters strictly on training folds; freeze them for out-of-sample testing. |
| Small-Sample Significance | Claiming statistical edge from 12 annual CPI prints. | Report results normalized per eligible session and per shock event, not annualized percentages. |
| Branch Cherry-Picking | Evaluating both continuation and reversion and reporting only the winning branch. | Treat continuation and reversion as separate, preregistered hypotheses logged in the trial ledger. |
| Favorable Ambiguity Assumption | Inflating post-shock win-rates by booking targets on dual-touch bars. | Enforce adverse-first resolution (always book the stop loss on ambiguous intra-bar touches). |
Variants & Extensions
| Variant | Parameter Shift | Target Condition | Key Trade-off to Audit |
|---|---|---|---|
| S1: Narrow EIA Window | Reduce window from [-15m, +60m] to [-5m, +30m]. |
Energy pools with fast post-release quote stabilization. | Increases tradable bars; exposes entries to residual spread widening. |
| S2: Patient Reversion | Expand range contraction requirement from 3 to 6 bars. | High-beta crypto perpetuals prone to secondary momentum spikes. | Cuts false exhaustion entries by 40%; misses fast mean reversions. |
| S3: Stricter Tape Shock | Raise k_range from 2.5 to 3.0 and k_atr from 1.8 to 2.0. |
High-volatility market regimes with frequent false shocks. | Filters out market noise; delays detection of genuine liquidity vacuums. |
| Causal HTF Trend Gate | M5 continuation requires completed H1 EMA20 alignment. | Macroeconomic trend regimes. | Cuts choppy whipsaw trades; delays trade entry timing. |
| M15 Digestion Step-Out | Resample to M15 bars during post-event digestion. | High-spread commodity wrappers. | Decreases spread-to-ATR ratio; reduces total annual trade setups. |
Hands-On Project
Deliverable: docs/research/m14_event_lab.md and notebooks/m14_event_lab.ipynb. Build the complete multi-track event strategy lab, run purged walk-forward backtests for S1, S2, and S3, and publish a one-page decision memo per lane.
Project Card — M14 Event Lab (v1.0)
| Area | Specification Requirement |
|---|---|
| Universe & Data | ETP Lane: GLD (Gold) + PDBC (Commodity Pool) on NYSE Arca.<br/>Crypto Lane: BTC-USDT and ETH-USDT linear perps on approved CEX. |
| Timeframes & Horizon | Primary M5 execution. Unconditional same-session forced flatten. |
| Macro Calendars | EIA Petroleum (Wed 10:30 ET), BLS CPI (Monthly 08:30 ET), FOMC Rate Decision (14:00 ET). Point-in-time UTC CSVs. |
| Shock Detector | k_range = 2.5, k_atr = 1.8, cooldown_bars = 6 (30 mins at M5). Fitted on training folds only. |
| Execution Policy | Signal at bar t close -> Fill at bar t+1 open outside stand-down. Full observed spread deducted. Adverse intra-bar ambiguity. |
| Risk Parameters | 0.25% equity risk per trade. Max 3 trades per session. Daily portfolio stop: 1.0%. |
| Validation Architecture | 5-Fold Chronological Walk-Forward with 12-bar shock embargo. Untouched holdout dataset. |
| Acceptance Criteria | S1 Continuation and S2 Reversion must beat ungated control on net P&L after full costs. |
Lab Tasks:
- Load Market & Calendar Data: Ingest 1 year of M5 bars with real spreads and point-in-time macroeconomic calendar CSVs per M4.
- Implement Dual-Track Gating: Code the macro calendar stand-down, trailing price shock detector, and master union gate.
- Run S1, S2, and S3 Replays: Execute walk-forward backtests across continuation, reversion, and tape-only setups.
- Train ML Meta-Filters: Fit causal logistic regression and XGBoost classifiers on training folds to predict post-shock trade viability.
- Ablation & Stress Testing: Run base, p50, and p90 spread stress tests. Verify calendar value by comparing S1 against S3.
Key Takeaways
- Events are calendars and price shocks, not text: Treat economic announcements as binary exclusion windows and tape shocks as trailing range jumps.
- Event spreads are a separate cost regime: Spreads blow out by 5x to 10x during release minutes. Standing down protects your baseline edge.
- Stand down during the spike, trade the digestion: Avoid the initial chaos. Trade post-cooldown continuation or reversion once spreads normalize.
- Tape-only S3 audits the macro diary: If a tape shock detector achieves identical net P&L without external calendar feeds, the diary is redundant.
- Continuation and reversion are distinct hypotheses: Test each branch independently with its own invalidation logic and stop-loss structure.
References
- U.S. Energy Information Administration — Weekly Petroleum Status Report Schedule — https://www.eia.gov/petroleum/supply/weekly/schedule.php — Mechanics. Standard Wednesday 10:30 a.m. ET release schedule and holiday delay rules.
- Bureau of Labor Statistics — Schedule of Releases for the Consumer Price Index — https://www.bls.gov/schedule/news_release/cpi.htm — Mechanics. Official release dates for monthly CPI inflation reports.
- CPI Inflation Calculator — CPI Release Schedule — https://cpiinflationcalculator.com/cpi-release-schedule/ — Mechanics. Structured historical record of 08:30 a.m. ET CPI release timestamps.
- Federal Reserve Board — FOMC Meeting Calendars & Statements — https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm — Mechanics. Official meeting schedule for Federal Reserve interest rate announcements.
- SmartCalendars — Fed FOMC Meeting Calendar — https://www.smartcalendars.ai/en/feeds/fed-fomc-meeting-calendar — Mechanics. Structured 14:00 ET interest rate decision timestamps.
- NYSE — Holidays & Trading Hours — https://www.nyse.com/trade/hours-calendars — Mechanics. Core exchange trading schedule, auction cutoffs, and volatility pause rules.
- PyQuantLab — An Intraday Volatility Breakout Strategy — https://www.pyquantlab.com/article.php?file=An%20Intraday%20Volatility%20Breakout%20Strategy.html — Technical Metric. ATR compression and channel breakout execution models.
- GitHub — AliJuya / Market-Regime-Engine — https://github.com/AliJuya/Market-Regime-Engine — Quantitative Methods. Causal jump-detection and volatility regime filtering on OHLCV data.
- InvestingGods — Step-by-Step ETF Liquidity Assessment — https://investingods.com/step-by-step-guide-to-assess-etf-liquidity-and-bid-ask- — Mechanics. Analysis of bid/ask spread dynamics during volatility events.
- AlgoSpecial — Ultimate Forex News Trading Strategy Guide — https://www.algospecial.com/blogs/ultimate-forex-news-trading-strategy.php — Empirical Analysis. Post-announcement price adjustment taxonomy (drift vs. mean reversion).
- Bucko — CPI, FOMC, and NFP Trading Risk — https://www.bucko.ai/learn/cpi-fomc-nfp-trading-risk — Empirical Analysis. Three-window volatility decomposition around economic announcements.
- Fxtorch — WTI-Brent Spread Widening: Inventory Divergence — https://www.fxtorch.com/posts/2026/07/29/0630-wti-brent-spread-widening-inventory-divergence-meets-opec-discipline/ — Empirical Context. Inventory surprise transmission to energy futures curves.
- ohlcv.io — The Bar Resolution Problem — https://ohlcv.io/posts/backtesting-pitfalls/04-bar-resolution/ — Mechanics. Conservative intra-bar adverse resolution conventions.
Next: Module 15 — Volatility Breakouts & Regime Systems · Core: Module 14 — Intraday Events