Sunday, June 20, 2010

Running Simple Moving Average (SMA)

When building a platform to test trading ideas...one of the big issues to deal with is all the indicators that require a spin through the price series in order to calculate. For example, in order to calculate the 200 day simple moving average (SMA) of closing prices for Google today you would have to loop back 200 - 1 days ago and sum the closing prices and divide by 200.

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.
Straight-forward and avoids the loop. Here's the sample Python code for the Running SMA:
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

12 comments:

Christian Bogeberg said...

Hi Mike,

I'm a recruiter at Google and was curious if you might be interested in exploring opportunities with us.

Please let me know if you'd like to chat.

Sincerely,
Christian Bogeberg | bogeberg@google.com
Engineering Recruiter | Google Inc., CA
Voice:650.762.6645 | Fax:650.963.3269

legit online jobs said...

I love your website! did you create this yourself or did you outsource it? Im looking for a blog design thats similar so thats the only reason I'm asking. Either way keep up the nice work I was impressed with your content really..

Mike Taylor said...

Thanks! I used the Daily Edition theme from themesheep. I believe they have a few other free ones to choose from and of course paid ones as well. Wordpress makes it pretty easy to try out different themes from various sources.

MT

TaylorTree » Blog Archive » Exponential Moving Average (EMA) said...

[...] that we’ve tackled Running Simple Moving Averages (SMA)…let’s move on to Exponential Moving Averages (EMA). You may wonder why we’re not [...]

Running Sum | TaylorTree said...

[...] covered Running SMAs and EMAs…let’s dig into Running Sums or often called Running Totals. Formula as [...]

Running Variance | TaylorTree said...

[...] You also need the Simple Moving Average, which you can find in one of my previous posts here. [...]

Yogi said...

Thanks for the algo.
Is there a way to calculate the value without requiring the
series[bar - period] data point as this requires me to store previous data points in a sliding window calculation.

Herbert said...

Thanks for good info:)
How to use this with lists?
I am getting errors when I try:

print "Today's 3-day SMA = %.4f" % currentsma
TypeError: float argument required, not str

Some solutions?

Mike Taylor said...

The example code is using a list (prices). Are you asking is there a way to pass a full list to running_sma to obtain list of running simple moving averages? Yes, but you'd have to restructure running_sma to expect a list of values instead of a value & previous value. Then perform the for bar, close in enumerate(prices) logic within the running_sma function. And of course, then return a list of values back instead of a value. This is basically what I did in my statio package on github - https://github.com/TaylorTree/statio.


Here's the sample code from the statio sma_values function:


def sma_values(values, period=None): """Returns list of running simple moving averages.
:param values: list of values to iterate and compute stats. :param period: (optional) # of values included in computation. * None - includes all values in computation. :rtype: list of simple moving averages.
Examples: >>> values = [34, 30, 29, 34, 38, 25, 35] >>> results = sma_values(values, 3) #using 3 period window. >>> ["%.2f" % x for x in results] ['34.00', '32.00', '31.00', '31.00', '33.67', '32.33', '32.67'] """ if period: if period < 1: raise ValueError("period must be 1 or greater")
period_n = float(period) period = int(period)
results = [] lastval = None for bar, newx in enumerate(values): if lastval == None: lastval = float(newx)
elif (not period) or (bar < period): lastval += ((newx - lastval) / (bar + 1.0))
else: lastval += ((newx - values[bar - period]) / period_n)
results.append(lastval)
return results

Mike Taylor said...

No way I know of to avoid storing the previous data point in order to obtain the running calculation. Just have to decide where you'd like to store it. You could store it and then use it in the call to your function. Or you could create a class for your logic and memoize the previous data point (cache the value) in the class. Then the object will keep track of it for you through the window.

Anonymous said...

Hi Mike, there's an error in the code here. The pval = 0.0 should be in an else statement.

Mike Taylor said...

Believe you are referring to the Running Variance post in regard to the pval = 0.0 error. I have corrected the code in that post. Thanks! MT