The candlestick plot in the current form is a bit bland. Traders would usually overlay stock indicators such as average true range (ATR), Bollinger band, commodity channel index (CCI), exponential moving average (EMA), moving average convergence divergence (MACD), relative strength index (RSI), and other various stats for technical analysis.
Stockstats (https://github.com/jealous/stockstats) is a great package for calculating the preceding indicators/stats and much more. It wraps around pandas DataFrames and generate that stats on the fly when they are accessed.
In this section, we can convert a pandas DataFrame to a stockstats DataFrame via stockstats.StockDataFrame.retype(). A plethora of stock indicators can then be accessed by following the pattern StockDataFrame["variable_timeWindow_indicator"]. For example, StockDataFrame['open_2_sma'] would give us 2-day simple moving average on opening price. Shortcuts may be available for some indicators, so please consult the official documentation for more information.
The file views2.py in our code repository contains the code to create an extended Bitcoin pricing view. You can copy views2.py from this chapter's code repository to crypto_stats/src/bitcoin, and rename it as views.py.
Here are the important changes to our previous code:
# FuncFormatter to convert tick values to Millions
def millions(x, pos):
return '%dM' % (x/1e6)
def bitcoin_chart(request):
# Get a dataframe of bitcoin prices
bitcoin_df = get_bitcoin_dataset()
# candlestick_ohlc expects Date (in floating point number), Open, High, Low, Close columns only
# So we need to select the useful columns first using DataFrame.loc[]. Extra columns can exist,
# but they are ignored. Next we get the data for the last 30 trading only for simplicity of plots.
candlestick_data = bitcoin_df.loc[:, ["Datetime",
"Open",
"High",
"Low",
"Close",
"Volume (Currency)"]].iloc[:30]
# Convert to StockDataFrame
# Need to pass a copy of candlestick_data to StockDataFrame.retype
# Otherwise the original candlestick_data will be modified
stockstats = StockDataFrame.retype(candlestick_data.copy())
# 5-day exponential moving average on closing price
ema_5 = stockstats["close_5_ema"]
# 10-day exponential moving average on closing price
ema_10 = stockstats["close_10_ema"]
# 30-day exponential moving average on closing price
ema_30 = stockstats["close_30_ema"]
# Upper Bollinger band
boll_ub = stockstats["boll_ub"]
# Lower Bollinger band
boll_lb = stockstats["boll_lb"]
# 7-day Relative Strength Index
rsi_7 = stockstats['rsi_7']
# 14-day Relative Strength Index
rsi_14 = stockstats['rsi_14']
# Create 3 subplots spread across three rows, with shared x-axis.
# The height ratio is specified via gridspec_kw
fig, axarr = plt.subplots(nrows=3, ncols=1, sharex=True, figsize=(8,8),
gridspec_kw={'height_ratios':[3,1,1]})
# Prepare a candlestick plot in the first axes
candlestick_ohlc(axarr[0], candlestick_data.values, width=0.6)
# Overlay stock indicators in the first axes
axarr[0].plot(candlestick_data["Datetime"], ema_5, lw=1, label='EMA (5)')
axarr[0].plot(candlestick_data["Datetime"], ema_10, lw=1, label='EMA (10)')
axarr[0].plot(candlestick_data["Datetime"], ema_30, lw=1, label='EMA (30)')
axarr[0].plot(candlestick_data["Datetime"], boll_ub, lw=2, linestyle="--", label='Bollinger upper')
axarr[0].plot(candlestick_data["Datetime"], boll_lb, lw=2, linestyle="--", label='Bollinger lower')
# Display RSI in the second axes
axarr[1].axhline(y=30, lw=2, color = '0.7') # Line for oversold threshold
axarr[1].axhline(y=50, lw=2, linestyle="--", color = '0.8') # Neutral RSI
axarr[1].axhline(y=70, lw=2, color = '0.7') # Line for overbought threshold
axarr[1].plot(candlestick_data["Datetime"], rsi_7, lw=2, label='RSI (7)')
axarr[1].plot(candlestick_data["Datetime"], rsi_14, lw=2, label='RSI (14)')
# Display trade volume in the third axes
axarr[2].bar(candlestick_data["Datetime"], candlestick_data['Volume (Currency)'])
# Label the axes
axarr[0].set_ylabel('Price (US $)')
axarr[1].set_ylabel('RSI')
axarr[2].set_ylabel('Volume (US $)')
axarr[2].xaxis.set_major_locator(WeekdayLocator(MONDAY)) # major ticks on the mondays
axarr[2].xaxis.set_minor_locator(DayLocator()) # minor ticks on the days
axarr[2].xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
axarr[2].xaxis_date() # treat the x data as dates
axarr[2].yaxis.set_major_formatter(FuncFormatter(millions)) # Change the y-axis ticks to millions
plt.setp(axarr[2].get_xticklabels(), rotation=90, horizontalalignment='right') # Rotate x-tick labels by 90 degree
# Limit the x-axis range to the last 30 days
time_now = datetime.datetime.now()
datemin = time_now-datetime.timedelta(days=30)
datemax = time_now
axarr[2].set_xlim(datemin, datemax)
# Show figure legend
axarr[0].legend()
axarr[1].legend()
# Show figure title
axarr[0].set_title("Bitcoin 30-day price trend", loc='left')
plt.tight_layout()
# Create a bytes buffer for saving image
fig_buffer = BytesIO()
plt.savefig(fig_buffer, dpi=150)
# Save the figure as a HttpResponse
response = HttpResponse(content_type='image/png')
response.write(fig_buffer.getvalue())
fig_buffer.close()
return response
The modified bitcoin_chart view would create three subplots that are spread across three rows, with a shared x axis. The height ratio between the subplots is specified via gridspec_kw.
The first subplot would display a candlestick chart as well as various stock indicators from the stockstats package.
The second subplot displays the relative strength index (RSI) of bitcoin across the 30-day window.
Finally, the third subplot displays the volume (USD) of Bitcoin. A custom FuncFormatter millions is used to convert the y axis values to millions.
You can now go to the same link at http://localhost:8000/bitcoin/30/ to view the complete chart.