|
| 1 | +import matplotlib.dates as mdates |
| 2 | +import matplotlib.font_manager as font_manager |
| 3 | +import matplotlib.pyplot as plt |
| 4 | +import matplotlib.ticker as mticker |
| 5 | +import numpy as np |
| 6 | +import pandas as pd |
| 7 | +from pandas.plotting import register_matplotlib_converters |
| 8 | +register_matplotlib_converters() |
| 9 | +import os.path |
| 10 | +import io |
| 11 | + |
| 12 | +def test_finance_work2(): |
| 13 | + |
| 14 | + ticker = 'SPY' |
| 15 | + infile = os.path.join('examples','data','yahoofinance-SPY-20080101-20180101.csv') |
| 16 | + r = pd.read_csv(infile, |
| 17 | + index_col=0, |
| 18 | + parse_dates=True, |
| 19 | + infer_datetime_format=True) |
| 20 | + |
| 21 | + |
| 22 | + def moving_average(x, n, type='simple'): |
| 23 | + """ |
| 24 | + compute an n period moving average. |
| 25 | + |
| 26 | + type is 'simple' | 'exponential' |
| 27 | + |
| 28 | + """ |
| 29 | + x = np.asarray(x) |
| 30 | + if type == 'simple': |
| 31 | + weights = np.ones(n) |
| 32 | + else: |
| 33 | + weights = np.exp(np.linspace(-1., 0., n)) |
| 34 | + |
| 35 | + weights /= weights.sum() |
| 36 | + |
| 37 | + a = np.convolve(x, weights, mode='full')[:len(x)] |
| 38 | + a[:n] = a[n] |
| 39 | + return a |
| 40 | + |
| 41 | + |
| 42 | + def relative_strength(prices, n=14): |
| 43 | + """ |
| 44 | + compute the n period relative strength indicator |
| 45 | + http://stockcharts.com/school/doku.php?id=chart_school:glossary_r#relativestrengthindex |
| 46 | + http://www.investopedia.com/terms/r/rsi.asp |
| 47 | + """ |
| 48 | + |
| 49 | + deltas = np.diff(prices) |
| 50 | + seed = deltas[:n + 1] |
| 51 | + up = seed[seed >= 0].sum() / n |
| 52 | + down = -seed[seed < 0].sum() / n |
| 53 | + rs = up / down |
| 54 | + rsi = np.zeros_like(prices) |
| 55 | + rsi[:n] = 100. - 100. / (1. + rs) |
| 56 | + |
| 57 | + for i in range(n, len(prices)): |
| 58 | + delta = deltas[i - 1] # cause the diff is 1 shorter |
| 59 | + |
| 60 | + if delta > 0: |
| 61 | + upval = delta |
| 62 | + downval = 0. |
| 63 | + else: |
| 64 | + upval = 0. |
| 65 | + downval = -delta |
| 66 | + |
| 67 | + up = (up * (n - 1) + upval) / n |
| 68 | + down = (down * (n - 1) + downval) / n |
| 69 | + |
| 70 | + rs = up / down |
| 71 | + rsi[i] = 100. - 100. / (1. + rs) |
| 72 | + |
| 73 | + return rsi |
| 74 | + |
| 75 | + |
| 76 | + def moving_average_convergence(x, nslow=26, nfast=12): |
| 77 | + """ |
| 78 | + compute the MACD (Moving Average Convergence/Divergence) using a fast and |
| 79 | + slow exponential moving avg |
| 80 | + |
| 81 | + return value is emaslow, emafast, macd which are len(x) arrays |
| 82 | + """ |
| 83 | + emaslow = moving_average(x, nslow, type='exponential') |
| 84 | + emafast = moving_average(x, nfast, type='exponential') |
| 85 | + return emaslow, emafast, emafast - emaslow |
| 86 | + |
| 87 | + |
| 88 | + plt.rc('axes', grid=True) |
| 89 | + plt.rc('grid', color='0.75', linestyle='-', linewidth=0.5) |
| 90 | + |
| 91 | + textsize = 9 |
| 92 | + left, width = 0.1, 0.8 |
| 93 | + rect1 = [left, 0.7, width, 0.2] |
| 94 | + rect2 = [left, 0.3, width, 0.4] |
| 95 | + rect3 = [left, 0.1, width, 0.2] |
| 96 | + |
| 97 | + |
| 98 | + fig = plt.figure(facecolor='white') |
| 99 | + axescolor = '#f6f6f6' # the axes background color |
| 100 | + |
| 101 | + ax1 = fig.add_axes(rect1, facecolor=axescolor) # left, bottom, width, height |
| 102 | + ax2 = fig.add_axes(rect2, facecolor=axescolor, sharex=ax1) |
| 103 | + ax2t = ax2.twinx() |
| 104 | + ax3 = fig.add_axes(rect3, facecolor=axescolor, sharex=ax1) |
| 105 | + |
| 106 | + |
| 107 | + # plot the relative strength indicator |
| 108 | + prices = r["Adj Close"] |
| 109 | + rsi = relative_strength(prices) |
| 110 | + fillcolor = 'darkgoldenrod' |
| 111 | + |
| 112 | + ax1.plot(r.index, rsi, color=fillcolor) |
| 113 | + ax1.axhline(70, color=fillcolor) |
| 114 | + ax1.axhline(30, color=fillcolor) |
| 115 | + ax1.fill_between(r.index, rsi, 70, where=(rsi >= 70), |
| 116 | + facecolor=fillcolor, edgecolor=fillcolor) |
| 117 | + ax1.fill_between(r.index, rsi, 30, where=(rsi <= 30), |
| 118 | + facecolor=fillcolor, edgecolor=fillcolor) |
| 119 | + ax1.text(0.6, 0.9, '>70 = overbought', va='top', |
| 120 | + transform=ax1.transAxes, fontsize=textsize) |
| 121 | + ax1.text(0.6, 0.1, '<30 = oversold', |
| 122 | + transform=ax1.transAxes, fontsize=textsize) |
| 123 | + ax1.set_ylim(0, 100) |
| 124 | + ax1.set_yticks([30, 70]) |
| 125 | + ax1.text(0.025, 0.95, 'RSI (14)', va='top', |
| 126 | + transform=ax1.transAxes, fontsize=textsize) |
| 127 | + ax1.set_title('%s daily' % ticker) |
| 128 | + |
| 129 | + # plot the price and volume data |
| 130 | + dx = r["Adj Close"] - r.Close |
| 131 | + low = r.Low + dx |
| 132 | + high = r.High + dx |
| 133 | + |
| 134 | + deltas = np.zeros_like(prices) |
| 135 | + deltas[1:] = np.diff(prices) |
| 136 | + up = deltas > 0 |
| 137 | + ax2.vlines(r.index[up], low[up], high[up], color='black', label='_nolegend_') |
| 138 | + ax2.vlines(r.index[~up], low[~up], high[~up], |
| 139 | + color='black', label='_nolegend_') |
| 140 | + ma20 = moving_average(prices, 20, type='simple') |
| 141 | + ma200 = moving_average(prices, 200, type='simple') |
| 142 | + |
| 143 | + linema20, = ax2.plot(r.index, ma20, color='blue', lw=2, label='MA (20)') |
| 144 | + linema200, = ax2.plot(r.index, ma200, color='red', lw=2, label='MA (200)') |
| 145 | + |
| 146 | + last = r.tail(1) |
| 147 | + s = '%s O:%1.2f H:%1.2f L:%1.2f C:%1.2f, V:%1.1fM Chg:%+1.2f' % ( |
| 148 | + last.index.strftime('%Y.%m.%d')[0], |
| 149 | + last.Open, last.High, |
| 150 | + last.Low, last.Close, |
| 151 | + last.Volume * 1e-6, |
| 152 | + last.Close - last.Open) |
| 153 | + t4 = ax2.text(0.3, 0.9, s, transform=ax2.transAxes, fontsize=textsize) |
| 154 | + |
| 155 | + props = font_manager.FontProperties(size=10) |
| 156 | + leg = ax2.legend(loc='center left', shadow=True, fancybox=True, prop=props) |
| 157 | + leg.get_frame().set_alpha(0.5) |
| 158 | + |
| 159 | + |
| 160 | + volume = (r.Close * r.Volume) / 1e6 # dollar volume in millions |
| 161 | + vmax = volume.max() |
| 162 | + poly = ax2t.fill_between(r.index, volume, 0, label='Volume', |
| 163 | + facecolor=fillcolor, edgecolor=fillcolor) |
| 164 | + ax2t.set_ylim(0, 5 * vmax) |
| 165 | + ax2t.set_yticks([]) |
| 166 | + |
| 167 | + |
| 168 | + # compute the MACD indicator |
| 169 | + fillcolor = 'darkslategrey' |
| 170 | + nslow = 26 |
| 171 | + nfast = 12 |
| 172 | + nema = 9 |
| 173 | + emaslow, emafast, macd = moving_average_convergence( |
| 174 | + prices, nslow=nslow, nfast=nfast) |
| 175 | + ema9 = moving_average(macd, nema, type='exponential') |
| 176 | + ax3.plot(r.index, macd, color='black', lw=2) |
| 177 | + ax3.plot(r.index, ema9, color='blue', lw=1) |
| 178 | + ax3.fill_between(r.index, macd - ema9, 0, alpha=0.5, |
| 179 | + facecolor=fillcolor, edgecolor=fillcolor) |
| 180 | + |
| 181 | + |
| 182 | + ax3.text(0.025, 0.95, 'MACD (%d, %d, %d)' % (nfast, nslow, nema), va='top', |
| 183 | + transform=ax3.transAxes, fontsize=textsize) |
| 184 | + |
| 185 | + # ax3.set_yticks([]) |
| 186 | + # turn off upper axis tick labels, rotate the lower ones, etc |
| 187 | + for ax in ax1, ax2, ax2t, ax3: |
| 188 | + if ax != ax3: |
| 189 | + for label in ax.get_xticklabels(): |
| 190 | + label.set_visible(False) |
| 191 | + else: |
| 192 | + for label in ax.get_xticklabels(): |
| 193 | + label.set_rotation(30) |
| 194 | + label.set_horizontalalignment('right') |
| 195 | + |
| 196 | + ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d') |
| 197 | + |
| 198 | + |
| 199 | + class MyLocator(mticker.MaxNLocator): |
| 200 | + def __init__(self, *args, **kwargs): |
| 201 | + mticker.MaxNLocator.__init__(self, *args, **kwargs) |
| 202 | + |
| 203 | + def __call__(self, *args, **kwargs): |
| 204 | + return mticker.MaxNLocator.__call__(self, *args, **kwargs) |
| 205 | + |
| 206 | + # at most 5 ticks, pruning the upper and lower so they don't overlap |
| 207 | + # with other ticks |
| 208 | + # ax2.yaxis.set_major_locator(mticker.MaxNLocator(5, prune='both')) |
| 209 | + # ax3.yaxis.set_major_locator(mticker.MaxNLocator(5, prune='both')) |
| 210 | + |
| 211 | + |
| 212 | + ax2.yaxis.set_major_locator(MyLocator(5, prune='both')) |
| 213 | + ax3.yaxis.set_major_locator(MyLocator(5, prune='both')) |
| 214 | + |
| 215 | + buf = io.BytesIO() |
| 216 | + plt.savefig(buf) |
0 commit comments