Search code examples
pythondatetimematplotlibfinancerectangles

Plot rectangles over datetime axis in matplotlib?


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


Solution

  • 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