LessonThis is a lesson restated in new words. Any figure in it is an example from the source, not this desk's record.

A moving-average crossover backtest in pandas compares a fast average with a slow one and assigns a long or short position on each row. The lesson uses Yahoo Finance daily data for the SPY ETF, about three years ending in the 2021 session, a 7-day average, and a 14-day average, both on the close. It builds an equity curve from a $100,000 start and reports a Sharpe ratio, mean return divided by the volatility of return, of about 0.697.

Why it matters

The example is deliberately simple so the pandas steps stay visible: load a CSV, keep the columns you need, add rolling features, drop empty rows, loop, and summarize. The lesson points readers who want a machine-learning strategy to a separate research paper for model scores and results. This session is the dataframe mechanics, plus a warning about peeking at the same close you trade.

The idea in plain words

After the download, the frame has a date, open, high, low, close, adjusted close, and volume. The lesson keeps date and close. A 7-day simple moving average is the rolling mean of close with a window of 7. A 14-day average doubles the window. The first rows are empty because a 14-day mean needs 14 closes. Dropping rows where the 14-day column is missing also drops the shorter empty stretch of the 7-day column.

The signal is true when the 7-day average is above the 14-day average. The lesson goes long on true and short on false. Daily return is the close-to-close percent change. On a short day the sign of that percent is flipped. An equity curve starts at 100,000 and multiplies by one plus each signed return.

The lesson flags a timing cheat. Both averages include today's close, and the return uses today's close as well. A version that could actually be traded would keep the open and would trade the next day's open-to-close move from the prior day's signal. The published loop is an illustration of the frame, with that caveat stated up front.

How to use it

Read the CSV with pandas and restrict the frame to date and close. Add the two rolling means. Drop missing values on the 14-day column. Add a signal column from the comparison of the two means. Set the starting position from the first signal and the starting price from the first close. Iterate from the next row so a return exists. For each row, compute close minus the stored price, divided by the stored price. If the stored position is the short flag, multiply that return by minus one. Append it, then set the stored price to this close and the stored position to this row's signal.

Start equity at 100,000 and walk the return list, multiplying by one plus the return and recording each level. Plot the levels with Matplotlib. The lesson describes a drawdown and then a rise nearer 2020 and 2021 as the market recovered. It does not read out a final dollar wealth.

For the scale of the mean and the deviation, multiply the average daily return by 252 and the daily standard deviation by the square root of 252. The lesson also computes a full-sample return as the last equity level minus 100,000, divided by 100,000, and calls that the clearer expectation. The Sharpe it prints, that return divided by the standard deviation of the returns, is about 0.697, which it calls roughly 0.7.

A Sortino-style figure uses only the losing days. Collect daily returns below zero, take their standard deviation, multiply by the square root of 252, and divide the return by that downside deviation. The lesson computes this and does not speak the number.

What usually goes wrong

Empty average rows break the true-or-false signal. Dropping on the slower column is the fix. Forgetting to flip the sign on short days records the underlying's return. Forgetting to update the stored position freezes the first signal for the whole history.

Scaling a daily standard deviation by 252, rather than by the square root of 252, inflates the deviation. The deeper issue is the timing. The lesson already says the signal sees the close it earns. Treat 0.697 as the output of this illustrative loop. A tradable test would use the prior signal and the next session's open-to-close move.

Questions people ask

Which market and window does the lesson use?

SPY daily closes from a Yahoo Finance file covering roughly 2018 into 2021, with 7-day and 14-day averages.

What Sharpe does the script print?

About 0.697. The lesson defines it as the strategy return divided by the standard deviation of returns, after putting the daily mean on a 252-day scale and the daily deviation on a square-root-of-252 scale.

What does the lesson say to change for a tradable test?

Use the prior day's crossover, and earn the next day's open-to-close move, so today's close is not both the signal and the gain.