Reputation: 135
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
Reputation: 1
You can use Plotly's CandleStick Chart, with have all that you want built-in.
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
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
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