When you are backtesting an idea you often need to start from day 1 of a stock's trading history and loop forward to the most current day. In essence, pretending each day is the current day at that point in time. Thus, you are looping back 200 - 1 data points for each day in the series. This isn't such a big deal with a stock such as Google whose trading history is rather limited (2004). But, take a stock like IBM with a more extensive trading history and your code is going to bog down with each call to the SMA indicator. Throw 20,000 securities into your backtest and the looping adds up.
Therefore, running calculations are the preferred method in order to spin just once through the data points. So, in order to calculate the running simple moving average for closing prices you apply the following formula:
\(SMA_{today} = SMA_{yesterday} + ((Price_{today} - Price_{today - n}) /n)\)
Where
- \(n\) = number of values included in your rolling computational window.
def cumulative_sma(bar, series, prevma): """ Returns the cumulative or unweighted simple moving average. Avoids sum of series per call. Keyword arguments: bar -- current index or location of the value in the series series -- list or tuple of data to average prevma -- previous average (n - 1) of the series. """ if bar <= 0: return series[0] return prevma + ((series[bar] - prevma) / (bar + 1.0))
def running_sma(bar, series, period, prevma): """ Returns the running simple moving average - avoids sum of series per call. Keyword arguments: bar -- current index or location of the value in the series series -- list or tuple of data to average period -- number of values to include in average prevma -- previous simple moving average (n - 1) of the series """ if period < 1: raise ValueError("period must be 1 or greater") if bar <= 0: return series[0] elif bar < period: return cumulative_sma(bar, series, prevma) return prevma + ((series[bar] - series[bar - period]) / float(period))And the example call and results:
prices = [10, 15, 25, 18, 13, 16] prevsma = prices[0] #1st day nothing to average so return itself. for bar, close in enumerate(prices): currentsma = running_sma(bar, prices, 3, prevsma) print "Today's 3-day SMA = %.4f" % currentsma prevsma = currentsma ------- Results ---------------- Today's 3-day SMA = 10.0000 Today's 3-day SMA = 12.5000 Today's 3-day SMA = 16.6667 Today's 3-day SMA = 19.3333 Today's 3-day SMA = 18.6667 Today's 3-day SMA = 15.6667