Indicators: a price pattern turned into a number

Quantitative Finance Series 2

On this page
  1. Three summaries of the same twenty days
  2. The lookback is a dial, and both ends are faults
  3. Normalise before you compare
  4. Three indicators, about one piece of information
  5. An indicator is not a strategy
  6. Traps
  7. The code
  8. Summary
  9. References

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\key{-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=Pt−SMA20(t)2 σ20(t)\key{b_t} = \frac{\good{P_t} - \mathrm{SMA}_{20}(t)}{2\,\risk{\sigma_{20}(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\risk{25.62}. The close is 54.79 below the mean, and 54.79 divided by twice 25.62 gives b=−1.07\key{b} = \key{-1.07}: the price sat just outside the lower band. The same day, the 20-day momentum was −28.95%\key{-28.95\%}, and the price was 20.08%\key{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 windowCrossings of the 100-day average, 2019 to 2023
5 days28
10 days18
20 days12
50 days8

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.

{"panels":[{"x0":49.4,"x1":564.49,"y0":29.16,"y1":223.28,"title":"1. The low, then the day each average turned up (dashed)","xlabel":"Date (2020)","ylabel":"Price (dollars)","xlog":false},{"x0":49.4,"x1":564.49,"y0":311.96,"y1":506.08,"title":"2. Two indicators built from the same 20 days move together","xlabel":"Date (2020)","ylabel":"Indicator value","xlog":false}],"series":[{"panel":0,"label":"SPY close","color":"var(--chart-maroon, #b6262c)","x":["2020-01-02","2020-01-03","2020-01-06","2020-01-07","2020-01-08","2020-01-09","2020-01-10","2020-01-13","2020-01-14","2020-01-15","2020-01-16","2020-01-17","2020-01-21","2020-01-22","2020-01-23","2020-01-24","2020-01-27","2020-01-28","2020-01-29","2020-01-30","2020-01-31","2020-02-03","2020-02-04","2020-02-05","2020-02-06","2020-02-07","2020-02-10","2020-02-11","2020-02-12","2020-02-13","2020-02-14","2020-02-18","2020-02-19","2020-02-20","2020-02-21","2020-02-24","2020-02-25","2020-02-26","2020-02-27","2020-02-28","2020-03-02","2020-03-03","2020-03-04","2020-03-05","2020-03-06","2020-03-09","2020-03-10","2020-03-11","2020-03-12","2020-03-13","2020-03-16","2020-03-17","2020-03-18","2020-03-19","2020-03-20","2020-03-23","2020-03-24","2020-03-25","2020-03-26","2020-03-27","2020-03-30","2020-03-31","2020-04-01","2020-04-02","2020-04-03","2020-04-06","2020-04-07","2020-04-08","2020-04-09","2020-04-13","2020-04-14","2020-04-15","2020-04-16","2020-04-17","2020-04-20","2020-04-21","2020-04-22","2020-04-23","2020-04-24","2020-04-27","2020-04-28","2020-04-29","2020-04-30","2020-05-01","2020-05-04","2020-05-05","2020-05-06","2020-05-07","2020-05-08","2020-05-11","2020-05-12","2020-05-13","2020-05-14","2020-05-15","2020-05-18","2020-05-19","2020-05-20","2020-05-21","2020-05-22","2020-05-26","2020-05-27","2020-05-28","2020-05-29","2020-06-01","2020-06-02","2020-06-03","2020-06-04","2020-06-05","2020-06-08","2020-06-09","2020-06-10","2020-06-11","2020-06-12","2020-06-15","2020-06-16","2020-06-17","2020-06-18","2020-06-19","2020-06-22","2020-06-23","2020-06-24","2020-06-25","2020-06-26","2020-06-29","2020-06-30","2020-07-01","2020-07-02","2020-07-06","2020-07-07","2020-07-08","2020-07-09","2020-07-10","2020-07-13","2020-07-14","2020-07-15","2020-07-16","2020-07-17","2020-07-20","2020-07-21","2020-07-22","2020-07-23","2020-07-24","2020-07-27","2020-07-28","2020-07-29","2020-07-30","2020-07-31"],"y":[295.392,293.155,294.273,293.446,295.01,297.01,296.156,298.192,297.738,298.411,300.893,301.829,301.238,301.275,301.62,298.938,294.146,297.228,296.983,297.947,292.537,294.71,299.202,302.657,303.675,302.057,304.312,304.839,306.803,306.476,306.967,306.176,307.639,306.376,303.221,293.164,284.281,283.235,270.514,269.378,281.044,272.997,284.471,275.015,270.469,249.347,262.249,249.465,225.597,244.882,218.086,229.861,218.223,218.686,209.262,203.912,222.387,225.716,238.896,231.78,239.307,235.74,225.131,230.326,226.997,242.243,242.49,250.63,254.444,252.121,259.557,254.042,255.267,262.163,257.545,249.725,255.267,255.249,258.807,262.538,261.331,268.172,265.675,258.642,259.355,261.752,259.977,263.115,267.468,267.523,262.191,257.554,260.636,261.834,269.809,267.038,271.575,269.7,270.212,273.541,277.611,277.099,278.334,279.459,281.772,285.522,284.772,292.071,295.601,293.397,291.76,274.94,278.233,280.83,286.236,285.047,285.156,283.527,285.346,286.659,279.347,282.342,275.636,279.687,283.27,285.254,286.825,291.253,288.249,290.453,288.8,291.749,289.222,292.971,295.662,294.688,295.543,297.931,298.565,300.264,296.682,294.771,296.921,295.037,298.666,297.6,299.952],"sx":[72.82,75.03,81.69,83.91,86.13,88.35,90.57,97.23,99.45,101.67,103.88,106.1,114.98,117.2,119.42,121.64,128.3,130.52,132.73,134.95,137.17,143.83,146.05,148.27,150.49,152.71,159.37,161.58,163.8,166.02,168.24,177.12,179.34,181.56,183.78,190.43,192.65,194.87,197.09,199.31,205.97,208.19,210.41,212.63,214.85,221.5,223.72,225.94,228.16,230.38,237.04,239.26,241.48,243.7,245.92,252.57,254.79,257.01,259.23,261.45,268.11,270.33,272.55,274.77,276.99,283.64,285.86,288.08,290.3,299.18,301.4,303.62,305.84,308.05,314.71,316.93,319.15,321.37,323.59,330.25,332.47,334.69,336.9,339.12,345.78,348.0,350.22,352.44,354.66,361.32,363.54,365.75,367.97,370.19,376.85,379.07,381.29,383.51,385.73,394.6,396.82,399.04,401.26,407.92,410.14,412.36,414.58,416.8,423.45,425.67,427.89,430.11,432.33,438.99,441.21,443.43,445.65,447.87,454.52,456.74,458.96,461.18,463.4,470.06,472.28,474.5,476.72,485.59,487.81,490.03,492.25,494.47,501.13,503.35,505.57,507.79,510.01,516.66,518.88,521.1,523.32,525.54,532.2,534.42,536.64,538.86,541.07],"sy":[58.82,62.62,60.72,62.13,59.47,56.06,57.52,54.05,54.83,53.68,49.46,47.87,48.87,48.81,48.22,52.79,60.94,55.69,56.11,54.47,63.68,59.98,52.34,46.46,44.73,47.48,43.64,42.75,39.4,39.96,39.13,40.47,37.98,40.13,45.5,62.61,77.72,79.5,101.14,103.08,83.23,96.92,77.4,93.49,101.22,137.16,115.2,136.95,177.56,144.75,190.34,170.31,190.11,189.32,205.35,214.45,183.02,177.36,154.94,167.04,154.24,160.3,178.35,169.52,175.18,149.24,148.82,134.97,128.48,132.44,119.78,129.17,127.08,115.35,123.21,136.51,127.08,127.11,121.06,114.71,116.77,105.13,109.38,121.34,120.13,116.05,119.07,113.73,106.33,106.23,115.3,123.19,117.95,115.91,102.34,107.06,99.34,102.53,101.66,95.99,89.07,89.94,87.84,85.93,81.99,75.61,76.89,64.47,58.46,62.21,65.0,93.61,88.01,83.59,74.4,76.42,76.23,79.0,75.91,73.67,86.12,81.02,92.43,85.54,79.44,76.07,73.39,65.86,70.97,67.22,70.03,65.02,69.31,62.94,58.36,60.01,58.56,54.5,53.42,50.53,56.62,59.87,56.22,59.42,53.25,55.06,51.06]},{"panel":0,"label":"20-day average","color":"var(--chart-blue, #3b6ea5)","x":["2020-01-02","2020-01-03","2020-01-06","2020-01-07","2020-01-08","2020-01-09","2020-01-10","2020-01-13","2020-01-14","2020-01-15","2020-01-16","2020-01-17","2020-01-21","2020-01-22","2020-01-23","2020-01-24","2020-01-27","2020-01-28","2020-01-29","2020-01-30","2020-01-31","2020-02-03","2020-02-04","2020-02-05","2020-02-06","2020-02-07","2020-02-10","2020-02-11","2020-02-12","2020-02-13","2020-02-14","2020-02-18","2020-02-19","2020-02-20","2020-02-21","2020-02-24","2020-02-25","2020-02-26","2020-02-27","2020-02-28","2020-03-02","2020-03-03","2020-03-04","2020-03-05","2020-03-06","2020-03-09","2020-03-10","2020-03-11","2020-03-12","2020-03-13","2020-03-16","2020-03-17","2020-03-18","2020-03-19","2020-03-20","2020-03-23","2020-03-24","2020-03-25","2020-03-26","2020-03-27","2020-03-30","2020-03-31","2020-04-01","2020-04-02","2020-04-03","2020-04-06","2020-04-07","2020-04-08","2020-04-09","2020-04-13","2020-04-14","2020-04-15","2020-04-16","2020-04-17","2020-04-20","2020-04-21","2020-04-22","2020-04-23","2020-04-24","2020-04-27","2020-04-28","2020-04-29","2020-04-30","2020-05-01","2020-05-04","2020-05-05","2020-05-06","2020-05-07","2020-05-08","2020-05-11","2020-05-12","2020-05-13","2020-05-14","2020-05-15","2020-05-18","2020-05-19","2020-05-20","2020-05-21","2020-05-22","2020-05-26","2020-05-27","2020-05-28","2020-05-29","2020-06-01","2020-06-02","2020-06-03","2020-06-04","2020-06-05","2020-06-08","2020-06-09","2020-06-10","2020-06-11","2020-06-12","2020-06-15","2020-06-16","2020-06-17","2020-06-18","2020-06-19","2020-06-22","2020-06-23","2020-06-24","2020-06-25","2020-06-26","2020-06-29","2020-06-30","2020-07-01","2020-07-02","2020-07-06","2020-07-07","2020-07-08","2020-07-09","2020-07-10","2020-07-13","2020-07-14","2020-07-15","2020-07-16","2020-07-17","2020-07-20","2020-07-21","2020-07-22","2020-07-23","2020-07-24","2020-07-27","2020-07-28","2020-07-29","2020-07-30","2020-07-31"],"y":[288.8,289.367,289.965,290.393,290.943,291.609,292.192,292.755,293.286,293.752,294.339,294.972,295.516,295.999,296.476,296.819,296.844,297.028,297.279,297.544,297.401,297.479,297.725,298.186,298.619,298.872,299.279,299.612,300.065,300.468,300.772,300.989,301.309,301.564,301.644,301.356,300.862,300.163,298.839,297.411,296.836,295.751,295.014,293.632,291.972,289.336,287.233,284.464,280.404,277.324,272.88,269.065,264.594,260.209,255.511,251.049,247.954,245.078,243.497,241.617,239.53,237.668,234.701,232.466,230.293,229.937,228.949,229.008,230.45,230.812,232.885,234.094,235.947,238.121,240.535,242.825,244.469,245.946,246.941,248.479,249.581,251.202,253.229,254.645,256.263,257.239,258.113,258.737,259.388,260.158,260.29,260.466,260.734,260.718,261.331,262.197,263.012,263.735,264.305,264.855,265.669,266.115,266.748,267.789,268.91,270.098,271.338,272.786,274.193,275.486,276.965,277.834,278.714,279.664,280.485,281.386,282.065,282.756,283.513,284.169,284.255,284.518,284.383,284.394,284.469,284.456,284.558,284.517,284.15,284.002,283.854,284.695,285.244,285.851,286.323,286.805,287.324,288.044,288.705,289.385,290.252,290.874,291.938,292.705,293.475,294.093,294.749],"sx":[72.82,75.03,81.69,83.91,86.13,88.35,90.57,97.23,99.45,101.67,103.88,106.1,114.98,117.2,119.42,121.64,128.3,130.52,132.73,134.95,137.17,143.83,146.05,148.27,150.49,152.71,159.37,161.58,163.8,166.02,168.24,177.12,179.34,181.56,183.78,190.43,192.65,194.87,197.09,199.31,205.97,208.19,210.41,212.63,214.85,221.5,223.72,225.94,228.16,230.38,237.04,239.26,241.48,243.7,245.92,252.57,254.79,257.01,259.23,261.45,268.11,270.33,272.55,274.77,276.99,283.64,285.86,288.08,290.3,299.18,301.4,303.62,305.84,308.05,314.71,316.93,319.15,321.37,323.59,330.25,332.47,334.69,336.9,339.12,345.78,348.0,350.22,352.44,354.66,361.32,363.54,365.75,367.97,370.19,376.85,379.07,381.29,383.51,385.73,394.6,396.82,399.04,401.26,407.92,410.14,412.36,414.58,416.8,423.45,425.67,427.89,430.11,432.33,438.99,441.21,443.43,445.65,447.87,454.52,456.74,458.96,461.18,463.4,470.06,472.28,474.5,476.72,485.59,487.81,490.03,492.25,494.47,501.13,503.35,505.57,507.79,510.01,516.66,518.88,521.1,523.32,525.54,532.2,534.42,536.64,538.86,541.07],"sy":[70.03,69.07,68.05,67.32,66.39,65.25,64.26,63.3,62.4,61.61,60.61,59.53,58.61,57.79,56.97,56.39,56.35,56.04,55.61,55.16,55.4,55.27,54.85,54.06,53.33,52.9,52.2,51.64,50.87,50.18,49.67,49.3,48.75,48.32,48.18,48.67,49.51,50.7,52.95,55.38,56.36,58.21,59.46,61.81,64.64,69.12,72.7,77.41,84.32,89.56,97.12,103.61,111.22,118.67,126.67,134.26,139.52,144.42,147.11,150.31,153.86,157.02,162.07,165.87,169.57,170.18,171.86,171.76,169.3,168.69,165.16,163.1,159.95,156.25,152.15,148.25,145.45,142.94,141.25,138.63,136.76,134.0,130.55,128.14,125.39,123.73,122.24,121.18,120.07,118.76,118.54,118.24,117.78,117.81,116.77,115.29,113.91,112.68,111.71,110.77,109.39,108.63,107.55,105.78,103.87,101.85,99.74,97.28,94.88,92.68,90.17,88.69,87.19,85.58,84.18,82.65,81.49,80.32,79.03,77.91,77.76,77.32,77.55,77.53,77.4,77.42,77.25,77.32,77.94,78.19,78.45,77.02,76.08,75.05,74.25,73.43,72.54,71.32,70.19,69.04,67.56,66.5,64.69,63.39,62.08,61.03,59.91]},{"panel":0,"label":"100-day average","color":"var(--chart-grey, #a3a19c)","x":["2020-01-02","2020-01-03","2020-01-06","2020-01-07","2020-01-08","2020-01-09","2020-01-10","2020-01-13","2020-01-14","2020-01-15","2020-01-16","2020-01-17","2020-01-21","2020-01-22","2020-01-23","2020-01-24","2020-01-27","2020-01-28","2020-01-29","2020-01-30","2020-01-31","2020-02-03","2020-02-04","2020-02-05","2020-02-06","2020-02-07","2020-02-10","2020-02-11","2020-02-12","2020-02-13","2020-02-14","2020-02-18","2020-02-19","2020-02-20","2020-02-21","2020-02-24","2020-02-25","2020-02-26","2020-02-27","2020-02-28","2020-03-02","2020-03-03","2020-03-04","2020-03-05","2020-03-06","2020-03-09","2020-03-10","2020-03-11","2020-03-12","2020-03-13","2020-03-16","2020-03-17","2020-03-18","2020-03-19","2020-03-20","2020-03-23","2020-03-24","2020-03-25","2020-03-26","2020-03-27","2020-03-30","2020-03-31","2020-04-01","2020-04-02","2020-04-03","2020-04-06","2020-04-07","2020-04-08","2020-04-09","2020-04-13","2020-04-14","2020-04-15","2020-04-16","2020-04-17","2020-04-20","2020-04-21","2020-04-22","2020-04-23","2020-04-24","2020-04-27","2020-04-28","2020-04-29","2020-04-30","2020-05-01","2020-05-04","2020-05-05","2020-05-06","2020-05-07","2020-05-08","2020-05-11","2020-05-12","2020-05-13","2020-05-14","2020-05-15","2020-05-18","2020-05-19","2020-05-20","2020-05-21","2020-05-22","2020-05-26","2020-05-27","2020-05-28","2020-05-29","2020-06-01","2020-06-02","2020-06-03","2020-06-04","2020-06-05","2020-06-08","2020-06-09","2020-06-10","2020-06-11","2020-06-12","2020-06-15","2020-06-16","2020-06-17","2020-06-18","2020-06-19","2020-06-22","2020-06-23","2020-06-24","2020-06-25","2020-06-26","2020-06-29","2020-06-30","2020-07-01","2020-07-02","2020-07-06","2020-07-07","2020-07-08","2020-07-09","2020-07-10","2020-07-13","2020-07-14","2020-07-15","2020-07-16","2020-07-17","2020-07-20","2020-07-21","2020-07-22","2020-07-23","2020-07-24","2020-07-27","2020-07-28","2020-07-29","2020-07-30","2020-07-31"],"y":[274.226,274.563,274.871,275.248,275.635,276.003,276.332,276.701,277.045,277.396,277.839,278.263,278.692,279.103,279.484,279.839,280.162,280.486,280.774,281.069,281.309,281.57,281.858,282.171,282.496,282.813,283.146,283.483,283.839,284.205,284.576,284.961,285.345,285.721,286.08,286.327,286.516,286.742,286.82,286.85,287.009,287.128,287.337,287.434,287.458,287.274,287.192,286.987,286.535,286.288,285.754,285.347,284.816,284.285,283.649,282.944,282.424,281.929,281.574,281.122,280.733,280.313,279.785,279.3,278.775,278.407,278.036,277.746,277.489,277.19,276.962,276.68,276.421,276.235,275.997,275.659,275.37,275.068,274.812,274.617,274.43,274.293,274.127,273.864,273.618,273.399,273.153,272.915,272.719,272.503,272.233,271.917,271.62,271.322,271.099,270.849,270.628,270.39,270.172,269.981,269.803,269.643,269.484,269.344,269.211,269.096,268.983,268.921,268.9,268.85,268.759,268.49,268.26,268.055,267.901,267.762,267.672,267.535,267.419,267.306,267.174,267.051,266.815,266.585,266.381,266.213,266.038,265.902,265.717,265.557,265.375,265.231,265.047,264.913,264.837,264.852,264.965,265.112,265.392,265.701,265.858,266.075,266.2,266.4,266.682,267.165,267.542],"sx":[72.82,75.03,81.69,83.91,86.13,88.35,90.57,97.23,99.45,101.67,103.88,106.1,114.98,117.2,119.42,121.64,128.3,130.52,132.73,134.95,137.17,143.83,146.05,148.27,150.49,152.71,159.37,161.58,163.8,166.02,168.24,177.12,179.34,181.56,183.78,190.43,192.65,194.87,197.09,199.31,205.97,208.19,210.41,212.63,214.85,221.5,223.72,225.94,228.16,230.38,237.04,239.26,241.48,243.7,245.92,252.57,254.79,257.01,259.23,261.45,268.11,270.33,272.55,274.77,276.99,283.64,285.86,288.08,290.3,299.18,301.4,303.62,305.84,308.05,314.71,316.93,319.15,321.37,323.59,330.25,332.47,334.69,336.9,339.12,345.78,348.0,350.22,352.44,354.66,361.32,363.54,365.75,367.97,370.19,376.85,379.07,381.29,383.51,385.73,394.6,396.82,399.04,401.26,407.92,410.14,412.36,414.58,416.8,423.45,425.67,427.89,430.11,432.33,438.99,441.21,443.43,445.65,447.87,454.52,456.74,458.96,461.18,463.4,470.06,472.28,474.5,476.72,485.59,487.81,490.03,492.25,494.47,501.13,503.35,505.57,507.79,510.01,516.66,518.88,521.1,523.32,525.54,532.2,534.42,536.64,538.86,541.07],"sy":[94.83,94.25,93.73,93.09,92.43,91.8,91.24,90.62,90.03,89.44,88.68,87.96,87.23,86.53,85.88,85.28,84.73,84.18,83.69,83.19,82.78,82.33,81.84,81.31,80.76,80.22,79.65,79.08,78.47,77.85,77.22,76.56,75.91,75.27,74.66,74.24,73.92,73.53,73.4,73.35,73.08,72.88,72.52,72.36,72.32,72.63,72.77,73.12,73.89,74.31,75.21,75.91,76.81,77.71,78.8,80.0,80.88,81.72,82.33,83.1,83.76,84.47,85.37,86.19,87.09,87.71,88.35,88.84,89.28,89.79,90.17,90.65,91.09,91.41,91.82,92.39,92.88,93.4,93.83,94.16,94.48,94.71,95.0,95.44,95.86,96.24,96.65,97.06,97.39,97.76,98.22,98.76,99.26,99.77,100.15,100.57,100.95,101.35,101.72,102.05,102.35,102.63,102.9,103.13,103.36,103.55,103.75,103.85,103.89,103.97,104.13,104.59,104.98,105.33,105.59,105.82,105.98,106.21,106.41,106.6,106.82,107.04,107.44,107.83,108.17,108.46,108.76,108.99,109.3,109.58,109.89,110.13,110.44,110.67,110.8,110.78,110.58,110.33,109.86,109.33,109.07,108.69,108.48,108.14,107.66,106.84,106.2]},{"panel":1,"label":"Bollinger position (two-standard-deviation units)","color":"var(--chart-maroon, #b6262c)","x":["2020-01-02","2020-01-03","2020-01-06","2020-01-07","2020-01-08","2020-01-09","2020-01-10","2020-01-13","2020-01-14","2020-01-15","2020-01-16","2020-01-17","2020-01-21","2020-01-22","2020-01-23","2020-01-24","2020-01-27","2020-01-28","2020-01-29","2020-01-30","2020-01-31","2020-02-03","2020-02-04","2020-02-05","2020-02-06","2020-02-07","2020-02-10","2020-02-11","2020-02-12","2020-02-13","2020-02-14","2020-02-18","2020-02-19","2020-02-20","2020-02-21","2020-02-24","2020-02-25","2020-02-26","2020-02-27","2020-02-28","2020-03-02","2020-03-03","2020-03-04","2020-03-05","2020-03-06","2020-03-09","2020-03-10","2020-03-11","2020-03-12","2020-03-13","2020-03-16","2020-03-17","2020-03-18","2020-03-19","2020-03-20","2020-03-23","2020-03-24","2020-03-25","2020-03-26","2020-03-27","2020-03-30","2020-03-31","2020-04-01","2020-04-02","2020-04-03","2020-04-06","2020-04-07","2020-04-08","2020-04-09","2020-04-13","2020-04-14","2020-04-15","2020-04-16","2020-04-17","2020-04-20","2020-04-21","2020-04-22","2020-04-23","2020-04-24","2020-04-27","2020-04-28","2020-04-29","2020-04-30","2020-05-01","2020-05-04","2020-05-05","2020-05-06","2020-05-07","2020-05-08","2020-05-11","2020-05-12","2020-05-13","2020-05-14","2020-05-15","2020-05-18","2020-05-19","2020-05-20","2020-05-21","2020-05-22","2020-05-26","2020-05-27","2020-05-28","2020-05-29","2020-06-01","2020-06-02","2020-06-03","2020-06-04","2020-06-05","2020-06-08","2020-06-09","2020-06-10","2020-06-11","2020-06-12","2020-06-15","2020-06-16","2020-06-17","2020-06-18","2020-06-19","2020-06-22","2020-06-23","2020-06-24","2020-06-25","2020-06-26","2020-06-29","2020-06-30","2020-07-01","2020-07-02","2020-07-06","2020-07-07","2020-07-08","2020-07-09","2020-07-10","2020-07-13","2020-07-14","2020-07-15","2020-07-16","2020-07-17","2020-07-20","2020-07-21","2020-07-22","2020-07-23","2020-07-24","2020-07-27","2020-07-28","2020-07-29","2020-07-30","2020-07-31"],"y":[0.79492,0.484483,0.585068,0.429264,0.604828,0.853903,0.69774,0.950755,0.81207,0.837278,1.09558,1.08044,0.874525,0.781075,0.741904,0.316605,-0.406022,0.0310901,-0.0493413,0.0718907,-0.815314,-0.479854,0.262995,0.791846,0.850911,0.523769,0.786315,0.764499,0.900135,0.75432,0.730625,0.588735,0.680743,0.502702,0.164202,-0.797903,-1.33269,-1.15343,-1.43256,-1.17944,-0.636856,-0.842936,-0.384793,-0.652315,-0.718912,-1.14187,-0.688944,-0.89835,-1.20378,-0.698762,-1.06923,-0.751541,-0.865903,-0.776019,-0.85484,-0.845881,-0.466369,-0.365082,-0.0889859,-0.194923,-0.00475763,-0.0435887,-0.247921,-0.0636483,-0.11558,0.444539,0.564413,0.893293,0.899911,0.770245,0.895474,0.639389,0.610916,0.737689,0.554268,0.269552,0.445144,0.404228,0.506016,0.603501,0.498944,0.707082,0.580482,0.214564,0.230667,0.380523,0.193093,0.47459,0.826984,0.752542,0.193481,-0.307449,-0.0107347,0.122129,0.859748,0.56824,0.968295,0.699464,0.681726,0.910017,1.09034,0.911183,0.875766,0.845217,0.884408,0.969916,0.817307,1.04957,1.02894,0.805731,0.659368,-0.140894,-0.0254826,0.068098,0.344085,0.235244,0.207045,0.0560411,0.147178,0.2148,-0.430057,-0.198657,-0.771793,-0.416092,-0.106518,0.0709282,0.200504,0.60163,0.40619,0.672175,0.538987,0.800733,0.469288,0.802137,0.943105,0.746673,0.732616,0.823011,0.769345,0.790189,0.484132,0.302611,0.455007,0.248786,0.602989,0.446025,0.694412],"sx":[72.82,75.03,81.69,83.91,86.13,88.35,90.57,97.23,99.45,101.67,103.88,106.1,114.98,117.2,119.42,121.64,128.3,130.52,132.73,134.95,137.17,143.83,146.05,148.27,150.49,152.71,159.37,161.58,163.8,166.02,168.24,177.12,179.34,181.56,183.78,190.43,192.65,194.87,197.09,199.31,205.97,208.19,210.41,212.63,214.85,221.5,223.72,225.94,228.16,230.38,237.04,239.26,241.48,243.7,245.92,252.57,254.79,257.01,259.23,261.45,268.11,270.33,272.55,274.77,276.99,283.64,285.86,288.08,290.3,299.18,301.4,303.62,305.84,308.05,314.71,316.93,319.15,321.37,323.59,330.25,332.47,334.69,336.9,339.12,345.78,348.0,350.22,352.44,354.66,361.32,363.54,365.75,367.97,370.19,376.85,379.07,381.29,383.51,385.73,394.6,396.82,399.04,401.26,407.92,410.14,412.36,414.58,416.8,423.45,425.67,427.89,430.11,432.33,438.99,441.21,443.43,445.65,447.87,454.52,456.74,458.96,461.18,463.4,470.06,472.28,474.5,476.72,485.59,487.81,490.03,492.25,494.47,501.13,503.35,505.57,507.79,510.01,516.66,518.88,521.1,523.32,525.54,532.2,534.42,536.64,538.86,541.07],"sy":[370.15,380.28,377.0,382.09,376.36,368.22,373.32,365.06,369.59,368.77,360.34,360.83,367.55,370.6,371.88,385.76,409.35,395.08,397.71,393.75,422.72,411.76,387.51,370.25,368.32,379.0,370.43,371.14,366.72,371.48,372.25,376.88,373.88,379.69,390.74,422.15,439.6,433.75,442.86,434.6,416.89,423.62,408.66,417.39,419.57,433.38,418.59,425.43,435.4,418.91,431.0,420.63,424.37,421.43,424.01,423.71,411.32,408.02,399.0,402.46,396.26,397.52,404.19,398.18,399.87,381.59,377.68,366.94,366.72,370.96,366.87,375.23,376.16,372.02,378.01,387.3,381.57,382.9,379.58,376.4,379.81,373.02,377.15,389.1,388.57,383.68,389.8,380.61,369.1,371.53,389.78,406.14,396.45,392.11,368.03,377.55,364.49,373.27,373.85,366.39,360.51,366.35,367.51,368.51,367.23,364.44,369.42,361.84,362.51,369.8,374.58,400.7,396.93,393.88,384.87,388.42,389.34,394.27,391.3,389.09,410.14,402.58,421.29,409.68,399.58,393.78,389.55,376.46,382.84,374.16,378.51,369.96,380.78,369.91,365.31,371.73,372.18,369.23,370.99,370.3,380.3,386.22,381.25,387.98,376.42,381.54,373.43]},{"panel":1,"label":"20-day momentum (return, times 10)","color":"var(--chart-blue, #3b6ea5)","x":["2020-01-02","2020-01-03","2020-01-06","2020-01-07","2020-01-08","2020-01-09","2020-01-10","2020-01-13","2020-01-14","2020-01-15","2020-01-16","2020-01-17","2020-01-21","2020-01-22","2020-01-23","2020-01-24","2020-01-27","2020-01-28","2020-01-29","2020-01-30","2020-01-31","2020-02-03","2020-02-04","2020-02-05","2020-02-06","2020-02-07","2020-02-10","2020-02-11","2020-02-12","2020-02-13","2020-02-14","2020-02-18","2020-02-19","2020-02-20","2020-02-21","2020-02-24","2020-02-25","2020-02-26","2020-02-27","2020-02-28","2020-03-02","2020-03-03","2020-03-04","2020-03-05","2020-03-06","2020-03-09","2020-03-10","2020-03-11","2020-03-12","2020-03-13","2020-03-16","2020-03-17","2020-03-18","2020-03-19","2020-03-20","2020-03-23","2020-03-24","2020-03-25","2020-03-26","2020-03-27","2020-03-30","2020-03-31","2020-04-01","2020-04-02","2020-04-03","2020-04-06","2020-04-07","2020-04-08","2020-04-09","2020-04-13","2020-04-14","2020-04-15","2020-04-16","2020-04-17","2020-04-20","2020-04-21","2020-04-22","2020-04-23","2020-04-24","2020-04-27","2020-04-28","2020-04-29","2020-04-30","2020-05-01","2020-05-04","2020-05-05","2020-05-06","2020-05-07","2020-05-08","2020-05-11","2020-05-12","2020-05-13","2020-05-14","2020-05-15","2020-05-18","2020-05-19","2020-05-20","2020-05-21","2020-05-22","2020-05-26","2020-05-27","2020-05-28","2020-05-29","2020-06-01","2020-06-02","2020-06-03","2020-06-04","2020-06-05","2020-06-08","2020-06-09","2020-06-10","2020-06-11","2020-06-12","2020-06-15","2020-06-16","2020-06-17","2020-06-18","2020-06-19","2020-06-22","2020-06-23","2020-06-24","2020-06-25","2020-06-26","2020-06-29","2020-06-30","2020-07-01","2020-07-02","2020-07-06","2020-07-07","2020-07-08","2020-07-09","2020-07-10","2020-07-13","2020-07-14","2020-07-15","2020-07-16","2020-07-17","2020-07-20","2020-07-21","2020-07-22","2020-07-23","2020-07-24","2020-07-27","2020-07-28","2020-07-29","2020-07-30","2020-07-31"],"y":[0.546509,0.402462,0.42341,0.300019,0.387574,0.469688,0.410002,0.392029,0.36997,0.32249,0.406071,0.437809,0.374849,0.330809,0.326877,0.234722,0.0173377,0.124825,0.172542,0.180824,-0.0966513,0.0530402,0.167474,0.313881,0.293733,0.169907,0.275403,0.2229,0.304476,0.270272,0.201866,0.143999,0.212496,0.169311,0.0530601,-0.193141,-0.335395,-0.470803,-0.891253,-0.958856,-0.392877,-0.736765,-0.492314,-0.913295,-1.09348,-1.74503,-1.38222,-1.8165,-2.64685,-2.00973,-2.89544,-2.4925,-2.90654,-2.86214,-3.09868,-3.04445,-2.1772,-2.03078,-1.16883,-1.39573,-1.48505,-1.36472,-2.086,-1.62498,-1.60729,-0.284892,-0.753444,0.0466999,1.27871,0.295591,1.90155,1.05195,1.69754,1.98809,2.30726,2.24669,1.47851,1.3084,0.83346,1.32705,0.920319,1.37575,1.80094,1.2294,1.42552,0.805332,0.721155,0.498125,0.511861,0.610897,0.101481,0.138249,0.210325,-0.0125571,0.476224,0.693304,0.638841,0.566146,0.44068,0.419089,0.622957,0.332864,0.476454,0.804834,0.864339,0.908139,0.953737,1.10053,1.05184,0.967184,1.12778,0.675071,0.675156,0.725513,0.608811,0.674383,0.500117,0.512696,0.560079,0.479577,0.0625408,0.18921,-0.0969269,0.00817653,0.0531351,-0.00940382,0.0720716,-0.0280172,-0.248737,-0.100342,-0.10146,0.611343,0.394971,0.432289,0.329323,0.33825,0.364225,0.508033,0.463266,0.474602,0.62054,0.440218,0.772204,0.548842,0.543517,0.432821,0.457677],"sx":[72.82,75.03,81.69,83.91,86.13,88.35,90.57,97.23,99.45,101.67,103.88,106.1,114.98,117.2,119.42,121.64,128.3,130.52,132.73,134.95,137.17,143.83,146.05,148.27,150.49,152.71,159.37,161.58,163.8,166.02,168.24,177.12,179.34,181.56,183.78,190.43,192.65,194.87,197.09,199.31,205.97,208.19,210.41,212.63,214.85,221.5,223.72,225.94,228.16,230.38,237.04,239.26,241.48,243.7,245.92,252.57,254.79,257.01,259.23,261.45,268.11,270.33,272.55,274.77,276.99,283.64,285.86,288.08,290.3,299.18,301.4,303.62,305.84,308.05,314.71,316.93,319.15,321.37,323.59,330.25,332.47,334.69,336.9,339.12,345.78,348.0,350.22,352.44,354.66,361.32,363.54,365.75,367.97,370.19,376.85,379.07,381.29,383.51,385.73,394.6,396.82,399.04,401.26,407.92,410.14,412.36,414.58,416.8,423.45,425.67,427.89,430.11,432.33,438.99,441.21,443.43,445.65,447.87,454.52,456.74,458.96,461.18,463.4,470.06,472.28,474.5,476.72,485.59,487.81,490.03,492.25,494.47,501.13,503.35,505.57,507.79,510.01,516.66,518.88,521.1,523.32,525.54,532.2,534.42,536.64,538.86,541.07],"sy":[378.26,382.96,382.28,386.31,383.45,380.77,382.72,383.3,384.02,385.57,382.84,381.81,383.86,385.3,385.43,388.44,395.53,392.03,390.47,390.2,399.25,394.37,390.63,385.85,386.51,390.55,387.11,388.82,386.16,387.28,389.51,391.4,389.16,390.57,394.37,402.4,407.05,411.47,425.19,427.4,408.93,420.15,412.17,425.91,431.8,453.07,441.22,455.4,482.5,461.71,490.62,477.47,490.98,489.53,497.25,495.48,467.17,462.39,434.26,441.66,444.58,440.65,464.2,449.15,448.57,405.4,420.7,394.58,354.36,386.45,334.02,361.76,340.68,331.2,320.78,322.76,347.83,353.39,368.89,352.78,366.06,351.19,337.31,355.97,349.56,369.81,372.56,379.84,379.39,376.16,392.79,391.59,389.23,396.51,380.55,373.47,375.25,377.62,381.71,382.42,375.76,385.23,380.55,369.83,367.88,366.45,364.97,360.17,361.76,364.53,359.28,374.06,374.06,372.42,376.23,374.09,379.77,379.36,377.82,380.44,394.06,389.92,399.26,395.83,394.37,396.41,393.75,397.01,404.22,399.38,399.41,376.14,383.21,381.99,385.35,385.06,384.21,379.52,380.98,380.61,375.84,381.73,370.89,378.18,378.36,381.97,381.16]}]} 2026-10-21T04:13:04.705622 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/ 2020-01 2020-02 2020-03 2020-04 2020-05 2020-06 2020-07 2020-08 Date (2020) 200 220 240 260 280 300 Price (dollars) 1. The low, then the day each average turned up (dashed) SPY close 20-day average 100-day average 2020-01 2020-02 2020-03 2020-04 2020-05 2020-06 2020-07 2020-08 Date (2020) −3 −2 −1 0 1 2 Indicator value 2. Two indicators built from the same 20 days move together Bollinger position (two-standard-deviation units) 20-day momentum (return, times 10)

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:

ItemPrice over average, minus oneBollinger position20-day momentum
Price over average, minus one1.000.830.85
Bollinger position0.831.000.68
20-day momentum0.850.681.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 thinkActually
A shorter window removes the lagit shortens the lag; any average is behind the price
Three indicators agreeing is strong confirmationif they share a window and a price series, they are one reading three times
The best window is the one that backtested besta 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 stocksdivide 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:

VariableHolds
pxSPY’s adjusted close, one row per trading day
wthe twenty closes ending on 2020-03-16
sma20the 20-day average, one value per day once twenty days exist
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)["SPY"]

w = px.loc[:"2020-03-16"].iloc[-20:]              # the last twenty closes up to that day
b = (w.iloc[-1] - w.mean()) / (2 * w.std())        # distance from the mean, in two-standard-deviation units
print(f"mean {w.mean():.2f}  sd {w.std():.2f}  bollinger {b:+.2f}")

sma20 = px.rolling(20).mean()                      # the same mean, for every day
low = px["2020"].idxmin()                          # the 2020 low
turn = sma20[low:].diff().gt(0).idxmax()           # first day the average rose after it
print(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

IdeaIn one lineOn SPY, 2019 to 2023
An indicator is a rolling summaryone number per day about the recent pastBollinger position −1.07 on 2020-03-16
The lookback is a diallong is late, short is noisy20-day average turned 12 days after the low, 100-day 83 days after
Normalise before comparingdollars differ by stock; percentages and standard deviations do notprice 20.08% below its average, 1.07 bands below
Shared inputs mean shared informationindicators from one window agree with each otherpairwise correlation 0.68 to 0.85
Indicator, rule, strategydescribe, decide, then pay for it5-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.