Question

I coded a function that calculates the moving average of a stock given a list of dates and prices. But the output is incorrect. I just need a second set of eyes on the code. here is my code.

def calculate(self, stock_date_price_list, min_days=2):
    '''Calculates the moving average and generates a signal strategy for buy or sell
       strategy given a list of stock date and price. '''

    stock_averages = []
    stock_signals  = []
    price_list   = [float(n) for n in stock_date_price_list[1::2]]
    days_window   = collections.deque(maxlen=min_days)
    rounding_point = 0.01

    for price in price_list:
        days_window.append(price)
        stock_averages.append(0)
        stock_signals.append("")
        if len(days_window) == min_days:
            moving_avg = sum(days_window) / min_days
            stock_averages[-1] = moving_avg
            if price < moving_avg:
                stock_signals[-1] = "SELL"
            elif price > moving_avg:
                if price_list[-2] < stock_averages[-2]:
                    stock_signals[-1] = "BUY"

    stock_averages[:] = ("%.2f" % avg if abs(avg)>=rounding_point else ' '  for avg in stock_averages)

    return stock_averages, stock_signals

The input is a list of stock price and dates in the following format:

 [2012-10-10,52.30,2012-10-09,51.60]

The output I get is:

 2012-10-01      659.39                                              
 2012-10-02      661.31                                              
 2012-10-03      671.45                                              
 2012-10-04      666.80                                              
 2012-10-05      652.59                                              
 2012-10-08      638.17                                              
 2012-10-09      635.85                                              
 2012-10-10      640.91                                              
 2012-10-11      628.10                                              
 2012-10-12      629.71         648.43           SELL                
 2012-10-15      634.76         645.97           SELL                
 2012-10-16      649.79         644.81           BUY                    
 2012-10-17      644.61         642.13           BUY                 
 2012-10-18      632.64         638.71           SELL                
 2012-10-19      609.84         634.44           SELL                
 2012-10-22      634.03         634.02           BUY                   
 2012-10-23      613.36         631.77           SELL                
 2012-10-24      616.83         629.37           SELL                

Whereas it should be:

2012-10-01      659.39                                              
2012-10-02      661.31                                              
2012-10-03      671.45                                              
2012-10-04      666.80                                              
2012-10-05      652.59                                              
2012-10-08      638.17                                              
2012-10-09      635.85                                              
2012-10-10      640.91                                              
2012-10-11      628.10                                              
2012-10-12      629.71         648.43                           
2012-10-15      634.76         645.97                           
2012-10-16      649.79         644.81           BUY                    
2012-10-17      644.61         642.13                              
2012-10-18      632.64         638.71           SELL                
2012-10-19      609.84         634.44                           
2012-10-22      634.03         634.02           BUY                    
2012-10-23      613.36         631.77           SELL                
2012-10-24      616.83         629.37           

Parameters for buying/selling:

If the closing price on a particular day has crossed above the simple moving average (i.e., the closing price on that day is above that day's simple moving average, while the previous closing price is not above the previous simple moving average), generate a buy signal.

If the closing price on a particular day has crossed below the simple moving average, generate a sell signal.

Otherwise, generate no signal.

Was it helpful?

Solution

As you state yourself, the condition for buying is not just price > moving_avg but also that the previous_price < previous_moving_avg. You do address this with

price_list[-2] < stock_averages[-2]

except that price_list is one big list, and price_list[-2] is always the penultimate item in the big list. It isn't necessarily the previous price relative to where you are in the loop.

Similarly, the signal to sell needs to be not only price < moving_avg but also that previous_price > previous_moving_avg.


There are other (mainly stylistic) problems with calculate.

  • stock_data_price_list is a required input, but you only use the slice stock_data_price_list[1::2]. If that's the case, you should require the slice as the input, not stock_data_price_list
  • price_list is essentially this slice, except that you call float on each item. That implies the data has not been parsed properly. Don't make calculate be both a data parser as well as a data analyzer. It's much better to make simple functions which accomplish one and only one task.
  • Similarly, calculate should not be in the business of formatting the result:

    stock_averages[:] = ("%.2f" % avg if abs(avg)>=rounding_point else ' '  for avg in stock_averages)
    

Here is how you could fix the code using pandas:

import pandas as pd

data = [('2012-10-01', 659.38999999999999),
 ('2012-10-02', 661.30999999999995),
 ('2012-10-03', 671.45000000000005),
 ('2012-10-04', 666.79999999999995),
 ('2012-10-05', 652.59000000000003),
 ('2012-10-08', 638.16999999999996),
 ('2012-10-09', 635.85000000000002),
 ('2012-10-10', 640.90999999999997),
 ('2012-10-11', 628.10000000000002),
 ('2012-10-12', 629.71000000000004),
 ('2012-10-15', 634.75999999999999),
 ('2012-10-16', 649.78999999999996),
 ('2012-10-17', 644.61000000000001),
 ('2012-10-18', 632.63999999999999),
 ('2012-10-19', 609.84000000000003),
 ('2012-10-22', 634.02999999999997),
 ('2012-10-23', 613.36000000000001),
 ('2012-10-24', 616.83000000000004)]

df = pd.DataFrame(data, columns=['date','price'])
df['average'] = pd.rolling_mean(df['price'], 10)
df['prev_price'] = df['price'].shift(1)
df['prev_average'] = df['average'].shift(1)
df['signal'] = ''
buys = (df['price']>df['average']) & (df['prev_price']<df['prev_average'])
sells = (df['price']<df['average']) & (df['prev_price']>df['prev_average'])
df.loc[buys, 'signal'] = 'BUY'
df.loc[sells, 'signal'] = 'SELL'

print(df)

yields

          date   price  average  prev_price  prev_average signal
0   2012-10-01  659.39      NaN         NaN           NaN       
1   2012-10-02  661.31      NaN      659.39           NaN       
2   2012-10-03  671.45      NaN      661.31           NaN       
3   2012-10-04  666.80      NaN      671.45           NaN       
4   2012-10-05  652.59      NaN      666.80           NaN       
5   2012-10-08  638.17      NaN      652.59           NaN       
6   2012-10-09  635.85      NaN      638.17           NaN       
7   2012-10-10  640.91      NaN      635.85           NaN       
8   2012-10-11  628.10      NaN      640.91           NaN       
9   2012-10-12  629.71  648.428      628.10           NaN       
10  2012-10-15  634.76  645.965      629.71       648.428       
11  2012-10-16  649.79  644.813      634.76       645.965    BUY
12  2012-10-17  644.61  642.129      649.79       644.813       
13  2012-10-18  632.64  638.713      644.61       642.129   SELL
14  2012-10-19  609.84  634.438      632.64       638.713       
15  2012-10-22  634.03  634.024      609.84       634.438    BUY
16  2012-10-23  613.36  631.775      634.03       634.024   SELL
17  2012-10-24  616.83  629.367      613.36       631.775       

[18 rows x 6 columns]

Without pandas, you could do something like this:

nan = float('nan')

def calculate(prices, size=2):
    '''Calculates the moving average and generates a signal strategy for buy or sell
       strategy given a list of stock date and price. '''

    averages = [nan]*(size-1) + moving_average(prices, size)
    previous_prices = ([nan] + prices)[:-1]
    previous_averages = ([nan] + averages)[:-1]

    signal = []
    for price, ave, prev_price, prev_ave in zip(
        prices, averages, previous_prices, previous_averages):
        if price > ave and prev_price < prev_ave:
            signal.append('BUY')
        elif price < ave and prev_price > prev_ave:
            signal.append('SELL')
        else:
            signal.append('')
    return averages, signal


def window(seq, n=2):
    """
    Returns a sliding window (of width n) over data from the sequence
    s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...
    """
    for i in xrange(len(seq) - n + 1):
        yield tuple(seq[i:i + n])


def moving_average(data, size):
    return [(sum(grp)/len(grp)) for grp in window(data, n=size)]

def report(*args):
    for row in zip(*args):
        print(''.join(map('{:>10}'.format, row)))

dates = ['2012-10-01',
 '2012-10-02',
 '2012-10-03',
 '2012-10-04',
 '2012-10-05',
 '2012-10-08',
 '2012-10-09',
 '2012-10-10',
 '2012-10-11',
 '2012-10-12',
 '2012-10-15',
 '2012-10-16',
 '2012-10-17',
 '2012-10-18',
 '2012-10-19',
 '2012-10-22',
 '2012-10-23',
 '2012-10-24']

prices = [659.38999999999999,
        661.30999999999995,
        671.45000000000005,
        666.79999999999995,
        652.59000000000003,
        638.16999999999996,
        635.85000000000002,
        640.90999999999997,
        628.10000000000002,
        629.71000000000004,
        634.75999999999999,
        649.78999999999996,
        644.61000000000001,
        632.63999999999999,
        609.84000000000003,
        634.02999999999997,
        613.36000000000001,
        616.83000000000004]

averages, signals = calculate(prices, size=10)
report(dates, prices, averages, signals)

which yields

2012-10-01    659.39       nan          
2012-10-02    661.31       nan          
2012-10-03    671.45       nan          
2012-10-04     666.8       nan          
2012-10-05    652.59       nan          
2012-10-08    638.17       nan          
2012-10-09    635.85       nan          
2012-10-10    640.91       nan          
2012-10-11     628.1       nan          
2012-10-12    629.71   648.428          
2012-10-15    634.76   645.965          
2012-10-16    649.79   644.813       BUY
2012-10-17    644.61   642.129          
2012-10-18    632.64   638.713      SELL
2012-10-19    609.84   634.438          
2012-10-22    634.03   634.024       BUY
2012-10-23    613.36   631.775      SELL
2012-10-24    616.83   629.367          
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top