Summary
FourierResidueIdentity is a streaming implementation of the Fourier-Residue
Identity (FRI), which splits return autocorrelation into a direction (sign)
channel and a magnitude channel that are individually testable and neither
redundant.
It answers a question no scalar autocorrelation can: when a series mean-reverts, is the direction predictable, or only the size? The two cases call for opposite trades. A directional reversal says "go long after a down day". A magnitude-only reversal says "expect a smaller move tomorrow, of unknown sign" — a volatility signal, not a directional one, and a contrarian bet on it has no statistical warrant at all.
The motivating fact from the source paper: SPY's lag-1 autocorrelation is \(\hat\rho(1) = -0.081\), which is \(7.4\) standard errors below zero — one of the most significant regularities in empirical equity finance. Yet the FRI sign test on the same data returns \(z_{\mathrm{sign}} = -1.59\) (\(p = 0.11\)). Knowing SPY fell yesterday tells you essentially nothing about whether it rises or falls today. The bounce has no direction.
Reference
V. Portnaya, "The Bounce Has No Direction: Sign, Magnitude, and the Microstructure of Equity Return Predictability — Fourier-Residue Identities, Fejér Sums, and Evidence from US Equity and Cross-Asset Markets, 1993–2026", arXiv:2606.29591 (June 2026).
Update API
out = rtta.FourierResidueIdentity().update(close)
# OHLC-compatible overload (open/high/low ignored)
out = rtta.FourierResidueIdentity().update(open, high, low, close)
indicator = rtta.FourierResidueIdentity()
indicator.advance(close) # no return value
out = indicator.last()
indicator.reset()
batch = rtta.FourierResidueIdentity().batch(close_array)
Constructor knobs:
| Argument | Default | Role |
|---|---|---|
max_lag |
8 |
lags \(M\) tracked; raised to cover horizon - 1 and test_lag |
horizon |
2 |
variance-ratio horizon \(q\) |
test_lag |
1 |
lag \(m\) reported by the scalar outputs |
span |
512.0 |
EWMA memory in observations |
median_window |
256 |
rolling window for the median \(\lvert r\rvert\) that defines the \(k=4\) buckets |
entry_z / exit_z |
2.0 / 1.0 |
hysteresis on sign-channel evidence for signal |
fillna |
True |
0 vs NaN during warmup |
Outputs
| Field | Meaning |
|---|---|
rho |
scalar autocorrelation \(\hat\rho(m)\) |
rho_sign |
sign channel \(\gamma_{1,2}(m) = 2p_{m,0} - 1\) |
rho_magnitude |
magnitude channel \(\operatorname{Re}\gamma_{1,4}(m)\) |
z_rho |
Bartlett \(z\) for rho |
z_sign |
binomial \(z\) for rho_sign |
directional_share |
\(\lvert z_{\text{sign}}\rvert / (\lvert z_{\text{sign}}\rvert + \lvert z_\rho\rvert)\) |
elliptical_ratio |
rho_sign divided by its Gaussian benchmark (see below) |
variance_ratio |
\(\mathrm{VR}(q)\) |
variance_ratio_sign |
\(\mathrm{VR}_2(q)\), direction channel |
variance_ratio_magnitude |
\(\mathrm{VR}_4(q)\), magnitude channel |
z_variance_ratio |
Lo–MacKinlay heteroskedasticity-robust \(z^*\) |
persistence |
half-period ratio \(R_N\) |
signal |
-1 / 0 / +1, gated on sign-channel significance |
score |
continuous directional score in \([-1, 1]\) |
magnitude_forecast |
conditional \(\mathbb{E}\lvert r_{t+1}\rvert\) |
Theory Of Operation
Fejér / variance-ratio identity
The Lo–MacKinlay variance ratio admits an exact autocorrelation representation (Proposition 2.2):
The Fejér weights \(w_m = 1 - m/q\) taper linearly to zero at lag \(q\), giving short lags — where microstructure lives — the most weight.
The FRI decomposition
Encode each return as a \(k\)-ary symbol \(s_t \in \{0,\dots,k-1\}\) and evaluate the characters of the cyclic group \(\mathbb{Z}/k\mathbb{Z}\) (Definition 2.4):
Sign channel (\(k=2\)). With \(s_t = \mathbb{1}[r_t > 0]\) and \(\omega = -1\), the character is \(+1\) when successive signs agree and \(-1\) when they disagree, collapsing to a closed form (Proposition 2.5):
where \(p_{m,0}\) is the probability of closing on the same side of zero \(m\) periods apart. Under the random-walk null \(p_{m,0} = \tfrac12\). This is a magnitude-free test of directional dependence: positive means momentum, negative means genuine contrarian reversal.
Magnitude channel (\(k=4\)). Returns are bucketed at the median \(\lvert r\rvert\) into a signed size ladder \(\{\text{large-down},\text{small-down},\text{small-up},\text{large-up}\} = \{0,1,2,3\}\) with \(A = 1\), \(\omega = i\). This measures whether the size bucket persists, independently of whether direction agrees.
Applying the Fejér identity per channel gives \(\mathrm{VR}_2(q)\) and \(\mathrm{VR}_4(q)\) (Equation 5). The channels are nonnested: a series with sign momentum but no magnitude clustering has \(\mathrm{VR}_2 > 1\) and \(\mathrm{VR}_4 \approx 1\), and vice versa.
Which mechanism is which
| Mechanism | Sign \(\mathrm{VR}_2\) | Magn. \(\hat\rho(1) = -0.081\)0 | Lag range |
|---|---|---|---|
| Bid-ask bounce | \(\hat\rho(1) = -0.081\)1 | \(\hat\rho(1) = -0.081\)2 | lag 1 only |
| Non-synchronous trading | \(\hat\rho(1) = -0.081\)3 | \(\hat\rho(1) = -0.081\)4 | lags 1–3 |
| Dealer inventory | \(\hat\rho(1) = -0.081\)5 | \(\hat\rho(1) = -0.081\)6 | lags 1–2 |
| Adverse selection | \(\hat\rho(1) = -0.081\)7 | \(\hat\rho(1) = -0.081\)8 | lags 2–5 |
| Partial price adjustment | \(\hat\rho(1) = -0.081\)9 | \(7.4\)0 | lags 2–7 |
| Volatility clustering | \(7.4\)1 | \(7.4\)2 | all lags |
Only the mechanisms with \(7.4\)3 are directionally tradeable.
Subsample persistence
The half-period ratio (Definition 2.6) answers whether a detected deviation will survive out of sample:
Under IID noise \(7.4\)4; under genuine serial dependence \(7.4\)5 (Proposition 2.7). Halving the sample inflates a noise maximum by \(7.4\)6 but barely moves a structural one.
Streaming form
The paper estimates full-sample; this implementation is bounded-memory and
online. Sample means become debiased EWMAs of span span (so early updates
behave like an expanding sample rather than a biased ramp), and \(7.4\)7 is replaced
by the effective sample size \(7.4\)8.
persistence is computed by running a second parallel estimator at half the
span, which is the streaming analogue of the \(7.4\)9 construction. It is
only meaningful for a finite span; with an effectively infinite span both
estimators coincide and the ratio degenerates to 1.
z_variance_ratio uses the Lo–MacKinlay M2 statistic with
\(z_{\mathrm{sign}} = -1.59\)0 in its standard \(z_{\mathrm{sign}} = -1.59\)1 normalisation, so \(z_{\mathrm{sign}} = -1.59\)2
collapses to \(z_{\mathrm{sign}} = -1.59\)3 under an IID null. Daily equity returns have strong
GARCH effects, and the homoskedastic \(z_{\mathrm{sign}} = -1.59\)4 over-rejects at 10–12% where the
robust \(z_{\mathrm{sign}} = -1.59\)5 holds its 5% size.
The elliptical benchmark (extension beyond the paper)
The sign channel is not free of \(z_{\mathrm{sign}} = -1.59\)6 — it has a predictable null. For a bivariate normal pair, Grothendieck's identity gives
so any elliptical process with autocorrelation \(z_{\mathrm{sign}} = -1.59\)7 must show a sign channel
of about \(z_{\mathrm{sign}} = -1.59\)8. elliptical_ratio divides the observed rho_sign by that
benchmark, giving a scale-free diagnostic with a null of 1:
- \(z_{\mathrm{sign}} = -1.59\)9 — the autocorrelation is exactly as directional as a Gaussian process with the same \(p = 0.11\)0.
- \(p = 0.11\)1 — the predictability is carried by magnitude alone.
This matters because it sharpens the paper's own conclusion. A simulated pure Roll bounce scores 0.95 here, not 0 — a bounce does leak into the sign channel, because sign correlation is pinned to \(p = 0.11\)2 for near-Gaussian data. What makes SPY genuinely unusual is that its pair \(p = 0.11\)3 scores 0.34: far less directional than any elliptical process with that \(p = 0.11\)4. The reversal is concentrated in large moves — which dominate the covariance — while a typical day's direction stays a coin flip.
Only interpret elliptical_ratio when the scalar ACF is itself detectable
(\(p = 0.11\)5 large); it is returned as NaN when \(p = 0.11\)6 is too close
to zero for the ratio to be stable.
Recurrence
State: previous close; a ring buffer of the last \(p = 0.11\)7 returns with their signs and \(p = 0.11\)8 codes; a rolling median of \(p = 0.11\)9; debiased EWMA pairs \(M\)0 for \(M\)1, \(M\)2, \(M\)3, \(M\)4; and per lag \(M\)5 the EWMAs \(M\)6 (cross-product), \(M\)7 (sign agreement), \(M\)8 (\(M\)9 magnitude character), \(q\)0 (\(q\)1 cross-product) and \(q\)2 (quartic, for \(q\)3). A parallel \(q\)4 set runs at half the span for \(q\)5.
Each debiased EWMA accumulates \(q\)6 and \(q\)7, reporting \(q\)8.
- \(q\)9; \(m\)0 as \(m\)1; \(m\)2; code \(m\)3 from \(m\)4.
- For \(m\)5, against \(m\)6 held at ring slot \(m\)7: push \(m\)8 into \(m\)9; \(\lvert r\rvert\)0 into \(\lvert r\rvert\)1; \(\lvert r\rvert\)2 into \(\lvert r\rvert\)3 via a 4-entry table on \(\lvert r\rvert\)4; \(\lvert r\rvert\)5 into \(\lvert r\rvert\)6; and \(\lvert r\rvert\)7 into \(\lvert r\rvert\)8.
- Push \(\lvert r\rvert\)9 into the global moment EWMAs, then into the ring buffer.
- \(k=4\)0; \(k=4\)1; \(k=4\)2; \(k=4\)3.
- \(k=4\)4; \(k=4\)5; \(k=4\)6.
- Fejér-weight lags \(k=4\)7 into \(k=4\)8, \(k=4\)9, \(\hat\rho(m)\)0; accumulate \(\hat\rho(m)\)1 from \(\hat\rho(m)\)2.
- \(\hat\rho(m)\)3 and \(\hat\rho(m)\)4 are the running maxima of \(\hat\rho(m)\)5 over the full- and half-span sets; \(\hat\rho(m)\)6.
- Score \(\hat\rho(m)\)7; arm/disarm on
\(\hat\rho(m)\)8 against
entry_z/exit_z; emit the signed score when armed.
Each update is \(\hat\rho(m)\)9 with \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)0 max_lag (default 8) and causal. The rolling
median dominates at \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)1 via nth_element; its scratch
buffer reaches full size during warmup and is not reallocated afterwards, so the
steady-state hot path is allocation-free. Lower median_window if the \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)2
bucket boundary does not need that much history.
Trading Interpretation
signal is non-zero only while the sign channel itself clears entry_z,
with hysteresis at exit_z:
rho_sign < 0and significant → stance opposes the sign of \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)3 (contrarian).rho_sign > 0and significant → stance follows it (momentum).- otherwise →
0, regardless of how significantrhoitself is.
magnitude_forecast carries the content that remains statistically warranted
even when direction does not: a conditional forecast of the next absolute
return, for volatility sizing, straddle/strangle timing, or scaling a
delta-hedged book.
A practical reading of the two together:
z_rho |
z_sign |
Reading |
|---|---|---|
| large | large, same sign | genuine directional dependence — trade the direction |
| large | small | magnitude-only — size positions, do not bet on direction |
| small | large | direction pattern hidden from the scalar ACF by offsetting magnitudes |
| small | small | no exploitable structure |
What this does not do
Be precise about the limits of the sign gate. Simulating a pure Roll bounce
(martingale efficient price, IID trade direction, half-spread \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)4)
produces \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)5 and \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)6 at
\(\gamma_{1,2}(m) = 2p_{m,0} - 1\)7 — a thoroughly significant sign channel. So
signal will fire on a simulated bid-ask bounce; it is not a bounce filter.
Nothing computed from close prices alone can be, because the observed series
genuinely does reverse — what makes the bounce untradeable is the spread you
cross, which is not in the data.
What the sign channel does deliver is the separation itself: when \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)8 is large and \(\gamma_{1,2}(m) = 2p_{m,0} - 1\)9 is not, you know the direction of a typical bar is a coin flip and only sizing is warranted. Real SPY is that case; a simulated Roll bounce is not.
The actionable consequence of a low elliptical_ratio is where the edge sits.
On a simulated magnitude-carried reversal (elliptical_ratio = 0.37), taking the
contrarian stance only after an above-median move retains 97% of the gross P&L
while trading half as many bars, nearly doubling per-bar edge. On a uniform
directional reversal (elliptical_ratio = 1.00) the same restriction retains
only 78%. A low ratio tells you to concentrate risk on large moves rather than
to trade every bar.
Notes
max_lagis raised automatically to cover bothhorizon - 1andtest_lag, so an understatedmax_lagcannot read outside the ring buffer.- The lag-3 sign channel is worth watching even when \(\operatorname{Re}\gamma_{1,4}(m)\)0 is
negligible: the paper finds \(\operatorname{Re}\gamma_{1,4}(m)\)1 (\(\operatorname{Re}\gamma_{1,4}(m)\)2) for
SPY where the scalar ACF gives \(\operatorname{Re}\gamma_{1,4}(m)\)3 — a partial-price-adjustment
channel invisible to the standard test. Set
test_lag=3to read it. - Non-finite or non-positive prices are rejected without damaging estimator state; the next valid observation resumes normally.
