The time-series post ended with a rule: every statistic
uses a window that ends today. A technical indicator is what you get when you apply that rule to
prices on purpose, day after day, and keep the result as a new column. Hilpisch calls them
rolling statistics, “often also called financial indicators or financial studies”, and that
plainer name is the right place to start. An indicator is not a prediction and not a strategy. It
is a number that summarises the recent past in a particular way, and the whole craft is in
knowing what each summary keeps, what it throws away, and how late it arrives. This post builds
three of them on SPY’s prices from 2019 to 2023 and asks those questions of each.
Three summaries of the same twenty days
Take the last twenty closing prices. There are many ways to turn them into one number; three
cover most of what traders use.
A simple moving average is the mean of the last n closes, recomputed every day as the window
slides forward by one. It smooths: a single wild day moves the average by only a twentieth of
itself. Hilpisch’s chapter 8 builds it in one line of pandas and notes that the series “only
start at the day for which there is enough data”, so a 100-day average has no value for the first
99 days. An exponentially weighted average does the same job but lets recent days count for
more than old ones, so it turns sooner for the same amount of smoothing.
Momentum over n days is the return over the window: today’s price over the price n days ago,
minus one. It keeps direction and size and throws away the path. The alphas paper puts the
contrast sharply: a momentum signal has the same sign as the return it is built on (what went up
is expected to keep going), and a mean-reversion signal has the opposite sign (what went up is
expected to give some back).
A Bollinger position asks how far today’s price sits from its recent average, measured in
units of how much it has been moving. Romero and Balch describe the band as a range of plus or
minus one standard deviation around the mean; the common convention, and the one the series’
shared code uses, is two. Either way the number is a distance in standard deviations, so a
reading of −1.0 means the same thing in a calm year and a wild one.
b, the Bollinger position: distance from the average in two-standard-deviation unitsP, the observed closing priceσ, the standard deviation of the last 20 closes
bt=2σ20(t)Pt−SMA20(t)
Here is one value by hand, for 2020-03-16, in the middle of the crash. The twenty closes ending
that day ran from 306.18 down to 218.09. Their mean is 272.88 and their standard deviation is
25.62. The close is 54.79 below the mean, and 54.79 divided by twice 25.62 gives
b=−1.07: the price sat just outside the lower band. The same day, the 20-day
momentum was −28.95%, and the price was 20.08% below its 20-day average. Three
numbers, one message: a very large fall over the last month.
An indicator keeps one aspect of the recent past and discards the rest. The average keeps
the level and loses the direction. Momentum keeps the direction and size and loses the path.
The Bollinger position keeps the distance from normal and loses the level entirely.
The lookback is a dial, and both ends are faults
Every indicator has a window, and the window decides how it behaves far more than the formula
does. Two faults sit at the two ends of the dial.
A long window is late. An average of 100 days needs many days of new prices before the old
ones stop dominating it. SPY’s 2020 low was on 2020-03-23. The 20-day average first rose 12
trading days later, on 2020-04-08. The 100-day average first rose on 2020-07-16, 83 trading days
later, by which time the price had recovered most of the fall. Averaging always introduces this
lag, and a shorter window reduces it but does not remove it; even a 5-day average is a few days
behind the price.
A short window is noisy. The price of responsiveness is that the indicator reacts to every
wobble. One way to see it is to count how often a fast average crosses the 100-day average, which
is how often a rule built on the crossover would change its mind:
Fast window
Crossings of the 100-day average, 2019 to 2023
5 days
28
10 days
18
20 days
12
50 days
8
Twenty-eight flips in five years is one every two months, and most of them are the fast average
dipping across and coming straight back. The dial has no free setting: Hilpisch’s chapter 15
searches 121 pairs of windows for the two-average rule, finds the best pair on the data he used,
and immediately warns that the result “is heavily dependent on the data set used and is prone to
overfitting”, recommending that the search be done on one period and tested on another. A window
chosen by looking at what would have worked is a window fitted to the past.
Normalise before you compare
An indicator built on SPY at 400 dollars cannot be compared with one built on a 40-dollar stock
unless the units are the same. A moving average is in dollars, so “price minus its average” is
too, and a 5-dollar gap is a large move for one stock and nothing for the other. The fix is the
same one the time-series post used for returns: divide by something that puts every stock on the
same footing. Price divided by its average, minus one, is a percentage. The Bollinger position
goes one step further and divides by the stock’s own recent volatility, so a reading of one
standard deviation means the same in a quiet stock and a wild one. The alphas paper reaches for
a third tool, rank: replace each stock’s value by its position among all stocks that day, so the
scale disappears altogether. Whichever is used, the point is that an indicator feeds a decision
only after it is on a scale the decision can read, and a model given raw indicators will
otherwise be dominated by whichever has the biggest numbers.
Three indicators, about one piece of information
The three indicators above were all computed from the same twenty closes. How different are they?
Here is the correlation of each pair across the five years:
Item
Price over average, minus one
Bollinger position
20-day momentum
Price over average, minus one
1.00
0.83
0.85
Bollinger position
0.83
1.00
0.68
20-day momentum
0.85
0.68
1.00
Correlations of 0.68 to 0.85 mean that when one of them is high, the others nearly always are
too. That is what you would expect: a price well above its average over the last month is a price
that rose over the last month, and dividing by volatility changes the units more than the
ranking. This is why “three indicators agree” is weaker evidence than it sounds. If they are built
from the same window of the same prices, they are three readings of one thermometer. Confirmation
means something when the indicators look at different things: a short window and a long one, a
price measure and a volume measure, this stock and the market. The Handbook’s survey of what
actually drives cross-sectional returns names short-term reversal, momentum, volatility and size,
which are four different things, not four versions of one.
An indicator is not a strategy
Hilpisch’s phrase for the moving averages in his crossover rule is that they are “only a means to
an end”: the end is a position. Between an indicator and a profit sit three more decisions, each
with its own way of going wrong.
A thresholdthe value at which the indicator means "act"
What it adds a Bollinger position of −1.07 is just a number; "buy below −1" is a rule. The threshold decides how often the rule fires and how extreme a reading it waits for.
Where it goes wrong a threshold tuned on the past fires at the past's extremes. Romero and Balch's event-study example trades a stock near the bottom of its band while the market is near the top of its own; the band positions are the indicator, the two conditions are the rule.
A positionwhat the rule does when it fires, and for how long
What it adds long, short or flat, and when to leave. Hilpisch's crossover goes long when the short average is above the long one and short otherwise, so it is always in the market in one direction.
Where it goes wrong a rule that is always in the market pays every reversal in full; a rule that exits also needs an exit indicator, which has its own lag.
A costwhat each change of position pays
What it adds the 28 flips of the 5-day crossover are 28 round trips. The post What a trade really costs showed a rule that traded almost every day lose nearly everything to costs.
Where it goes wrong indicator quality is judged before costs, strategy profit after them, and the fast settings that make an indicator look responsive are the ones that trade most.
The habit this leaves is to keep the three words apart. An indicator describes. A rule decides. A
strategy is the rule plus its positions plus its costs, tested on data it has not seen. A better
indicator can make a worse strategy if it trades more.
Traps
People think
Actually
A shorter window removes the lag
it shortens the lag; any average is behind the price
Three indicators agreeing is strong confirmation
if they share a window and a price series, they are one reading three times
The best window is the one that backtested best
a window chosen on the past is fitted to the past; choose it on one period, test on another
A Bollinger reading of −2 means “buy”
it means the price is two of its own standard deviations below its average; what to do about that is a separate decision
Indicators in dollars can be compared across stocks
divide by the price, by volatility or by rank first
The code
The snippet reproduces the Bollinger value by hand and the lag after the 2020 low. Variables:
Variable
Holds
px
SPY’s adjusted close, one row per trading day
w
the twenty closes ending on 2020-03-16
sma20
the 20-day average, one value per day once twenty days exist
import pandas as pdurl = "https://raw.githubusercontent.com/ankurtrapasiya/examples/main/data/prices.csv"px = pd.read_csv(url, index_col="date", parse_dates=True)["SPY"]w = px.loc[:"2020-03-16"].iloc[-20:] # the last twenty closes up to that dayb = (w.iloc[-1] - w.mean()) / (2 * w.std()) # distance from the mean, in two-standard-deviation unitsprint(f"mean {w.mean():.2f} sd {w.std():.2f} bollinger {b:+.2f}")sma20 = px.rolling(20).mean() # the same mean, for every daylow = px["2020"].idxmin() # the 2020 lowturn = sma20[low:].diff().gt(0).idxmax() # first day the average rose after itprint(low.date(), "->", turn.date())
It prints a mean of 272.88, a standard deviation of 25.62 and a Bollinger position of −1.07, then
the low on 2020-03-23 and the first rise of the 20-day average on 2020-04-08. .rolling(20) is
the pandas idiom for a window that ends on each row and looks back twenty rows.
The script behind the real numbers
The tables and the chart come from
examples/technical-indicators/indicators.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 technical-indicators/indicators.py.
Summary
Idea
In one line
On SPY, 2019 to 2023
An indicator is a rolling summary
one number per day about the recent past
Bollinger position −1.07 on 2020-03-16
The lookback is a dial
long is late, short is noisy
20-day average turned 12 days after the low, 100-day 83 days after
Normalise before comparing
dollars differ by stock; percentages and standard deviations do not
price 20.08% below its average, 1.07 bands below
Shared inputs mean shared information
indicators from one window agree with each other
pairwise correlation 0.68 to 0.85
Indicator, rule, strategy
describe, decide, then pay for it
5-day crossover flipped 28 times, 50-day 8
References
Yves Hilpisch, Python for Finance, 2nd edition (O’Reilly, 2018). Chapter 8, “Rolling
Statistics”, for rolling statistics as financial indicators, the simple and exponentially
weighted moving averages, and the technical-analysis example with two averages; Chapter 15,
“Simple Moving Averages”, for the crossover rule, the brute-force search over window pairs and
the overfitting warning.
Romero and Balch, What Hedge Funds Really Do (Business Expert Press, 2014). Chapter 11 for
the Bollinger band as a range of one standard deviation around the mean and the event-study
strategy built on it; Chapter 8 for what the weak form of the efficient-market hypothesis says
about rules built from past prices alone.
Kakushadze, 101 Formulaic Alphas (2015). Section 3 for the sign convention that separates
momentum from mean-reversion signals, and the use of cross-sectional rank to remove scale.
CFA Institute Research Foundation, Handbook of Artificial Intelligence and Big Data
Applications in Investments (2023). Chapter 1 for past returns as the momentum feature and the
list of characteristics that dominate cross-sectional returns.