4

I am trying to manually create a candlestick chart with matplotlib using errorbar for the daily High and Low prices and Rectangle() for the Adjusted Close and Open prices. This question seemed to have all the prerequisites for accomplishing this.

I attempted to use the above very faithfully, but the issue of plotting something over an x-axis of datetime64[ns]'s gave me no end of errors, so I've additionally tried to incorporate the advice here on plotting over datetime.

This is my code so far, with apologies for the messiness:

import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle

def makeCandles(xdata,high,low,adj_close,adj_open,fc='r',ec='None',alpha=0.5):

    ## Converting datetimes to numerical format matplotlib can understand.
    dates = mdates.date2num(xdata)

    ## Creating default objects
    fig,ax = plt.subplots(1)

    ## Creating errorbar peaks based on high and low prices
    avg = (high + low) / 2
    err = [high - avg,low - avg]

    ax.errorbar(dates,err,fmt='None',ecolor='k')

    ## Create list for all the error patches
    errorboxes = []

    ## Loop over data points; create "body" of candlestick 
    ## based on adjusted open and close prices

    errors=np.vstack((adj_close,adj_open))
    errors=errors.T

    for xc,yc,ye in zip(dates,avg,errors):
        rect = Rectangle((xc,yc-ye[0]),1,ye.sum())
        errorboxes.append(rect)

    ## Create patch collection with specified colour/alpha
    pc = PatchCollection(errorboxes,facecolor=fc,alpha=alpha,edgecolor=ec)

    ## Add collection to axes
    ax.add_collection(pc)

    plt.show()

With my data looking like

enter image description here

This is what I try to run, first getting a price table from quandl,

import quandl as qd
api =  '1uRGReHyAEgwYbzkPyG3'
qd.ApiConfig.api_key = api 

data = qd.get_table('WIKI/PRICES', qopts = { 'columns': ['ticker', 'date', 'high','low','adj_open','adj_close'] }, \
                        ticker = ['AMZN', 'XOM'], date = { 'gte': '2014-01-01', 'lte': '2016-12-31' })
data.reset_index(inplace=True,drop=True)

makeCandles(data['date'],data['high'],data['low'],data['adj_open'],data['adj_close'])

The code runs with no errors, but outputs an empty graph. So what I am asking for is advice on how to plot these rectangles over the datetime dates. For the width of the rectangles, I simply put a uniform "1" bec. I am not aware of a simple way to specify the datetime width of a rectangle.

Edit

This is the plot I am currently getting, having transformed my xdata into matplotlib mdates:

enter image description here

Before I transformed xdata via mdates, with just xdata as my x-axis everywhere, this was one of the errors I kept getting:

enter image description here

halfer
  • 19,824
  • 17
  • 99
  • 186
Coolio2654
  • 1,589
  • 3
  • 21
  • 46
  • Can you share the resulting plot figure? – karlphillip Apr 28 '18 at 03:08
  • It would be so much easier to answer this question if you show us your current result and an example of you are trying to accomplish. Certainly you've seen this visual representation somewhere else. Anyway, you can use `candlestick2_ohlc` from `matplotlib.finance` if you need to plot something quick. – karlphillip Apr 28 '18 at 03:22
  • @karlphillip I have put two pics up in an edit. For a clear graphical example, something I would like to see is this: https://datavizcatalogue.com/methods/images/top_images/SVG/candlestick_chart.svg – Coolio2654 Apr 28 '18 at 03:33
  • My question has all the code I am using now, I apologize for that silly mistake. – Coolio2654 Apr 28 '18 at 18:38

1 Answers1

2

To get the plot you want, there's a couple of things that need to be considered. First you're retrieving to stocks AMZN and XOM, displaying both will make the chart you want look funny, because the data are quite far apart. Second, candlestick charts in which you plot each day for several years will get very crowded. Finally, you need to format your ordinal dates back on the x-axis.

As mentioned in the comments, you can use the pre-built matplotlib candlestick2_ohlc function (although deprecated) accessible through mpl_finance, install as shown in this answer. I opted for using solely the matplotlib barchart with built-in errorbars.

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import quandl as qd
from matplotlib.dates import DateFormatter, WeekdayLocator, \
    DayLocator, MONDAY

# get data
api = '1uRGReHyAEgwYbzkPyG3'
qd.ApiConfig.api_key = api
data = qd.get_table('WIKI/PRICES', qopts={'columns': ['ticker', 'date', 'high', 'low', 'open', 'close']},
                    ticker=['AMZN', 'XOM'], date={'gte': '2014-01-01', 'lte': '2014-03-10'})
data.reset_index(inplace=True, drop=True)

fig, ax = plt.subplots(figsize = (10, 5))
data['date'] = mdates.date2num(data['date'].dt.to_pydatetime()) #convert dates to ordinal
tickers = list(set(data['ticker'])) # unique list of stock names
for stock_ind in tickers:
    df = data[data['ticker'] == 'AMZN'] # select one, can do more in a for loop, but it will look funny

    inc = df.close > df.open
    dec = df.open > df.close

    ax.bar(df['date'][inc],
           df['open'][inc]-df['close'][inc],
           color='palegreen',
           bottom=df['close'][inc],
           # this yerr is confusing when independent error bars are drawn => (https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar)
           yerr = [df['open'][inc]-df['high'][inc], -df['open'][inc]+df['low'][inc]],
           error_kw=dict(ecolor='gray', lw=1))

    ax.bar(df['date'][dec],
           df['close'][dec]-df['open'][dec],
           color='salmon', bottom=df['open'][dec],
           yerr = [df['close'][dec]-df['high'][dec], -df['close'][dec]+df['low'][dec]],
           error_kw=dict(ecolor='gray', lw=1))

    ax.set_title(stock_ind)

#some tweaking, setting the dates
mondays = WeekdayLocator(MONDAY)  # major ticks on the mondays
alldays = DayLocator()  # minor ticks on the days
weekFormatter = DateFormatter('%b %d')  # e.g., Jan 12
dayFormatter = DateFormatter('%d')  # e.g., 12
ax.xaxis.set_major_locator(mondays)
ax.xaxis.set_minor_locator(alldays)
ax.xaxis.set_major_formatter(weekFormatter)
ax.set_ylabel('monies ($)')

plt.show()

Graph

Chris
  • 1,287
  • 12
  • 31
  • I honestly wasn't expecting an answer to my question, even though it's not using `Rectangle` as I approached it, but you have completely satisfied me regardless. Thank you! I have just 2 last thoughts I hope you would explain for me, based on your answer: 1) everything from `ax.xaxis.set_major_locator(mondays)` --> `ax.xaxis.set_major_formatter(weekFormatter)` I do not understand the purpose of, since the code immediately before it seems to specify everything for an x-axis, 2) how on earth does using `df['date'][inc]`, for ex., for the bars' x-values work, since they are only booleans? – Coolio2654 Jun 15 '18 at 20:39
  • 1
    Good to hear! 1) `ax.xaxis.set_major_locator(mondays)` will ensure that the major ticks are on the mondays, as defined by `mondays = WeekdayLocator(MONDAY)`, where `ax.xaxis.set_major_formatter(weekFormatter)` will put the formatted date `%b %d` in that location. 2) The `[inc]` and `[dec]` are indeed a set of booleans, they ensure that when the stock price has decreased that day it will be either printed red or green. The boolean list will plot `df['date'][inc]` will thus plot only the green coloured bars. Hope that helps! – Chris Jun 15 '18 at 20:57