Simple Moving Average: The Formula, and What Crossing It Returned
A simple moving average is the arithmetic mean of the last n closing prices, plotted against the bar it ends on. There is no second step. Everything a chart package draws on top — envelopes, ribbons, the golden cross — is that one number, computed at different lengths and compared with itself.
Our engine computes it as batch_sma. Below is the kernel it actually runs, the reason that kernel refuses to do the obvious optimisation, and what 77,127 real crossings of price through its own average returned when they were scored against forward bars.
What a simple moving average actually is
Sum the last n closes. Divide by n. Put the answer under today's bar and repeat tomorrow.
Each new value drops the oldest close and admits the newest, because the frame slides forward exactly one bar. That is the whole mechanism, and it explains every property people ascribe to the line. It lags because half its inputs are old. It smooths because averaging is what smoothing means. It cannot turn before enough new bars have entered the window to outweigh what is still sitting inside it.
Nothing is emitted for the first n − 1 bars. Our kernel returns empty there rather than averaging four closes and calling it a twenty-bar mean, which is a small decision with a large consequence: a value that exists only because the implementation was lenient will still be traded by somebody.
The exact formula our engine runs
Here it is with nothing left implicit, straight out of the crate:
sma(c, n)[i] = mean(c[i−n+1 … i])— the mean of the window ending on bar i, inclusive of bar i itself, and nothing at all before bar n − 1.batch_smais three lines over that shared kernel, and the same kernel is whatavg_bodycalls to size every candlestick pattern in the engine.
Two details in that sentence are worth more than the formula. The window includes the current bar, so a twenty-bar average on a spike already contains the spike; traders who expect the line to be a clean "recent history excluding now" reference are reading a different indicator than the one on screen. And the average is taken over closes, not over the high, the low or the typical price. Feed it something else and you have built a different series with the same name.
I've checked that second point first for years whenever two platforms disagree about a moving average, and it resolves the argument more often than any other cause.
Why the window is recomputed, and what it costs
Now the part that makes this page different from every other description of the same formula, because our kernel does something that looks inefficient and is deliberate.
An obvious way to compute a rolling mean is to keep a running total: add the new close, subtract the one falling out, divide. That is O(1) per bar and it is what the Python implementation this engine was ported from still does. Our crate does not. It re-adds every value in the window, every single bar, at O(n) per bar — and the source comment explaining why is blunt about the trade-off, warning that values change in the last bits.
Carrying a total makes the value depend on more than the bars inside it. It holds accumulated rounding error from bar zero, so running the same kernel over a truncated history returns different bits for identical bars. Recomputing makes each value depend only on the bars inside it, which is the precondition for streaming and batch paths agreeing at all.
How much that difference matters. Across 282 symbols and 1,338,088 values at lengths 20, 50, 100 and 200, the largest gap between the two forms was 1.673e-10 — 6.54e-13 in relative terms — and 94.81% of values differed somewhere in the final bits. That sounds harmless. It is not quite: 105 of 88,562 crossing decisions changed, or 0.119%, counting every bar each average existed rather than the common window scored further down. The mechanism is exact ties. The recomputed mean lands exactly on the close 86 times in this sample against the accumulator's 12, and a crossing rule that compares with > has to call a tie one way or the other. 87 of the 105 changed decisions sit on one of those bars.
Our golden cross page reported zero changed decisions from the same difference, and both results are right. Two averages of the same series essentially never land on identical bits; an average and one of its own inputs do, because the close is inside the window that produced the mean.
What price crossing its own average actually returned
Everybody runs the same rule, and it deserves to be stated tightly enough to be proven wrong: buy when the close finishes above its moving average, sell when it finishes below.
Across 282 Binance spot pairs, from each one's first daily candle to 2026-08-17, I scored every crossing on the 298,285 bars where all four lengths existed. Each crossing was scored first-touch over the next twenty bars with a symmetric ±10% bracket, so break-even sits at exactly 50% before costs and a bar tagging both levels scores a loss. The same bracket was then applied to every eligible bar, which gives the base rate: 50.2% long, 49.0% short.
Against that yardstick the 50-bar version resolved 51.2% on up-crossings and 50.9% on down-crossings — worth +1.0 and +1.9 points over doing nothing in particular. The 20-bar version came back +0.4 and +3.2. Both longer settings gave the edge back: the 200-bar average scored 48.9% and 48.0%, under its own base rate on both sides.
So the answer is a real but tiny edge at short lengths and a negative one at long lengths, before a single fee is paid. On 34,551 signals at length 20, a fee of a few basis points per round trip erases the whole of it.
Choosing the length: 20, 50, 100 or 200
Length is the only parameter, and the four popular settings behave in a completely predictable way once you see them side by side.
Shorter windows cross more often. At length 20 this sample produced one crossing every 9 bars; at 50 it was one per 15, at 100 one per 22, and at 200 one per 32. The frequency is the point of the parameter and it is not free — the shorter the window, the more of those crossings are noise the average has not yet absorbed.
Here is the cost, measured. Between 58% and 61% of crossings at every length were undone by the opposite crossing within five bars. That is not a fringe case to be filtered out; at length 50 it is 11,244 of 19,405 consecutive pairs. Price spends its life oscillating across its own mean, which is exactly what a mean is for, and treating each oscillation as a signal produces round trips through the spread rather than positions.
One asymmetry surprised me enough to double-check it. The close sat above its 50-bar average on only 37.6% of eligible bars, in a sample whose long base rate is slightly above break-even. Crypto spends most of its time drifting under its own average and makes its money in short violent stretches above it, which is a fact about the market rather than about the formula.
Where the simple moving average fails
Both failures shown here are real crossings this rule produced, and they fail in the two distinct ways it fails.
Being late is the first, and it is structural. The median up-crossing in this sample printed with the close already 1.93% above the average, and the top decile printed at 8.11% above — the move had begun, the window had partly absorbed it, and only then did the line get crossed. REQ crossed up with the close stretched 13.87% past its average and gave back 21.0% before it gained anything.
Being wrong is the second, and it looks different: ADX crossed down with the close a mere 0.13% under the line and then rose 18.4%. A crossing that marginal is arithmetic noise dressed as an event, and the strictness of the comparison is doing all the work.
I use the average as a regime label now — above or below, nothing more — and take entries from something with no smoothing in it at all, such as a candlestick reversal. The two disagree usefully rather than redundantly.
Simple moving average versus the alternatives
Every other moving average exists because somebody wanted this one to respond faster, and each buys that response with a different weighting.
The exponential moving average weights recent closes more heavily via a decay constant, and never fully forgets old data. Weighted and Hull variants apply linear or blended weights. Wilder's smoothing, which our engine keeps as a separate function and which powers RSI and ATR, uses a slower constant than the exponential average and is regularly confused with it.
That page measures the comparison on this exact sample rather than repeating the folklore, and the summary is worth having before you switch: the faster average produced more crossings, was whipsawed more often, and led the simple one by a median of zero bars.
Frequently asked questions
What is the simple moving average formula? Take the closing prices of the last n bars, add them, divide by n, and repeat on the next bar. Our engine emits nothing until n bars exist rather than publishing a partial average.
What is a 50-day moving average? The mean of the last fifty daily closes. It updates each day as the newest close enters the window and the fiftieth-oldest leaves it, so roughly two months of history sit behind every value.
Is the SMA better than the EMA? Not on this evidence. Over 282 symbols the two produced statistically indistinguishable hit rates, and the exponential version generated more signals and more whipsaws for its extra responsiveness.
Which moving average length is best? The shorter settings carried a small positive edge here and the longer ones did not, but the differences are inside the range that fees would swallow. Choose the length that matches your holding period rather than the one that backtests best.
Does a moving average predict anything? No. It is a summary of closes that have already happened, and the MACD page shows what happens when two of them are subtracted and the result is treated as foresight.
This is educational material, not financial advice. Every figure here was measured on past bars, past behavior generalizes poorly to future bars, and trading carries real risk of loss — size any position so that being wrong stays survivable.