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 slicestock_data_price_list[1::2]
. If that's the case, you should require the slice as the input, notstock_data_price_list
price_list
is essentially this slice, except that you callfloat
on each item. That implies the data has not been parsed properly. Don't makecalculate
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