Bill Orton
Bill Orton

Reputation: 135

How do I get the y axis display of a matplotlib OHLC candlestick chart to show the OHLC values rather than the y axis cursor position

I am using matplotlib to create an OHLC candlestick stock price chart. I am using mpl_finance's candlestick_ohlc module to create the chart. Creating the chart is straightforward, however the x and y axis display at the bottom of the chart shows the date and y axis value for a given cursor position but I would like the x and y axis display to show the date and that dates open, high, low, close (ohlc) values rather than just a date and a y axis cursor position value. The quotes dataset is in the format of a list of tuples where each tuple contains the date as a number followed by the open, high, low, close and volume.

I am trying to use matplotlib's format_coord function to specify the ohlc values but I can not figure out how to get the format_coord function to accept the list containing the dates and associated ohlc values as an input and to then give the desired date and OHLC output. The following is some simplified code I have written that exhibits my issue: The code below has now been modified so that it fully works:

import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, WeekdayLocator, DayLocator, MONDAY
from mpl_finance import candlestick_ohlc
from matplotlib.dates import date2num, num2date


def ohlc_daily_date_axis():
    mondays = WeekdayLocator(MONDAY)  
    alldays = DayLocator()            
    weekFormatter = DateFormatter('%b %d %Y')  # e.g., Jan 12 2018

    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]

    fig, ax = plt.subplots(figsize=(18,5))
    plt.subplots_adjust(bottom=0.2)
    ax.xaxis.set_major_locator(mondays)
    ax.xaxis.set_minor_locator(alldays)
    ax.xaxis.set_major_formatter(weekFormatter)

    candlestick_ohlc(ax, quotes, width=0.6)

    ax.xaxis_date()
    ax.autoscale_view()
    plt.setp(plt.gca().get_xticklabels(), rotation=45, 
horizontalalignment='right')
    #the following line puts the ohlc data in the y axis display
    ax.format_coord = get_ohlc_from_date_xy
    # the following line puts the ohlc data in the x axis display
    #ax.fmt_xdata = get_ohlc_from_date_x

    plt.show()

def get_ohlc_from_date_x(dateasnum):
    print('dateasnum: ', int(dateasnum))
    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]
    for i in range(len(quotes)):
        if int(dateasnum) == quotes[i][0]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    dte = str(num2date(dateasnum).date())
    print('type(dte): ', type(dte))
    print('open: ', open)
    ohlc_str = dte + ' open: ' + str(open) + ' high: ' + str(high) + ' 
low: ' + str(low) + ' close: ' + str(close) + ' vol: ' + str(int(vol)) 
+ '    '

    return ohlc_str



def get_ohlc_from_date_xy(dateasnum,y):
    print('dateasnum: ', int(dateasnum))
    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]
    for i in range(len(quotes)):
        if int(dateasnum) == quotes[i][0]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    dte = str(num2date(dateasnum).date())
    #print('type(dte): ', type(dte))
    #print('open: ', open)
    ohlc_str = 'open: ' + str(open) + ' high: ' + str(high) + ' 
low: ' + str(low) + ' close: ' + str(close) + ' vol: ' + str(int(vol))

    return dte, ohlc_str



# This def does not work
def format_coord(x,y, quotes):
    for i in range(len(quotes)):
        if int(x) == quotes[i]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    y = 'open: ' + open # I'm just using open to simplify things
    x = DateFormatter('%b %d %Y')
    return (x,y)


if __name__ == '__main__':
    ohlc_daily_date_axis()

If I run this code as it is I get the following error (This is the error I got when I was using the incorrect def format_coord(x,y, quotes) approach):

File "/Users/Me/Mee/python_db_programs/learn_matplotlib_test.py", line 33, 
in ohlc_daily_date_axis
    ax.format_coord = format_coord(quotes)
TypeError: format_coord() missing 2 required positional arguments: 'y' 
and 'quotes'

If I comment out the ax.format_coord = format_coord(quotes) line then the code runs fine but without my desired date and ohlc values in the x and y display. Any help on how to proceed would be greatly appreciated.

I ended up not trying to change the y display but instead added the ohlc values to the x display. Which means I changed ax.format_coord = format_coord(quotes) to the command that just formats the x coordinate, which is ax.fmt_xdata and then wrote a def that used the quotes list to get each dates corresponding ohlc data:

ax.fmt_xdata = get_ohlc_from_date

instead of

ax.format_coord = format_coord(quotes)

and then added this def:

def get_ohlc_from_date(dateasnum):
    print('dateasnum: ', int(dateasnum))
    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]
    for i in range(len(quotes)):
        if int(dateasnum) == quotes[i][0]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    dte = str(num2date(dateasnum).date())
    print('type(dte): ', type(dte))
    print('open: ', open)
    ohlc_str = dte + ' open: ' + str(open) + ' high: ' + str(high) + ' 
low: ' + str(low) + ' close: ' + str(close) + ' vol: ' + str(int(vol)) 
+ '    '

    return ohlc_str

Also because I used matplotlibs dateasnum function I had to import that as well:

from matplotlib.dates import num2date

While this does not replace the y axis coordinate with the ohlc values it does provide the ohlc values in the x and y axis display

After figuring out how to add the ohlc values to the x axis display I realized that the logic I used to add the ohlc values to the x axis display could be applied to the y axis display allowing for the display of the ohlc values in the y axis parameter. This was accomplished by using the ax.format_coord = format_coord command and creating a new def that assigned the ohlc values to the y axis return value. I have modified the original code I posted so that depending on whether or not the ax.format_coord = format_coord line or the ax.fmt_xdata = get_ohlc_from_date line is commented out determines if the ohlc values are displayed as part of the x axis display or as part of the y axis display

Upvotes: 0

Views: 878

Answers (2)

Marcos Felipe Rocha
Marcos Felipe Rocha

Reputation: 1

You can use Plotly's CandleStick Chart, with have all that you want built-in.

Here is a example

Sequence of tuple to Dataframe

To do this you need your data to be inside a dataframe with columns ["Date", "High", "Low", "Open", "Close"], take a look at pandas.DataFrame.from_records to import your data to a DataFrame as it creates a DataFrame object from a sequence of tuples.

Other

  • You might need to convert the dates to datetime, look at pandas.to_datetime to convert it

The Answer to the question

From the plotly's documentation:

hoverinfo – Determines which trace information appear on hover. If none or skip are set, no information is displayed upon hovering. But, if none is set, click and hover events are still fired.

Also it is worth to read: Hover Text and Formatting in Python

Final consideration

I know it is not made with matplotlib as you wanted, but I think it is relevant as an answer.

Code

def generatePlotly(df):
    layout = go.Layout(
        plot_bgcolor="#FFF",      # Sets background color to white
        hovermode="x",
        hoverdistance=100,        # Distance to show hover label of data point
        spikedistance=1000,       # Distance to show spike
        xaxis=dict(
            title="Data",         # X Axis Title
            linecolor="#BCCCDC",  # Sets color of X-axis line
            showgrid=False,       # Removes X-axis grid lines
            showspikes=True,      # Show spike line for X-axis
            gridcolor="#BCCCDC",  # Grid color, if enabled
            # Format spike - Show a Line at the pointer
            spikethickness=2,
            spikedash="dot",
            spikecolor="#999999",
            spikemode="across",
            fixedrange=True,
            spikesnap="cursor",
        ),
        yaxis=dict(
            title="Preço (R$)",   # Y Axis Title
            linecolor="#BCCCDC",  # Sets color of Y-axis line
            showgrid=False,       # Removes Y-axis grid lines
            gridcolor="#BCCCDC",  # Grid color, if enabled
            showspikes=True,      # Show spike line for X-axis
            # Format spike - Show a Line at the pointer
            spikethickness=2,
            spikedash="dot",
            spikecolor="#999999",
            spikemode="across",
            fixedrange=True,
            side="right",
            spikesnap="cursor",
        ),
        margin=go.layout.Margin(
            l=0,  # left margin
            r=0,  # right margin
            b=0,  # bottom margin
            t=0,  # top margin
        ),
    )

    fig = go.Figure(
        data=[
            go.Candlestick(
                x=df["Date"],      # Your data
                open=df["Open"],   
                high=df["High"],
                low=df["Low"],
                close=df["Close"],
            )
        ],
        layout=layout,
    )
    # Remove rangeslider from the chart, you can just comment the next line
    fig.update_layout(xaxis_rangeslider_visible=False)

    # Legend position
    fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01)) 

    ############################################################
    # For this part look at EXTRAS at the bottom of the answer #
    ############################################################
    # build complete timepline from start date to end date
    dt_all = pd.date_range(start=df["Date"].iloc[0], end=df["Date"].iloc[-1])
    # retrieve the dates that ARE in the original datset
    dt_obs = [d.strftime("%Y-%m-%d") for d in pd.to_datetime(df["Date"])]
    # define dates with missing values
    dt_breaks = [d for d in dt_all.strftime("%Y-%m-%d").tolist() if not d in dt_obs]
    fig.update_xaxes(
        rangebreaks=[
            # dict(bounds=["sat", "mon"]),  # hide weekends
            dict(values=dt_breaks)
        ]
    )

    # Hover Distance and Hover Info
    # fig.update_layout(hoverdistance=0)
    # fig.update_traces(xaxis="x", hoverinfo="none")

    fig.show()

Extras

Plotly: How to style a plotly figure so that it doesn't display gaps for missing dates?

Upvotes: 0

Bill Orton
Bill Orton

Reputation: 135

Below is the solution which allows for the x and y axis readout of a matplotlib OHLC candlestick chart to display the OHLC values in the Y axis readout rather than the Y axis cursor position.

import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, WeekdayLocator, DayLocator, 
MONDAY
from mpl_finance import candlestick_ohlc
from matplotlib.dates import date2num, num2date


def ohlc_daily_date_axis():
    mondays = WeekdayLocator(MONDAY)  
    alldays = DayLocator()            
    weekFormatter = DateFormatter('%b %d %Y')  # e.g., Jan 12 2018

    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]

    fig, ax = plt.subplots(figsize=(18,5))
    plt.subplots_adjust(bottom=0.2)
    ax.xaxis.set_major_locator(mondays)
    ax.xaxis.set_minor_locator(alldays)
    ax.xaxis.set_major_formatter(weekFormatter)

    candlestick_ohlc(ax, quotes, width=0.6)

    ax.xaxis_date()
    ax.autoscale_view()
    plt.setp(plt.gca().get_xticklabels(), rotation=45, 
horizontalalignment='right')
    #the following line puts the ohlc data in the y axis display
    ax.format_coord = get_ohlc_from_date_xy
    # the following line puts the ohlc data in the x axis display
    #ax.fmt_xdata = get_ohlc_from_date_x

    plt.show()

def get_ohlc_from_date_x(dateasnum):
    print('dateasnum: ', int(dateasnum))
    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]
    for i in range(len(quotes)):
        if int(dateasnum) == quotes[i][0]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    dte = str(num2date(dateasnum).date())
    print('type(dte): ', type(dte))
    print('open: ', open)
    ohlc_str = dte + ' open: ' + str(open) + ' high: ' + str(high) + ' 
low: ' + str(low) + ' close: ' + str(close) + ' vol: ' + str(int(vol)) 
+ '    '

    return ohlc_str



def get_ohlc_from_date_xy(dateasnum,y):
    print('dateasnum: ', int(dateasnum))
    quotes = [(737042.0, 2.72, 2.78, 2.6815, 2.74, 414378.0),
              (737045.0, 2.71, 2.77, 2.57, 2.63, 578841.0),
              (737046.0, 2.64, 2.64, 2.4228, 2.47, 1451450.0),
              (737047.0, 2.9, 3.15, 2.7, 2.96, 7230260.0),
              (737048.0, 2.92, 3.29, 2.67, 2.83, 2784110.0),
              (737049.0, 2.78, 2.82, 2.4701, 2.51, 822776.0),
              (737052.0, 2.56, 2.6344, 2.49, 2.5, 278883.0),
              (737054.0, 2.5, 2.619, 2.34, 2.6, 606002.0),
              (737055.0, 2.57, 2.63, 2.45, 2.57, 1295820.0),
              (737056.0, 2.57, 2.75, 2.51, 2.65, 435838.0)]
    for i in range(len(quotes)):
        if int(dateasnum) == quotes[i][0]:
            open = quotes[i][1]
            high = quotes[i][2]
            low = quotes[i][3]
            close = quotes[i][4]
            vol = quotes[i][5]
    dte = str(num2date(dateasnum).date())
    #print('type(dte): ', type(dte))
    #print('open: ', open)
    ohlc_str = 'open: ' + str(open) + ' high: ' + str(high) + ' 
low: ' + str(low) + ' close: ' + str(close) + ' vol: ' + str(int(vol))

    return dte, ohlc_str






if __name__ == '__main__':
    ohlc_daily_date_axis()

Upvotes: 0

Related Questions