The Learning Library
Contents

Module 7 Companion — Scalping/Intraday Frequency & Feasibility Experiments

Part III · Intraday Design, Alpha & Playbooks · Companion to Module 7 Prerequisites: M7, M5, M22, M23


Overview

The core module established the feasibility framework: evaluating Average True Range (ATR) against observed spreads, calculating cost-to-target ratios, building hourly atlases, and applying conservative same-session execution rules.

This companion provides three structured experiments designed to answer one central question: Is this specific timeframe and session window tradable after costs, or should it be stepped slower, restricted to liquid hours, or rejected entirely?

None of these experiments claims to prove alpha. Each is an empirical feasibility audit with preregistered cost ceilings, explicit metrics, and objective rejection criteria.

Experiment Core Question Target Lane & Clock Primary Gate
E1 — ATR-to-Spread Frontier Do median moves at M1, M5, and M15 cover transaction costs net of spread? One commodity ETP (RTH) + one BTC/ETH perp (UTC synthetic) Spread-to-ATR ≤ ceiling and Cost-to-Target ≤ ceiling
E2 — Session Clock Atlas Which specific hours of the trading day offer high ATR and narrow spreads? ETP RTH (09:30–16:00 ET) vs. Crypto synthetic (00:00–23:50 UTC) Hourly pass rate across cost and volatility filters
E3 — Causal HTF Gating Does higher-timeframe alignment improve net viability, or does it merely starve trade frequency? Same two lanes across candidate execution timeframes Aligned net pass rate and opportunity count vs. ungated baseline

💡 The Wage vs. Toll Analogy: Feasibility is an accounting audit, not a prediction. You are comparing the broker’s invoice (spread + fees) against the market’s gross wage (ATR) under a strict calendar that tells you when both are active.

📌 Data Contract: All experiments use OHLCV + spread data only. Technical calculations are bar-based; no order book or tick data is assumed. Signals on the close of bar t fill at the open of bar t+1. Ambiguous bars resolve stop-first. All positions are forced flat before the session close. Completed H1/H4/D1 bars serve as causal context only.


How It Works

The three experiments follow a sequential decision pipeline.

E1 — The Frequency Frontier

Resample completed bars to M1, M5, and M15. Calculate median ATR, median observed spread, Spread-to-ATR, and Cost-to-Target across each timeframe. Apply your preregistered cost ceilings and opportunity floors. Timeframes that fail basic arithmetic are eliminated immediately.

E2 — The Clock Atlas

Take the surviving timeframe from E1 and break it down by hour of the day. Map median spread, median ATR, and cost pass rates across each hourly tile. This isolates structural liquidity sweet spots (narrow spreads, tall bars) from expensive dead zones (wide spreads, flat bars).

E3 — The Causal HTF Gate Test

Add a completed higher-timeframe filter (such as an H1 20 EMA trend or H4 volatility regime) to the surviving timeframe. Compare the gated strategy directly against the ungated baseline. An HTF gate is only justified if it improves net expectancy per session without crushing overall trade opportunity.

Figure: The experimental workflow. E1 screens timeframes, E2 maps tradable hours, and E3 audits higher-timeframe gating.

flowchart TD
    data["OHLCV + spread<br/>completed M1/M5 + H1/H4/D1 releases"] --> e1["E1 Frontier<br/>M1 vs M5 vs M15"]
    e1 -->|"best TF + target"| e2["E2 Clock Atlas<br/>RTH vs UTC synthetic"]
    e1 --> e3["E3 HTF-Gated Feasibility<br/>gated vs ungated"]
    e2 --> e3
    e3 --> decision{"Revise<br/>or reject?"}
    decision -->|"pass"| carry["Carry horizon + clock + gate<br/>to M8/M10/M11"]
    decision -->|"revise"| e1
    decision -->|"reject"| stop["Reject TF or clock<br/>document & stop spend"]

    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 data
    class e1,e2,e3 process
    class decision decision
    class stop risk
    class carry ok

Strategy Rules

Each experiment follows our standardized nine-point protocol.

E1 — ATR-to-Spread & Cost-to-Target Frontier (M1 vs. M5 vs. M15)

1. Market, Timeframe, and Data Assumptions

  • Lanes: One U.S. commodity ETP (e.g., GLD physical trust or PDBC futures pool) and one BTC or ETH linear perpetual on an approved exchange.
  • Clocks: ETP operates on RTH (09:30–16:00 ET); Crypto operates on an explicit UTC synthetic session (00:00–23:50 UTC).
  • Data: Clean OHLCV + timestamped spread per bar. Resampled strictly from completed M1 bars.

2. Mechanics vs. Testable Hypothesis

  • Mechanics: ETF share creation/redemption SEC ETF bulletin, futures roll costs Fidelity on contango/backwardation, and perpetual funding rates Coinbase perpetual funding dictate product friction.
  • Hypothesis: At least one timeframe in {M1, M5, M15} generates sufficient median ATR relative to spread such that Spread-to-ATR ≤ 15% and Cost-to-Target ≤ 10% while satisfying the session opportunity floor.

3. Required Indicators & Calculations

  • True Range and ATR(14) via exponential moving average.
  • Timestamped spread and rolling session spread percentiles.
  • Derived ratios: Spread ÷ ATR and (Spread + Fees + Slippage) ÷ Target.

4. Causal HTF Context & LTF Trigger

  • E1 evaluates raw baseline feasibility without HTF gating.
  • Signals evaluate at the close of bar t and execute at the open of bar t+1.

5. Entry, Stop, Target, Time Stop, and Forced Flatten

  • Simulated Entry: Open of bar t+1 paying the ask (entry price + half-spread).
  • Hypothetical Target: $0.9\times$ to $1.2\times$ ATR from entry.
  • Hypothetical Stop: $1.0\times$ to $1.5\times$ ATR from entry.
  • Forced Flatten: Unconditional exit at the final eligible bar of the session. Zero overnight positions.

6. Cost Filters & Conservative Execution

  • Cost Ceilings: Median Spread-to-ATR ≤ 15%; Cost-to-Target ≤ 10%.
  • Path Ambiguity: If a bar touches both stop and target, record as stop-first (worst case) or exclude from expectancy.

7. Position Sizing & Risk Limits

  • Feasibility triage phase: Sizing is fixed at 1 hypothetical unit to audit gross vs. net move distribution.

8. Machine Learning Extension (Walk-Forward Meta-Filter)

  • Baseline: Deterministic cost-gate rule.
  • Model: Train a regularized Logistic Regression classifier on rolling chronological folds.
  • Features (Strictly Trailing): Trailing spread percentile, lagged Spread-to-ATR, rolling ATR percentile, and cyclical hour encodings.
  • Target: Predict whether an eligible bar yields a net positive return under full cost deduction.
  • Validation: Purged rolling walk-forward with an embargo period. All scalers fit on training folds only.

9. Failure Modes & Rejection Criteria

  • M1 Toll Trap: Spread-to-ATR exceeds 20% across all hours. Verdict: Reject M1; step out to M5.
  • Sample Starvation: Cost filters pass, but fewer than 3 trades occur per session. Verdict: Reject timeframe.
  • Rejection Trigger: If median Cost-to-Target exceeds 10% after fees, discard the horizon.

E2 — RTH vs. UTC-Synthetic Clock Atlas

1. Market, Timeframe, and Data Assumptions

  • Evaluates the winning timeframe from E1 (typically M5 or M15).
  • Same dual-lane scope: Commodity ETP on RTH vs. Crypto perp on UTC synthetic.

2. Mechanics vs. Testable Hypothesis

  • Mechanics: Intraday volume and liquidity cluster around specific market openings and session overlaps Pomegra on commodity ETF liquidity.
  • Hypothesis: Restricting trading to high-liquidity hourly tiles brings session-level Cost-to-Target below 10% without dropping total session opportunity below the statistical floor.

3. Required Indicators & Calculations

  • Hourly median ATR, median spread, Spread-to-ATR, and 90th percentile spread spikes.

4. Causal HTF Context & Trigger

  • Prior completed Daily ATR rank or H1 volatility regime determines whether tomorrow’s session is eligible for trading.

5. Execution & Flatten Contract

  • Standard contract: Signal on close of bar t → fill at open of bar t+1. Mandatory forced flatten at session end.

6. Cost Filter & Execution

  • Hourly tiles are flagged as tradable only if their median Spread-to-ATR ≤ 15%.

7. Sizing & Risk

  • Concentrating trades into fewer liquid hours does not justify increasing leverage. Standard risk limits apply.

8. ML Extension (Hourly Tile Classifier)

  • Train a shallow XGBoost or Decision Tree model against a Logistic Regression baseline on chronological folds.
  • Predict hourly profitability regimes using trailing volume, spread z-scores, and prior-day range metrics.

9. Failure Modes & Rejection Criteria

  • Calendar Overfitting: Micro-slicing data into arbitrary 15-minute windows. Fix: Restrict analysis to full 1-hour blocks.
  • Rejection Trigger: Reject the clock if no contiguous 2-hour window clears the 10% Cost-to-Target ceiling.

E3 — Causal HTF-Gated Feasibility (H1/H4 Context)

1. Market, Timeframe, and Data Assumptions

  • Same lanes and execution timeframes as E1 and E2.
  • Adds completed H1 and H4 bars joined strictly at their close timestamp.

2. Mechanics vs. Testable Hypothesis

  • Mechanics: Higher-timeframe bars summarize institutional flow. Filtering against HTF trend reduces counter-trend chop.
  • Hypothesis: Requiring M5 trades to align with completed H1 20 EMA trend direction increases net session expectancy after spreads compared to an ungated baseline.

3. Required Indicators & Calculations

  • Completed H1 20 EMA slope, H1 ADX(14), and prior completed Daily high/low levels.

4. Causal Synchronization

  • An H1 feature stamped at 10:00 is only tradable starting at the 10:05 M5 open. The forming 10:00–11:00 bar is completely invisible.

5. Entry, Stop, and Flatten Rules

  • Same M5 entry rules, filtered by H1 trend permission. Mandatory session flatten remains unchanged.

6. Cost Filter

  • Same per-bar spread ceilings applied inside and outside the HTF gate.

7. Sizing & Risk

  • Standard ATR-based risk sizing. Gated setups do not receive larger capital allocations.

8. ML Extension (Regime Routing)

  • Fit a 2-state Hidden Markov Model (HMM) or GMM on standardized H1 volatility and spread metrics to dynamically route between trend and stand-aside regimes.

9. Failure Modes & Rejection Criteria

  • Opportunity Collapse: HTF gate boosts win rate by 2% but eliminates 80% of trades. Verdict: Reject gate.
  • Rejection Trigger: Reject the HTF gate if gated net return per session fails to exceed the ungated baseline on out-of-sample folds.

Building It In Python

Here is the complete Polars implementation for E1, E2, and E3.

1. Intuition: The E1 Frequency Frontier

We resample M1 data to M5 and M15, compute completed-bar ATR, and calculate feasibility ratios.

# E1: Timeframe feasibility frontier in Polars
# Q: Which timeframe's gross wage comfortably covers its transaction toll?
import polars as pl

def calculate_true_range(frame: pl.DataFrame) -> pl.Series:
    prev_close = frame["close"].shift(1)
    return pl.max_horizontal([
        frame["high"] - frame["low"],
        (frame["high"] - prev_close).abs(),
        (frame["low"] - prev_close).abs(),
    ])

def build_feasibility_frontier(
    m1_frame: pl.DataFrame,
    ceilings: dict = None,
    target_mult: float = 0.9
) -> pl.DataFrame:
    ceilings = ceilings or {"spread_to_atr": 0.15, "cost_to_target": 0.10}
    summary_rows = []

    for tf, every in [("M1", "1m"), ("M5", "5m"), ("M15", "15m")]:
        # Resample completed bars
        resampled = (
            m1_frame.group_by_dynamic("timestamp", every=every)
            .agg([
                pl.first("open").alias("open"),
                pl.max("high").alias("high"),
                pl.min("low").alias("low"),
                pl.last("close").alias("close"),
                pl.mean("spread").alias("spread"),
            ])
            .sort("timestamp")
        )

        # Calculate ATR and Ratios
        resampled = resampled.with_columns(
            calculate_true_range(resampled).ewm_mean(span=14, adjust=False).alias("atr14")
        )
        resampled = resampled.with_columns([
            (pl.col("spread") / pl.col("atr14")).alias("spread_to_atr"),
            (pl.col("spread") / (target_mult * pl.col("atr14"))).alias("cost_to_target"),
        ])

        # Filter by cost ceilings
        passed = resampled.filter(
            (pl.col("spread_to_atr") <= ceilings["spread_to_atr"]) &
            (pl.col("cost_to_target") <= ceilings["cost_to_target"])
        )

        summary_rows.append({
            "timeframe": tf,
            "total_bars": resampled.height,
            "median_atr": resampled["atr14"].median(),
            "median_spread": resampled["spread"].median(),
            "median_s2atr": resampled["spread_to_atr"].median(),
            "median_c2t": resampled["cost_to_target"].median(),
            "passed_bars": passed.height,
            "pass_rate": passed.height / max(1, resampled.height),
        })

    return pl.DataFrame(summary_rows)

What this means for your P&L: This table tells you immediately whether M1 or M5 is mathematically viable. If median_s2atr is 0.22 at M1, you are paying 22% of every bar in friction—meaning M1 should be rejected before you design a single indicator.

2. Intuition: The E2 Hourly Clock Atlas

We map cost and volatility across the 24 hours of the day to locate optimal trading windows.

# E2: Hourly clock atlas
# Q: Which hours offer narrow spreads and tall bars?

def build_clock_atlas(frame: pl.DataFrame) -> pl.DataFrame:
    return (
        frame.with_columns(pl.col("timestamp").dt.hour().alias("hour"))
        .group_by("hour")
        .agg([
            pl.col("spread").median().alias("spread_p50"),
            pl.col("spread").quantile(0.90).alias("spread_p90"),
            pl.col("atr14").median().alias("atr_p50"),
            pl.col("spread_to_atr").median().alias("s2atr_p50"),
            (pl.col("cost_to_target") <= 0.10).mean().alias("pass_rate_target10"),
            pl.len().alias("bar_count"),
        ])
        .sort("hour")
    )

3. Intuition: The E3 Causal HTF Gate Join

We join completed H1 trend features onto M5 bars using a strictly causal as_of join.

# E3: Causal H1 trend gate joined to M5 bars
# Q: Does aligning with completed H1 trend improve net feasibility?

def apply_causal_h1_gate(m5_frame: pl.DataFrame, h1_frame: pl.DataFrame) -> pl.DataFrame:
    # Compute H1 20 EMA on completed bars only
    h1_processed = h1_frame.with_columns([
        pl.col("close").ewm_mean(span=20, adjust=False).alias("ema20_h1")
    ]).with_columns([
        ((pl.col("close") > pl.col("ema20_h1")) &
         (pl.col("ema20_h1") > pl.col("ema20_h1").shift(1))).alias("h1_bullish")
    ])

    # Ensure H1 timestamp reflects bar close (right-labeled)
    # Perform causal as-of join: M5 bar only sees H1 that completed at or before M5 open
    m5_gated = m5_frame.join_asof(
        h1_processed.select(["timestamp", "h1_bullish"]),
        on="timestamp",
        strategy="backward"
    )
    return m5_gated.with_columns(pl.col("h1_bullish").fill_null(False))

4. Intuition: ML Walk-Forward Meta-Filter

We fit a Logistic Regression meta-filter strictly on rolling training windows.

# Walk-forward ML meta-filter
# Q: Can a model learn which cost-passing bars actually follow through?

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

def evaluate_ml_meta_filter(frame: pl.DataFrame, feature_cols: list, target_col: str = "profitable_move"):
    features = frame.select(feature_cols).to_numpy()
    labels = frame[target_col].to_numpy()

    tscv = TimeSeriesSplit(n_splits=5)
    model = Pipeline([
        ("scaler", StandardScaler()),
        ("classifier", LogisticRegression(C=0.1, penalty="l2"))
    ])

    out_of_sample_preds = []

    for train_idx, test_idx in tscv.split(features):
        X_train, y_train = features[train_idx], labels[train_idx]
        X_test, y_test = features[test_idx], labels[test_idx]

        # Fit ONLY on training window
        model.fit(X_train, y_train)
        probs = model.predict_proba(X_test)[:, 1]
        out_of_sample_preds.append((test_idx, probs, y_test))

    return out_of_sample_preds

Testing It Honestly

Honest backtesting enforces the following invariants:

  1. Next-Bar-Open Execution: Signals generated on the close of bar t fill at the open of bar t+1. Filling at the signal close is look-ahead bias Saral on next-bar-open execution.
  2. Spread Deduction: Observed timestamped spreads are deducted from both entry and exit legs.
  3. Worst-Case Ambiguous Resolution: When a single bar touches both stop loss and take profit, book the stop loss. Never assume favorable intrabar pathing ohlcv.io on bar resolution.
  4. Purged Walk-Forward Splits: Test folds are session-aligned and embargoed to eliminate information overlap.
  5. Cost Stress Testing: Evaluate strategies under median spread, p90 spread spikes, and additional fee/slippage stress.

Troubleshooting Diagnostic Table

Symptom Probable Cause Corrective Action
M1 shows high win rate in backtest but collapses in live paper trading Backtest filled at signal-bar close or mid-price Enforce open[t+1] execution and deduct full bid-ask spread
Backtest profitability disappears under p90 spread stress Strategy trades during illiquid spread-spike hours Restrict trading via the E2 Hourly Atlas
HTF gate raises win rate but destroys total P&L Gate eliminates 80% of eligible trades Evaluate strategy by net return per session, not per trade
Machine learning model beats baseline in-sample but fails OOS Feature scaler was fit across the entire dataset Re-fit scalers strictly within each walk-forward training fold

Variants & Extensions

Variant Modification Intended Benefit Key Metric to Report
Target Ladder ($0.7\times$ to $1.5\times$ ATR) Vary profit target multiple Identifies the optimal trade duration for the asset Cost-to-Target ratio across targets
p90 Spread Stress Scan Re-run triage using p90 spread surface Audits strategy survival during volatile liquidity droughts Pass rate under p90 spread stress
Prior-Day Range Filter Condition trading on prior D1 ATR percentile Avoids trading during compressed, low-range days Opportunity count by volatility regime
Funding Spike Exclusion Exclude 15 minutes before/after crypto funding Avoids spread widening caused by funding arbitrage Net return with vs. without funding windows

Hands-On Project

Deliverable: docs/research/m7_feasibility_experiments.md and runnable notebook notebooks/m7_feasibility_experiments.ipynb.

Project Card — M7 Feasibility Experiments (v1.0)

Specification Area Project Requirement
Assets & Lanes One commodity ETP (GLD or PDBC) and one crypto perp (BTC or ETH on an approved CEX).
Timeframes Scan M1, M5, and M15; select the dominant timeframe for atlas and HTF gating experiments.
Session Calendars ETP: RTH 09:30–16:00 ET; Crypto: UTC 00:00–23:50 synthetic session (forced flatten at 23:50 UTC).
Execution Model Signal on close of bar t → fill at open of bar t+1; deduct full timestamped spread; worst-case ambiguous resolution.
Cost Ceilings Spread-to-ATR ≤ 15%; Cost-to-Target ≤ 10%; pass rate ≥ 40% of session bars.
Validation Design 5-fold chronological walk-forward with training-only scalers and an untouched holdout dataset.
Decision Triggers Clear verdict required: Pass (carry to M8), Revise (restrict hours/step TF), or Reject (unviable).

Step-by-Step Instructions

  1. Resample clean M1 data to M5 and M15. Compute ATR, spreads, and feasibility ratios for both assets.
  2. Generate the E1 Feasibility Frontier table and record pass/reject verdicts for each timeframe.
  3. Take the surviving timeframe and construct the E2 Hourly Clock Atlas. Identify the top liquid hours.
  4. Implement the E3 causal H1 trend gate using backward as-of joins. Run gated vs. ungated ablations.
  5. Train a walk-forward Logistic Regression meta-filter and measure out-of-sample lift over the baseline.
  6. Write a one-page decision memo summarizing the tradable parameters for each lane.

Key Takeaways

  • Frequency triage is an accounting check: Spread-to-ATR and Cost-to-Target determine viability before alpha logic is tested.
  • Hourly atlases prevent trading illiquid traps: Spreads and volatility vary wildly throughout the day. Restrict trading to liquid session windows.
  • HTF gates must earn their keep: A gate that improves win rate but starves trade frequency can reduce overall session P&L.
  • Enforce causal joins: HTF features must be stamped at bar close and joined backward. Never peek at forming bars.
  • ML must serve as a meta-filter: Train small, regularized models strictly on walk-forward folds to filter deterministic setups.

References


Next: Module 8 — Low-Timeframe Trading Lab (Tick to M15) · Core: Module 7 — Frequency & Feasibility