Jack022
Jack022

Reputation: 1257

How to plot an horizontal line between two datapoints on Matplotlib?

I created a candlestick chart on Matplotlib and now I would like to draw more stuff on it.

Here is my code::

...
fig = plt.figure(facecolor='#131722',dpi=135)
#ax = fig.add_subplot(1,1,1)
ax1 = plt.subplot2grid((6,4), (1,0), rowspan=4, colspan=4, facecolor='#131722')

candlestick2_ohlc(ax1, opens, highs, lows, closes, width=FINALWIDTH, alpha=1,colorup='#53B987', colordown='#EB4D5C')
ax1.xaxis.set_major_locator(mticker.MaxNLocator(8))
xdate = [datetime.fromtimestamp(i) for i in dates]


for label in ax1.xaxis.get_ticklabels():
    label.set_rotation(20)

def mydate(x,pos=None):
    try:
        if CandleFrame == '1D' or CandleFrame == '4H':
            return xdate[int(x)].strftime('%m/%d %H:%M')
        else:
            t = xdate[int(x)].strftime('%m/%d %H:%M')
            print(t)
            return xdate[int(x)].strftime('%m/%d %H:%M')

    except IndexError:
        return ''
        #return pl.num2date(x).strftime('%Y-%m-%d')



ax1.xaxis.set_major_formatter(mticker.FuncFormatter(mydate))
ax1.grid(False, color='#242938', alpha=0.5, ls='dotted')
ax1.spines['bottom'].set_color("#131722")
ax1.spines['top'].set_color("#131722")
ax1.spines['left'].set_color("#131722")
ax1.spines['right'].set_color("#131722")
ax1.tick_params(axis='both', colors='w')
ax1.set_axisbelow(True)
plt.gca().yaxis.set_major_locator(mticker.MaxNLocator())


try:
    plt.hlines(y=9125, xmin='05/13 05:30', xmax='05/13 10:30', color='g')

except Exception as e:
    print(e)


plt.cla()
plt.close()
...
fig = plt.figure(facecolor='#131722',dpi=135)
#ax = fig.add_subplot(1,1,1)
ax1 = plt.subplot2grid((6,4), (1,0), rowspan=4, colspan=4, facecolor='#131722')

candlestick2_ohlc(ax1, opens, highs, lows, closes, width=FINALWIDTH, alpha=1,colorup='#53B987', colordown='#EB4D5C')
ax1.xaxis.set_major_locator(mticker.MaxNLocator(8))
xdate = [datetime.fromtimestamp(i) for i in dates]


for label in ax1.xaxis.get_ticklabels():
    label.set_rotation(20)

def mydate(x,pos=None):
    try:
        if CandleFrame == '1D' or CandleFrame == '4H':
            return xdate[int(x)].strftime('%m/%d %H:%M')
        else:
            t = xdate[int(x)].strftime('%m/%d %H:%M')
            print(t)
            return xdate[int(x)].strftime('%m/%d %H:%M')

    except IndexError:
        return ''
        #return pl.num2date(x).strftime('%Y-%m-%d')



ax1.xaxis.set_major_formatter(mticker.FuncFormatter(mydate))
ax1.grid(False, color='#242938', alpha=0.5, ls='dotted')
ax1.spines['bottom'].set_color("#131722")
ax1.spines['top'].set_color("#131722")
ax1.spines['left'].set_color("#131722")
ax1.spines['right'].set_color("#131722")
ax1.tick_params(axis='both', colors='w')
ax1.set_axisbelow(True)
plt.gca().yaxis.set_major_locator(mticker.MaxNLocator())

plt.cla()
plt.close()

Here is what the data on x axis looks like:

[datetime.datetime(2020, 5, 14, 22, 40), datetime.datetime(2020, 5, 14, 22, 45), datetime.datetime(2020, 5, 14, 22, 50), datetime.datetime(2020, 5, 14, 22, 55), datetime.datetime(2020, 5, 14, 23, 0), datetime.datetime(2020, 5, 14, 23, 5), datetime.datetime(2020, 5, 14, 23, 10), datetime.datetime(2020, 5, 14, 23, 15), datetime.datetime(2020, 5, 14, 23, 20), datetime.datetime(2020, 5, 14, 23, 25), datetime.datetime(2020, 5, 14, 23, 30), datetime.datetime(2020, 5, 14, 23, 35), datetime.datetime(2020, 5, 14, 23, 40), datetime.datetime(2020, 5, 14, 23, 45), datetime.datetime(2020, 5, 14, 23, 50), datetime.datetime(2020, 5, 14, 23, 55), datetime.datetime(2020, 5, 15, 0, 0), datetime.datetime(2020, 5, 15, 0, 5), datetime.datetime(2020, 5, 15, 0, 10), datetime.datetime(2020, 5, 15, 0, 15), datetime.datetime(2020, 5, 15, 0, 20), datetime.datetime(2020, 5, 15, 0, 25), datetime.datetime(2020, 5, 15, 0, 30), datetime.datetime(2020, 5, 15, 0, 35), datetime.datetime(2020, 5, 15, 0, 40), datetime.datetime(2020, 5, 15, 0, 45), datetime.datetime(2020, 5, 15, 0, 50), datetime.datetime(2020, 5, 15, 0, 55), datetime.datetime(2020, 5, 15, 1, 0), datetime.datetime(2020, 5, 15, 1, 5), datetime.datetime(2020, 5, 15, 1, 10), datetime.datetime(2020, 5, 15, 1, 15), datetime.datetime(2020, 5, 15, 1, 20), datetime.datetime(2020, 5, 15, 1, 25), datetime.datetime(2020, 5, 15, 1, 30), datetime.datetime(2020, 5, 15, 1, 35), datetime.datetime(2020, 5, 15, 1, 40), datetime.datetime(2020, 5, 15, 1, 45), datetime.datetime(2020, 5, 15, 1, 50), datetime.datetime(2020, 5, 15, 1, 55), datetime.datetime(2020, 5, 15, 2, 0), datetime.datetime(2020, 5, 15, 2, 5), datetime.datetime(2020, 5, 15, 2, 10), datetime.datetime(2020, 5, 15, 2, 15), datetime.datetime(2020, 5, 15, 2, 20), datetime.datetime(2020, 5, 15, 2, 25), datetime.datetime(2020, 5, 15, 2, 30), datetime.datetime(2020, 5, 15, 2, 35), datetime.datetime(2020, 5, 15, 2, 40), datetime.datetime(2020, 5, 15, 2, 45), datetime.datetime(2020, 5, 15, 2, 50), datetime.datetime(2020, 5, 15, 2, 55), datetime.datetime(2020, 5, 15, 3, 0), datetime.datetime(2020, 5, 15, 3, 5), datetime.datetime(2020, 5, 15, 3, 10), datetime.datetime(2020, 5, 15, 3, 15), datetime.datetime(2020, 5, 15, 3, 20), datetime.datetime(2020, 5, 15, 3, 25), datetime.datetime(2020, 5, 15, 3, 30), datetime.datetime(2020, 5, 15, 3, 35), datetime.datetime(2020, 5, 15, 3, 40), datetime.datetime(2020, 5, 15, 3, 45), datetime.datetime(2020, 5, 15, 3, 50), datetime.datetime(2020, 5, 15, 3, 55), datetime.datetime(2020, 5, 15, 4, 0), datetime.datetime(2020, 5, 15, 4, 5), datetime.datetime(2020, 5, 15, 4, 10), datetime.datetime(2020, 5, 15, 4, 15), datetime.datetime(2020, 5, 15, 4, 20), datetime.datetime(2020, 5, 15, 4, 25), datetime.datetime(2020, 5, 15, 4, 30), datetime.datetime(2020, 5, 15, 4, 35), datetime.datetime(2020, 5, 15, 4, 40), datetime.datetime(2020, 5, 15, 4, 45), datetime.datetime(2020, 5, 15, 4, 50), datetime.datetime(2020, 5, 15, 4, 55), datetime.datetime(2020, 5, 15, 5, 0), datetime.datetime(2020, 5, 15, 5, 5), datetime.datetime(2020, 5, 15, 5, 10), datetime.datetime(2020, 5, 15, 5, 15), datetime.datetime(2020, 5, 15, 5, 20), datetime.datetime(2020, 5, 15, 5, 25), datetime.datetime(2020, 5, 15, 5, 30), datetime.datetime(2020, 5, 15, 5, 35), datetime.datetime(2020, 5, 15, 5, 40), datetime.datetime(2020, 5, 15, 5, 45), datetime.datetime(2020, 5, 15, 5, 50), datetime.datetime(2020, 5, 15, 5, 55), datetime.datetime(2020, 5, 15, 6, 0), datetime.datetime(2020, 5, 15, 6, 5), datetime.datetime(2020, 5, 15, 6, 10), datetime.datetime(2020, 5, 15, 6, 15), datetime.datetime(2020, 5, 15, 6, 20), datetime.datetime(2020, 5, 15, 6, 25), datetime.datetime(2020, 5, 15, 6, 30), datetime.datetime(2020, 5, 15, 6, 35), datetime.datetime(2020, 5, 15, 6, 40), datetime.datetime(2020, 5, 15, 6, 45), datetime.datetime(2020, 5, 15, 6, 50), datetime.datetime(2020, 5, 15, 6, 55), datetime.datetime(2020, 5, 15, 7, 0), datetime.datetime(2020, 5, 15, 7, 5), datetime.datetime(2020, 5, 15, 7, 10), datetime.datetime(2020, 5, 15, 7, 15), datetime.datetime(2020, 5, 15, 7, 20), datetime.datetime(2020, 5, 15, 7, 25), datetime.datetime(2020, 5, 15, 7, 30), datetime.datetime(2020, 5, 15, 7, 35), datetime.datetime(2020, 5, 15, 7, 40), datetime.datetime(2020, 5, 15, 7, 45), datetime.datetime(2020, 5, 15, 7, 50), datetime.datetime(2020, 5, 15, 7, 55), datetime.datetime(2020, 5, 15, 8, 0), datetime.datetime(2020, 5, 15, 8, 5), datetime.datetime(2020, 5, 15, 8, 10), datetime.datetime(2020, 5, 15, 8, 15), datetime.datetime(2020, 5, 15, 8, 20), datetime.datetime(2020, 5, 15, 8, 25), datetime.datetime(2020, 5, 15, 8, 30), datetime.datetime(2020, 5, 15, 8, 35), datetime.datetime(2020, 5, 15, 8, 40), datetime.datetime(2020, 5, 15, 8, 45), datetime.datetime(2020, 5, 15, 8, 50), datetime.datetime(2020, 5, 15, 8, 55), datetime.datetime(2020, 5, 15, 9, 0), datetime.datetime(2020, 5, 15, 9, 5), datetime.datetime(2020, 5, 15, 9, 10), datetime.datetime(2020, 5, 15, 9, 15), datetime.datetime(2020, 5, 15, 9, 20), datetime.datetime(2020, 5, 15, 9, 25), datetime.datetime(2020, 5, 15, 9, 30), datetime.datetime(2020, 5, 15, 9, 35), datetime.datetime(2020, 5, 15, 9, 40), datetime.datetime(2020, 5, 15, 9, 45), datetime.datetime(2020, 5, 15, 9, 50), datetime.datetime(2020, 5, 15, 9, 55), datetime.datetime(2020, 5, 15, 10, 0), datetime.datetime(2020, 5, 15, 10, 5), datetime.datetime(2020, 5, 15, 10, 10), datetime.datetime(2020, 5, 15, 10, 15)]

Here is what the chart looks like: enter image description here

In parallel, I have an array of data that looks like this:

myData = [[9320, datetime.datetime(2020, 5, 15, 00, 20)'05/15 00:20'], [9440, datetime.datetime(2020, 5, 15, 8, 43)] ... ]

What I'm trying to do is to plot this array into the candlestick chart. So for example, there should be a small line, or a circle, or a small rectangle (whatever to visualize it correctly) on the chart at x=9320 below the candle that corresponds to the time '05/15 00:20', so it should be as large as the candle at that x point.

An example of the expected output:

What I tried:

plt.hlines(y=9320, xmin=?, xmax=?, color='g')

The problem with this solution: I can locate the y axis easily, but I don't understand how to locate it on the x axis.

Is there any way to do this? What can I do? A subplot, for example?

Upvotes: 2

Views: 1759

Answers (1)

Salvatore
Salvatore

Reputation: 11924

There are a couple of methods. The easiest I found was to copy the patch (or rectancle/bar) of the data point you care about, and then set it's y value to something new.

Since you didn't provide data, I'm pulling some old stock prices for ebay as an example. I couldn't get the exact version of matplotlib.finance you are using, as it is deprecated. I might install an old version in a virtual environment later, but I did get this to work in the up to date mplfinance library though with a function that is nearly identical to the one you are using (I'll include how you can update to this module at the end):

From the Matplotlib API:

matplotlib.finance.candlestick_ochl(...) returns (lines, patches) where lines is a list of lines added and patches is a list of the rectangle patches added

So, I grab those patches from the return of candlestick_ochl, copy the one you care about, and then change some of its attributes like color and position. All of the attributes and how to change them are in the documentation:

import copy 
...

lines, patches = candlestick_ohlc(ax1, ohlc[:5], width=0.4, colorup='#77d879', colordown='#db3f3f')

new_patch = copy.copy(patches[-3]) # must use copy, or you'll modify the original Rectangle. This grabs the 3rd one (right to left)
new_patch.set_y(148)
new_patch.set_color('white')
new_patch.set_height(0.3)

You can also print it to see some of its info:

print(new_patch)

Rectangle(xy=(736534, 148), width=0.4, height=0.3, angle=0)

Then, when you do your plotting, add it to the plot and readjust your view:

# Add the patch to the Axes
ax1.add_patch(new_patch)
ax1.autoscale_view()

I've zoomed in on the data set to show you the patch. It's the white one under the third candlestick:

enter image description here

Zoomed out (look for the little white bar):

enter image description here
enter image description here

You can also make a new rectangle instead of copying an old one:

from matplotlib.patches import Rectangle
...

new_patch = Rectangle(xy=(736530, 155), width=0.4, height=0.3, angle=0, color='white')
ax1.add_patch(new_patch)
ax1.autoscale_view()

You could even define a function and pass it the patch you care about to make this really easy:

def add_highlight(patch_to_highlight, y_position, color, height): 
    new_patch = copy.copy(patch_to_highlight)
    new_patch.set_y(y_position)
    new_patch.set_color(color)
    new_patch.set_height(height)
    ax1.add_patch(new_patch)
    ax1.autoscale_view()

And here's my full code. It works with the most up to date mplfinanace:

import copy
import urllib
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import Rectangle
import matplotlib.ticker as mticker
from mplfinance.original_flavor import candlestick_ohlc


def bytespdate2num(fmt, encoding='utf-8'):
    strconverter = mdates.strpdate2num(fmt)
    def bytesconverter(b):
        s = b.decode(encoding)
        return strconverter(s)
    return bytesconverter

def add_highlight(ax, patch_to_highlight, y_position, color='white', height=0.3):
    new_patch = copy.copy(patch_to_highlight)
    new_patch.set_y(y_position)
    new_patch.set_color(color)
    new_patch.set_height(height)
    ax.add_patch(new_patch)
    ax.autoscale_view()

def graph_data(stock):
    # This is some old ebay stock price data
    stock_price_url = 'https://pythonprogramming.net/yahoo_finance_replacement'
    source_code = urllib.request.urlopen(stock_price_url).read().decode()
    stock_data = []
    split_source = source_code.split('\n')
    for line in split_source[1:]:
        split_line = line.split(',')
        if len(split_line) == 7:
            if 'values' not in line and 'labels' not in line:
                stock_data.append(line)

    # parse and organize the data
    date, closep, highp, lowp, openp, _, volume = np.loadtxt(stock_data, delimiter=',', unpack=True, converters={0: bytespdate2num('%Y-%m-%d')})
    x = 0
    y = len(date)
    ohlc = []
    while x < y:
        append_me = date[x], openp[x], highp[x], lowp[x], closep[x], volume[x]
        ohlc.append(append_me)
        x+=1

    # do the plotting
    plt.style.use('dark_background')
    plt.figure()
    ax1 = plt.subplot2grid((1, 1), (0, 0))

    lines, patches = candlestick_ohlc(ax1, ohlc[:5], width=0.4, colorup='#53B987', colordown='#EB4D5C')

    for label in ax1.xaxis.get_ticklabels():
        label.set_rotation(45)

    ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    ax1.xaxis.set_major_locator(mticker.MaxNLocator(10))

    ## Method with copying existing patch
    # new_patch = copy.copy(patches[-3])
    # new_patch.set_y(148)
    # new_patch.set_color('orange')
    # new_patch.set_height(0.3)
    # ax1.add_patch(new_patch)
    # ax1.autoscale_view()

    ## Method with making new Rectangle
    # new_patch = Rectangle(xy=(736530, 155), width=0.4, height=0.3, angle=0, color='orange')
    # ax1.add_patch(new_patch)
    # ax1.autoscale_view()

    ## Method using a function (cleanest)
    add_highlight(ax1, patches[-3], 136)

    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.title(stock)
    plt.subplots_adjust(left=0.09, bottom=0.20, right=0.94, top=0.90, wspace=0.2, hspace=0)
    plt.tight_layout()
    plt.savefig('example.png')
    plt.show()

graph_data('EBAY')


Updating to mplfinance

To get the current mplfinance, use pip: pip install mplfinance

The documentation is here

The only thing you should have to change in your code is your import:

from mplfinance.original_flavor import candlestick_ohlc

And this function call (the name is different, and you need the returned values):

lines, patches = candlestick_ohlc((ax1, opens, highs, lows, closes, width=FINALWIDTH, alpha=1, colorup='#53B987', colordown='#EB4D5C')

Upvotes: 2

Related Questions