Skip to main content
Back to Research

The Squeeze Signal Survived Five Chronological Tests

12 min read

A corrected, costed event strategy finished positive in five chronological test blocks, with hourly funding settlement and causal execution timing included.

0xArchive Research · Funding Rate Alpha, Part 3 of 3

Part 2 showed that the pooled mean-reversion signal is a regime-mixing artifact. The conditional structure is real, but can we trade it? We test one event-driven squeeze rule through five sequential chronological blocks, with prior-block configuration selection and a one-day purge at every boundary.

  • Event-driven squeeze: enter only when divergence z exceeds a threshold and the observed falling-price quadrant matches the rule. The ten-symbol portfolio produces 44 to 156 trades per scored block.
  • Prior-block config selection: B2 uses a predefined default. Each later block selects its configuration using only earlier scored blocks.
  • Sequential chronological validation: six calendar blocks from May 2023 through July 10, 2026. B1 is signal warm-up history; B2 through B6 are scored in order.
  • Annualized bar Sharpe = 1.16 with a seven-day block-bootstrap 95% CI of [0.05, 2.33]. The separately calculated monthly-resampled Sharpe is 1.21.
  • Costs and funding: 9.5 bps per side on actual position transitions; full hourly funding settlements use oracle-price notional and mark-price return equity.

Five chronological test blocks, each reset to zero

Why event-driven, not continuous

The regime-conditional relationship in Part 2 appears at many adjacent 15-minute bars. Treating each bar as a fresh signal creates repeated turnover.

The squeeze strategy inverts the logic. Instead of trading every time the signal fires, trade only at extremes, when divergence z crosses a threshold inside one of two predefined falling-price quadrants. This cuts the event count to 44 to 156 trades per scored block across the ten-symbol portfolio.

The trade rule

For each 15m bar i, using information through bar i-1:

    SHORT ENTRY:
        z[i-1] > k
        and quadrant[i-1] == OI-down, price-down

    LONG ENTRY:
        z[i-1] < -k
        and quadrant[i-1] == OI-up, price-down

    EXIT:
        abs(z[i-1]) < k_exit

    POSITION SIZING: fixed +1 / -1
    COSTS: 9.5 bps per side × abs(position change)
    FUNDING: full hourly settlement, signed by position

The internal code aliases these quadrants as long_unwind and new_shorts. Those names are economic shorthand, not participant-level identification. OI and price alone do not prove that longs are trapped or shorts are about to cover.

Sequential chronological block validation

We split the sample into six calendar blocks and score them in order:

Block Period Market regime Role
B1 May to October 2023 mixed (+26%) signal warm-up history; not scored and not used to select B2
B2 November 2023 to April 2024 bull (+83%) test; predefined (W=48, k=2.5, k_exit=0.5)
B3 May to October 2024 choppy (+12%) test; selected from B2
B4 November 2024 to April 2025 bull (+33%) test; selected from B2 through B3
B5 May to October 2025 moderate bull (+21%) test; selected from B2 through B4
B6 November 2025 to July 10, 2026 bear (-29%) test; selected from B2 through B5

Block boundaries are calendar-based. The regime labels describe what happened inside each block; they did not choose the splits.

A one-day purge, 96 bars, is removed at every test-block boundary to reduce rolling-window overlap.

Five candidate configurations are evaluated:

(W, k, k_exit) ∈ {
  (48, 2.0, 0.5),
  (48, 2.5, 0.5),
  (48, 3.0, 0.5),
  (48, 2.5, 1.0),
  (72, 2.5, 0.5)
}

B2 uses the predefined default because there is no earlier scored block. Each later block takes the candidate with the best aggregate Sharpe on prior scored blocks only. No configuration is selected on the block it scores.

Venue accounting is part of the strategy

The signal is only half the backtest. The corrected run also enforces:

  1. One cost at each causal position transition. Costs are not charged again on every holding bar.
  2. One funding event per completed hour. Four 15-minute bucket averages reconstruct the full settlement rate, booked at the end of the hour.
  3. Oracle-price funding notional. Normalized funding returns include the oracle_price / mark_price ratio.
  4. One signal lag, not two. A position formed from bar i-1 earns bar i return and funding.
  5. UTC timestamp alignment across symbols. A symbol with no history at a timestamp contributes zero unavailable exposure to the fixed-universe average.
  6. Completed-period carry rankings. Daily or weekly ranks activate on the next bar, never inside the period that produced them.

These choices are not backtest polish. They determine which cashflows and returns the strategy could have earned.

Per-block performance

Block Applied configuration Sharpe Cumulative PnL Max drawdown Trades
B2 W=48, k=2.5, k_exit=0.5 2.89 +14.1% -5.9% 156
B3 W=48, k=2.5, k_exit=0.5 0.97 +4.1% -5.3% 58
B4 W=48, k=2.5, k_exit=0.5 0.38 +1.7% -6.8% 52
B5 W=48, k=3.0, k_exit=0.5 0.05 +0.2% -4.0% 46
B6 W=48, k=3.0, k_exit=0.5 1.12 +5.0% -7.0% 44

The edge is positive in all five scored blocks. It is not evenly distributed.

B2 accounts for most of the cumulative PnL. B5 is nearly flat. B4 is positive but weak. B6 remains positive through a bearish block. Five blocks is a small sample, and the interval's lower bound of 0.05 is close to zero.

Annualized bar-level Sharpe with seven-day block-bootstrap interval

The bootstrap interval belongs to the annualized 15-minute-bar Sharpe of 1.16. The monthly-resampled Sharpe of 1.21 is a separate statistic.

Why the event is plausible

Part 2 found the strongest negative spread in the OI-down, price-down quadrant. A high positive divergence reading inside that state is therefore a candidate short event, not a generic instruction to fade every high z-score.

The mirror rule tests a low z-score in OI-up, price-down as a long event. One economic story is a short squeeze, but the data does not identify the traders or prove their intent. The strategy stands or falls on the observed rule and its out-of-sample accounting, not the story attached to the quadrant name.

What doesn't work

We ran three carry variants through the same sequential framework:

Strategy Sequential out-of-sample annualized bar Sharpe
Event squeeze 1.16
Cross-sectional carry, daily W48 -1.43
Cross-sectional carry, weekly W48 -1.03
Cross-sectional carry, weekly W168 -0.97

The carry variants collected funding, but adverse mark-price movement outweighed those transfers. The useful result came from event selection, not from collecting funding indiscriminately.

Honest disclosure

  • Five scored blocks is a thin sample. The 95% interval [0.05, 2.33] is wide and nearly touches zero.
  • B2 does most of the work. B4 is weak and B5 is nearly flat.
  • The quadrants are observational proxies. They do not identify initiators, trapped participants, or forced covering.
  • The model does not include market impact, queue position, partial fills, capacity, outages, or live operational controls.
  • The result is historical. It is not a production-performance claim.

Methodology

Component Approach
Signal Dollar-weighted divergence z-score; oracle price for funding notional
Config grid k ∈ {2.0, 2.5, 3.0}, k_exit ∈ {0.5, 1.0}, W ∈ {48, 72}h
Config selection B2 predefined; B3 to B6 selected from prior scored blocks only
Quadrant filter OI-down/price-down for shorts; OI-up/price-down for longs
Exit abs(z) < k_exit
Costs 9.5 bps per side × absolute position change
Funding full hourly settlement; oracle-price notional normalized to mark-price equity
Portfolio UTC timestamp union; fixed-universe denominator; unavailable exposure zero
Validation sequential chronological blocks; one-day purge
Bootstrap 1,000 replicates; seven-day blocks; seed 42
Sample cutoff 2026-07-10T23:59:59.999Z

Reproduce this analysis

The corrected state machine forms position[i] from bar i-1 information. PnL then uses position[i] directly; it is not shifted a second time.

def squeeze_positions(z, quadrant, k=2.5, k_exit=0.5):
    pos = np.zeros(len(z))
    for i in range(1, len(z)):
        z_prev = z[i - 1]
        q_prev = quadrant[i - 1]
        prev = pos[i - 1]
        if prev != 0:
            pos[i] = 0 if abs(z_prev) < k_exit else prev
        elif z_prev > k and q_prev == "long_unwind":
            pos[i] = -1.0
        elif z_prev < -k and q_prev == "new_shorts":
            pos[i] = 1.0
    return pos

Reproduce the analysis with 0xArchive data in the documentation.

Funding Rate Alpha · Part 3 of 3

Part 1: Funding Rates as a Positioning Signal
Part 2: Regime Mixing in Perpetual Futures Signals