Reputation: 27
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.
Upvotes: 0
Views: 120
Reputation: 879591
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
Upvotes: 1