Alphas: a formula, a universe and a test of whether it worked

Quantitative Finance Series 2

On this page
  1. What an alpha looks like
  2. Two of the 101, from closes only
  3. Time-series versus cross-sectional
  4. The test: correlation with what came next
  5. Why 101 of them, and not one
  6. Traps
  7. The code
  8. Summary
  9. References

The indicators post ended with a warning: an indicator is a number, not a trading rule, until a threshold and a position are added. Quantitative trading has a word for the step in between. An alpha is a formula that takes today’s data and produces a forecast of future return, one number per stock per day, before anyone decides what to hold. In 2015 Zura Kakushadze published 101 of them, proprietary to WorldQuant and 80 of them in production at the time, as formulas that are also code, with the holding periods and correlations that go with them. This post reads that paper, computes two of its simplest alphas on the blog’s five tickers (SPY, AAPL, JPM, XOM, GLD, 2019 to 2023), and then does the thing the paper’s abstract keeps pointing at: tests whether they worked.

What an alpha looks like

The paper’s footnote 4 gives the trader’s definition: “a combination of mathematical expressions, computer source code, and configuration parameters that can be used, in combination with historical data, to make predictions about future movements of various financial instruments”. The word means expected return, roughly, not the academic alpha of the beta post, and the paper is explicit that the two are not the same.

Section 2 says what the 101 are made of. Almost all are price-volume: daily close-to-close returns, open, close, high, low, volume and the volume-weighted average price. A few use market cap or an industry classification. And “very coarsely”, the list is built from two opposite ideas. A mean-reversion alpha has the opposite sign to the return it is based on: the simplest example in the paper is minus the log of today’s open over yesterday’s close, the hope being that a stock that gapped up overnight gives some of it back. A momentum alpha has the same sign: the log of yesterday’s close over yesterday’s open, the hope being that a stock that ran up during the day keeps going. Complex alphas mix the two.

The paper also attaches a timing label to each formula. A delay-0 alpha is traded at the time of the latest data it uses (the open-over-close example is meant to be traded at today’s open). A delay-1 alpha is traded the day after its latest data; most of the 101 are delay-1. That label is not decoration. It says whether the forecast could actually have been acted on.

Section 3 reports what the 101 did in production between January 2010 and December 2013. Their average holding period runs from 0.6 to 6.4 days. Their average pairwise correlation is 15.9%\key{15.9\%} (median 14.3%): a hundred formulas, mostly saying different things. Their returns rise with their volatility and do not depend on how often they trade. And the introduction’s first paragraph names the two trends the whole list sits inside: more people mining alphas produces “ever fainter and more ephemeral alphas”, while automation produces ever more of them, so that what is actually traded is a combination of hundreds of thousands, a “mega-alpha”, not any single formula.

An alpha is a forecast, and a forecast is a tendency. It is wrong on many individual days and is only judged over a sample, at its own horizon. The question is never “did it work yesterday” but “over a thousand days, did the ones it liked do better than the ones it did not”.

Two of the 101, from closes only

The blog’s data has closes and nothing else, which rules out most of the list. Two of the simplest survive. Alpha#9 in Appendix A reads, in the paper’s notation, (0 < ts_min(delta(close, 1), 5)) ? delta(close, 1) : ((ts_max(delta(close, 1), 5) < 0) ? delta(close, 1) : (-1 * delta(close, 1))), where delta(close, 1) is today’s close minus yesterday’s and ts_min(x, 5) is the smallest value of x over the past five days. In words:

s, the signald, today's close minus yesterday's

st={dtif the close rose on each of the last five daysdtif it fell on each of the last five days−dtotherwise\key{s_t} = \begin{cases} \good{d_t} & \text{if the close rose on each of the last five days} \\[4pt] \good{d_t} & \text{if it fell on each of the last five days} \\[4pt] -\good{d_t} & \text{otherwise} \end{cases}

Five rises in a row, or five falls: keep today’s move, a momentum bet. Anything mixed: flip today’s move, a mean-reversion bet. Alpha#10 is the same expression with a four-day window, wrapped in rank(...), the paper’s cross-sectional rank: each day, line up every stock in the universe by its signal and replace the value with its position in the line.

Here is one day, 2022-06-17, for the inside of Alpha#10 and its rank:

TickerCloseToday’s move4-day min4-day maxSignalRank
SPY345.95+0.74−11.82+5.02−0.740.4
AAPL128.86+1.47−5.26+2.62−1.470.2
JPM101.18−0.36−1.77+1.21+0.360.6
XOM74.82−4.58−4.58+0.22+4.581.0
GLD171.27−1.42−1.42+2.20+1.420.8

No ticker had four one-way days, so every row is in the “otherwise” branch and the signal is minus today’s move. XOM fell $4.58, so the alpha likes it most (it expects a bounce); AAPL rose $1.47, so the alpha likes it least. That is the usual day. Over the five years, the five-rises branch fires on about 4% of days per ticker and the five-falls branch on 1 to 3%, so Alpha#9 is a one-day reversal bet on more than 92% of days, with a momentum exception for runs.

Time-series versus cross-sectional

The two alphas differ in what their number means, and that decides how each is tested.

Alpha#9 is a time-series signal. Its value is in dollars per share and compares a stock with its own history: +4.58 for XOM says “bounce expected”, and it would say so whether or not any other stock existed. Every stock in the universe can get a positive signal on the same day, and holding all of them means being long the market.

Alpha#10 is a cross-sectional signal. The rank compares stocks with each other on one day, and the units are gone: XOM’s +4.58 becomes 1.0 and GLD’s +1.42 becomes 0.8, the same gap as between GLD and JPM’s +0.36 even though the dollar gaps are $3.16 and $1.06. A rank keeps the order and throws away the spacing. Its scale does not depend on AAPL trading at $129 and GLD at $171, as the raw signal’s does, and it is immune to one wild value. The forecast it makes is relative: “XOM will do better than AAPL”, not “XOM will go up”. A stock can have a positive raw signal and still rank at the bottom.

A ranked alpha forecasts order, not size. The top of the list is expected to beat the bottom. Whether either goes up is a separate question the rank does not answer.

The test: correlation with what came next

The paper gives the formulas and the production statistics. It does not, and cannot, say whether Alpha#9 works on five tickers in 2019 to 2023. The standard test is the information coefficient, which Romero and Balch define as the correlation between a manager’s predictions and the actual future prices: 1 for a perfect forecaster, −1 for a perfectly wrong one.

IC, the information coefficient; s, the signalr, the return over the next h days

IC(h)=corr⁡(st,  rt→t+h)\key{\mathrm{IC}(h)} = \operatorname{corr}\big(\key{s_t},\; \good{r_{t \to t+h}}\big)

The correlation here is the rank kind: whether the days (or stocks) the signal liked more tended to return more, regardless of units. For Alpha#9 the pairing is each ticker against its own future, five correlations averaged. For Alpha#10 it is the five tickers against each other on each day, 1,257 daily correlations averaged. The return runs from today’s close to the close h days later, which treats both alphas as if they were traded at today’s close.

Days aheadAlpha#9, each ticker against its own futureAlpha#10, ranked across the five
10.0120.008
20.0230.019
30.0120.004
50.008−0.005
100.0180.009
200.010−0.005

Every number is a few hundredths from zero. The sign is right (positive) at short horizons, and the ranked version is lower and crosses zero by five days. That is the shape of a faint signal: not nothing, not much. The second question is whether even that held up:

YearAlpha#9, 1 dayAlpha#10, 1 day
20190.0240.022
20200.0970.027
2021−0.0030.021
2022−0.030−0.015
2023−0.017−0.016

The five-year 0.012\key{0.012} is 0.046\key{0.046} over 2019 to 2021 and −0.028\key{-0.028} over 2022 to 2023. One year, 2020, carried it, which is the year of the sharpest one-day moves in the sample and the year a “flip today’s move” bet pays best. The last two years went the other way. A signal with these numbers was never tradeable on five tickers, and the paper would not have expected it to be: its own example of a universe is a dollar-neutral portfolio of the 2,000 most liquid US stocks, where a tiny edge per name adds up across thousands of names and days. Five names do not give a tiny edge room to add up.

{"panels":[{"x0":65.96,"x1":565.2,"y0":29.16,"y1":223.44,"title":"1. Correlation with the return h days out: small at every horizon","xlabel":"Days ahead the return is measured over","ylabel":"Rank correlation with that return (IC)","xlog":false},{"x0":65.96,"x1":565.2,"y0":312.12,"y1":506.4,"title":"2. Did it hold? The 1-day IC, one year at a time","xlabel":"Year","ylabel":"Rank correlation with next day's return (IC)","xlog":false}],"series":[{"panel":0,"label":"Alpha#9, each ticker against its own future","color":"var(--chart-maroon, #b6262c)","x":[1.0,2.0,3.0,5.0,10.0,20.0],"y":[0.0121287,0.0228396,0.0120548,0.00762165,0.0180308,0.00974937],"sx":[88.65,112.54,136.43,184.2,303.64,542.51],"sy":[106.71,37.99,107.18,135.63,68.84,121.97]},{"panel":0,"label":"Alpha#10, ranked across the five each day","color":"var(--chart-blue, #3b6ea5)","x":[1.0,2.0,3.0,5.0,10.0,20.0],"y":[0.0080414,0.0187251,0.00430622,-0.00455272,0.00914194,-0.00468876],"sx":[88.65,112.54,136.43,184.2,303.64,542.51],"sy":[132.93,64.39,156.9,213.73,125.87,214.61]}]} 2026-10-29T02:18:01.006469 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/ 1 2 3 5 10 20 Days ahead the return is measured over −0.005 0.000 0.005 0.010 0.015 0.020 Rank correlation with that return (IC) 1. Correlation with the return h days out: small at every horizon Alpha#9, each ticker against its own future Alpha#10, ranked across the five each day 2019 2020 2021 2022 2023 Year −0.02 0.00 0.02 0.04 0.06 0.08 0.10 Rank correlation with next day's return (IC) 2. Did it hold? The 1-day IC, one year at a time Alpha#9, 1-day horizon Alpha#10, 1-day horizon

Test at the signal’s own horizon, then test again on time it has not seen. An alpha with a positive five-year number and a negative last-two-years number is a finished alpha, not a working one.

Why 101 of them, and not one

The two alphas here share their inputs, and it shows. The rank correlation between Alpha#9’s signal and the inside of Alpha#10 (the same rule with a four-day window) is 0.874\key{0.874}, and between Alpha#9 and plain “minus today’s move” it is 0.896\key{0.896}. Three formulas, one idea. Combining them adds almost nothing, because when one is wrong the others are wrong on the same day.

That is the point of the paper’s 15.9%. A hundred alphas with an average pairwise correlation of 0.16 are, roughly, a hundred different bets, and a combination of different bets is where the “mega-alpha” gets its edge and its smoother returns: when some go bust in a period, the others do not go with them. The introduction adds the practical bonus, that trading a combination lets opposite orders cancel inside the book before they reach the market, saving on costs. A hundred variants of one-day reversal would give none of this. The question to ask of a new alpha is not “is it good” but “what does it add to the ones I have”, and a correlation of 0.87 answers it.

Traps

People thinkActually
A ranked score says how much a stock will returnit says which stocks are expected to beat which; order only
A positive forecast means buyin a ranked signal a positive value can still sit at the bottom of the list
An alpha that is positive over five years workssplit the years; 0.046 then −0.028 is a signal that stopped
A tiny IC is uselesson 2,000 names, a tiny edge per name per day is the business; on five names it is noise
Two formulas are two signalsif they share inputs they share mistakes; 0.87 correlation is one signal
Delay-0 and delay-1 are detailsthey say whether the forecast could have been traded at all

The code

The snippet reproduces Alpha#9 and its 1-day information coefficient, for the five years and for the two halves. Variables:

VariableHolds
d1today’s close minus yesterday’s, per ticker (delta(close, 1) in the paper)
alpha9today’s move if the last five days all rose or all fell, minus it otherwise
fwd1the return from today’s close to tomorrow’s
ic(years)the rank correlation of the signal with that return, per ticker, averaged
import pandas as pd

url = "https://raw.githubusercontent.com/ankurtrapasiya/examples/main/data/prices.csv"
px = pd.read_csv(url, index_col="date", parse_dates=True)
d1 = px.diff()                                   # delta(close, 1): today's close minus yesterday's
lo, hi = d1.rolling(5).min(), d1.rolling(5).max()
alpha9 = d1.where(lo > 0, d1.where(hi < 0, -d1)) # five rises or five falls: keep the move; else flip it
fwd1 = px.shift(-1) / px - 1                     # the return from today's close to tomorrow's

def ic(years):
    out = []
    for t in px.columns:                         # each ticker against its own future, then average
        both = pd.concat([alpha9[t], fwd1[t]], axis=1).dropna()
        both = both[both.index.year.isin(years)]
        out.append(both.corr(method="spearman").iloc[0, 1])
    return sum(out) / len(out)

print("IC 2019-2023", round(ic(range(2019, 2024)), 3))
print("IC 2019-2021", round(ic([2019, 2020, 2021]), 3))
print("IC 2022-2023", round(ic([2022, 2023]), 3))

It prints 0.012, 0.046 and −0.028. d1.where(lo > 0, other) keeps d1 where the condition holds and takes other elsewhere; nested twice, it is the paper’s ? : written in pandas.

The script behind the real numbers

The tables and the chart come from examples/alphas-formula-universe-test/alphas.py, which runs on the blog’s five tickers. To run it yourself, clone the examples repo: its README has a three-command Docker setup, or run uv run alphas-formula-universe-test/alphas.py.

Summary

IdeaIn one lineOn the five tickers
Alphaa formula from today’s data to a forecast of return, one number per stock per dayAlpha#9: flip today’s move unless it is a five-day run
Mean reversion versus momentumopposite sign to the past move, or the same signthe reversal branch fires on over 92% of days
Time-series versus cross-sectionala stock against its own history, or against its peers todaydollars for Alpha#9, a rank of 0.2 to 1.0 for Alpha#10
Rankkeeps order, drops spacing and unitsXOM +4.58 and GLD +1.42 become 1.0 and 0.8
Information coefficientcorrelation of the forecast with what came next0.012 at one day
Test on unseen timethe number that matters is the one after the fit0.046 then −0.028
Redundancysignals that share inputs share mistakes0.87 between the two alphas; 0.16 across the paper’s 101

References

  • Zura Kakushadze, 101 Formulaic Alphas (Quantigic Solutions and Free University of Tbilisi, December 2015). Footnote 4 for the definition of an alpha; section 1 for “ever fainter and more ephemeral alphas” and the mega-alpha; section 2 for the price-volume inputs, the mean-reversion and momentum examples (equations 2 and 3) and the delay-0 / delay-1 labels; section 3 and the abstract for the 2010 to 2013 holding periods (0.6 to 6.4 days), the average pairwise correlation of 15.9% and the return-volatility link; section 4 for the 80 alphas in production and the example universe of the 2,000 most liquid US stocks; Appendix A for Alpha#9, Alpha#10 and the definitions of rank, delta, ts_min and ts_max.
  • Philip J. Romero and Tucker Balch, What Hedge Funds Really Do (Business Expert Press, 2014). Chapter 9, “A Few Definitions”, for the information coefficient as the correlation between predictions and actual future prices.