Evening Mean-Reversion — the NQ z-score signal

My core edge. Fade short-term price dislocations from a rolling mean, evenings only, exit on time.

Setup

The strategy runs on NQ futures. Order-book depth is resampled to a 1 Hz grid, and for each UTC second the last depth-event midpoint in that second wins. The midpoint is the plain arithmetic mid of best bid and best ask, in pips:

$$ \text{mid}_t = \frac{P^{\text{bid}}_t + P^{\text{ask}}_t}{2} $$

Over a trailing window of $W = 120$ of these 1-Hz mid samples, I track a running sum and sum-of-squares so the rolling statistics are $O(1)$ to update.

The signal

A rolling z-score of the midpoint, measured in sample standard deviation (ddof = 1). This is the key detail: a population std would be wrong here. With the window mean $\bar{m}_t = \tfrac{1}{n}\sum_{i=1}^{n} \text{mid}_{t-W+i}$ and sample variance:

$$ s^2_t = \frac{1}{n-1} \sum_{i=1}^{n} \left(\text{mid}_{t-W+i} - \bar{m}_t\right)^2 $$

the signal is

$$ z_t = \frac{\text{mid}_t - \bar{m}_t}{s_t} $$

Note the window is updated before $z$ is computed, so the value at time $t$ includes $\text{mid}_t$ itself. The z-score is undefined until at least 2 samples are in the window.

Entry rules

Entries require $|z| > 1.5$ using strict inequality, and only during the evening session. Outside the window or below threshold, the position is flat.

$$ \text{signal}_t = \begin{cases} \text{LONG} & \text{if } z_t < -1.5 \\[4pt] \text{SHORT} & \text{if } z_t > +1.5 \\[4pt] \text{FLAT} & \text{otherwise} \end{cases} $$

The direction is fade: a mid stretched far below its recent mean ($z < -1.5$) is bought, one stretched far above ($z > 1.5$) is sold.

Session gate

The evening window is Central Time, $17{:}00$ through $23{:}59$ CT inclusive. Concretely, entries are suppressed unless $17 \le \text{ct\_hour} \le 23$, where the hour is DST-aware (America/Chicago). This captures the re-open after the afternoon break.

Exit and position management

Positions are one-at-a-time and exit on time, not target: a flat 300-second hold, then out regardless. The harness layers daily fences on top — a $+\$500$ cap and a $-\$1000$ stop, after which trading halts for the day.

What is deliberately not here

This is the stripped-down, validated version. I tested spread gates and microprice gates; both hurt, so they're gone. There's also a VPIN toxicity hook in the code, but in production it's disabled (the cap is set above VPIN's $[0,1]$ range so it can never fire) — gating on toxicity removed edge here too.

Why it works (hand-wave)

Evening NQ sessions are thinner and choppier. Short-term dislocations from a 2-minute mean tend to get mean-reverted by the next wave of liquidity, and the time-based exit harvests that snap-back without trying to pick a top. It's a regime bet, not a forecasting model.