ClosePressureReversalSignal

Incremental, causal technical analysis documentation

ClosePressureReversalSignal is a late-session cross-sectional reversal signal. It is designed around a specific empirical idea: strong intraday losers can experience price pressure near the close, and that pressure may partially reverse over the final bars.

The primary paper reference is:

The RTTA implementation is not a verbatim replication. It converts the paper's idea into an incremental signal that can run on aggregate bars and optionally be used cross-sectionally through ClosePressureReversalUniverse.

API

rtta.ClosePressureReversalSignal(
    cutoff_after_bars=66,
    entry_start_after_bars=72,
    entry_end_after_bars=75,
    exit_after_bars=77,
    calibration_window=120,
    calibration_quantile=0.80,
    reversal_slope=0.025,
    entry_z=1.0,
    cost_buffer=0.0005,
    max_abs_target_fraction=0.04,
    participation_cap=0.02,
    allow_short_winners=False,
    fillna=False,
    max_loser_z=6.0,
    max_range_z=5.0,
    max_volume_shock=6.0,
)

update(open, high, low, close, volume, vwap=nan, transactions=nan, previous_session_close=nan, normal_dollar_volume=nan, normal_transactions=nan, reset_session=False) consumes one intraday aggregate bar.

The default timing assumes 5-minute bars in a 390-minute US regular session:

If the bar interval changes, the timing parameters should change too.

Session State

At session start the indicator resets:

Longer-lived calibration state remains unless reset() is called:

Core Idea

The paper studies end-of-day reversal in the cross-section. RTTA turns that into a bar-by-bar score:

  1. Measure how far the stock is down or up on the day by the cutoff.
  2. Normalize that move by realized intraday volatility up to the cutoff.
  3. Increase the score when volume and transaction count are unusually high.
  4. Increase the long score when the stock is below session VWAP.
  5. Only emit entries in the configured late-session entry window.
  6. Force exits in the configured exit window.

The default implementation is long-biased because the paper emphasizes reversal from intraday losers. allow_short_winners=True enables a weaker short side for intraday winners.

Step-by-Step Algorithm

For each valid bar:

  1. Increment bar_number.
  2. Establish anchor_log_close from previous_session_close if supplied, or from the first bar close.
  3. Compute close-to-close log return ret1.
  4. Before and including the cutoff bar, accumulate return mean and variance terms.
  5. Compute rest-of-day return:

text rod_return = log(close) - anchor_log_close

  1. At cutoff_after_bars, freeze that value:

text frozen_rod_return = rod_return at cutoff

  1. Estimate intraday volatility from returns up to cutoff:

text intraday_vol = sqrt(var(ret1_to_cutoff) * count)

  1. Convert the frozen return into loser and winner z-scores:

text loser_z = max(0, -frozen_rod_return) / intraday_vol winner_z = max(0, frozen_rod_return) / intraday_vol

  1. Compute activity shocks:

text volume_shock = log(dollar_volume / normal_dollar_volume) transaction_shock = log(transactions / normal_transactions)

  1. Compute session VWAP gap:

    text vwap_gap = close / session_vwap - 1

  2. Compute range z-score from the current high-low range relative to the range EWMA.

  3. Build pressure multipliers:

    text volume_mult = 1 + 0.20 * clamp(volume_shock, -2, 4) tx_mult = 1 + 0.10 * clamp(transaction_shock, -2, 4)

  4. For long reversal pressure, reward losers below VWAP:

    text long_vwap_mult = 1 + 0.50 * clamp((-vwap_gap) / intraday_vol, 0, 3) long_pressure_score = loser_z * volume_mult * tx_mult * long_vwap_mult

  5. If shorting winners is allowed, compute a weaker short pressure score for winners above VWAP:

    text short_vwap_mult = 1 + 0.50 * clamp(vwap_gap / intraday_vol, 0, 3) short_pressure_score = winner_z * volume_mult * tx_mult * short_vwap_mult

  6. Convert pressure into an expected reversal:

    ```text long_prediction = reversal_slope * loser_return * clamp(long_pressure_score / 2, 0, 2)

    short_prediction = -0.50 * reversal_slope * winner_return * clamp(short_pressure_score / 2, 0, 2) ```

  7. Block entries when the move looks too extreme:

    text news_guard = loser_z > max_loser_z or winner_z > max_loser_z or range_z > max_range_z or volume_shock > max_volume_shock

Error Band

When an entry-window prediction is made, it is stored. At or after exit_after_bars, the realized log return from the prediction bar to the current bar is compared to the prediction:

realized_error = abs(realized_return - prediction)

That error is pushed into a rolling quantile. Once there are enough calibration samples, radius is:

radius = max(rolling_error_quantile, cost_buffer)

Before calibration is ready, fallback radius is:

radius = max(2 * cost_buffer, max(0.0005, 1.25 * intraday_vol))

As with the other RTTA moonshot signals, this is conformal-inspired empirical calibration, not a formal paper replication with guaranteed coverage.

Trading Outputs

The score is:

score = prediction / (radius + cost_buffer)

Entries are allowed only when:

The signal is forced flat in the exit window:

signal = 0 if exit_window or news_guard or not entry_window
signal = +1 if prediction > entry_z * (radius + cost_buffer)
signal = -1 if allow_short_winners and prediction < -entry_z * (radius + cost_buffer)

target_fraction is score-scaled and capped by max_abs_target_fraction. max_trade_dollars = participation_cap * normal_dollar_volume.

ClosePressureReversalUniverse

ClosePressureReversalUniverse holds one ClosePressureReversalSignal per symbol and provides a cross-sectional selector:

selected, exits = universe.update(
    indices, open, high, low, close, volume, vwap, transactions, top_fraction
)

For each bar group:

This is the preferred API for the Massive/Polygon example because the paper's effect is cross-sectional.

Outputs

Intended Use

Use this on regular intraday bars with correct session resets. For US equities, 5-minute bars are the intended default. The example script ranks symbols cross-sectionally and uses quotes for delayed fills. The indicator itself does not place orders and does not know about borrow, halt, limit-up/limit-down, or portfolio constraints.