2
import yfinance as yf
import mplfinance as mpf
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import pandas as pd
import numpy as np

# Dates to get stock data
start_date = "2020-01-01"
end_date = "2023-06-15"

# Fetch Tesla stock data
tesla_data = yf.download("TSLA", start=start_date, end=end_date)
tesla_weekly_data = tesla_data.resample("W").agg({"Open": "first", "High": "max", "Low": "min", "Close": "last", "Volume": "sum"}).dropna()

# Get the latest closing price
latest_price = tesla_weekly_data['Close'][-1]

# Create additional plot
close_price = tesla_weekly_data['Close']
apd = mpf.make_addplot(close_price, color='cyan', width=2)

# Plot the candlestick chart
fig, axes = mpf.plot(tesla_weekly_data,
                     type='candle',
                     addplot=apd,
                     style='yahoo',
                     title='Tesla Stock Prices',
                     ylabel='Price',
                     xlabel='Date',
                     volume=True,
                     ylabel_lower='Volume',
                     volume_panel=1,
                     figsize=(16, 8),
                     returnfig=True
                     )

# Move the y-axis labels to the left side
axes[0].yaxis.tick_left()
axes[1].yaxis.tick_left()

# Adjust the position of the y-axis label for price
axes[0].yaxis.set_label_coords(-0.08, 0.5)

# Adjust the position of the y-axis label for volume
axes[1].yaxis.set_label_coords(-0.08, 0.5)

# Set y-axis label for price and volume
axes[0].set_ylabel('Price', rotation=0, labelpad=20)
axes[1].set_ylabel('Volume', rotation=0, labelpad=20)

# Make the legend box
handles = axes[0].get_legend_handles_labels()[0]
red_patch = mpatches.Patch(color='red')
green_patch = mpatches.Patch(color='green')
cyan_patch = mpatches.Patch(color='cyan')
handles = handles[:2] + [red_patch, green_patch, cyan_patch]
labels = ["Price Up", "Price Down", "Closing Price"]
axes[0].legend(handles=handles, labels=labels)

# Add a box to display the current price
latest_price_text = f"Current Price: ${latest_price:.2f}"
box_props = dict(boxstyle='round', facecolor='white', edgecolor='black', alpha=0.8)
axes[0].text(0.02, 0.95, latest_price_text, transform=axes[0].transAxes, fontsize=12, verticalalignment='top', bbox=box_props)

# Define hover label format
hover_label_format = [
    ("Open: ", lambda x: f"${x:.2f}"),
    ("High: ", lambda x: f"${x:.2f}"),
    ("Low: ", lambda x: f"${x:.2f}"),
    ("Close: ", lambda x: f"${x:.2f}"),
    ("Volume: ", lambda x: f"{int(x):,}"),
]

# Function to create hover annotations
def hover_annotations(data):
    annot = pd.DataFrame(index=data.index, columns=data.columns)
    annot_visible = False

    texts = []

    def onmove(event):
        nonlocal annot_visible

        if event.inaxes == axes[0]:
            index = int(event.xdata)
            if index >= len(data.index):
                return

            values = data.iloc[index]
            for label, formatter in hover_label_format:
                value = values[label.rstrip(': ')]
                if np.isnan(value):
                    annot.iloc[index][label.rstrip(': ')] = ""
                else:
                    annot.iloc[index][label.rstrip(': ')] = f"{label}{formatter(value)}"

            annot_visible = True
        else:
            annot_visible = False

        for t, text, (x, y) in zip(texts, annot.values, zip([event.xdata] or [], [event.ydata] or [])):
            if isinstance(x, (list, np.ndarray)):
                x = x[0] if len(x) > 0 and not np.isnan(x[0]) else None
            if isinstance(y, (list, np.ndarray)):
                y = y[0] if len(y) > 0 and not np.isnan(y[0]) else None

            if x is not None and y is not None:
                t.set_position((x, y))
                t.set_text('\n'.join(map(str, text)))
                t.set_visible(annot_visible)

        fig.canvas.draw_idle()

    for _ in data.index:
        t = axes[0].text(0, 0, '', visible=False, ha='left', va='top')
        texts.append(t)

    fig.canvas.mpl_connect('motion_notify_event', onmove)

    return annot


# Attach hover annotations to the plot
annotations = hover_annotations(tesla_weekly_data)

# Display the chart
plt.show()

If you run the code, instead of showing open high, low, close, and volume, its showing an error.

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Damian
  • 29
  • 5
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Jun 16 '23 at 04:32
  • The code is working fine and reproducible. You appear to be isolating `values = data.iloc[index]` OK, and the resulting `values` is of type Pandas.Series which is fine. I am trying to understand what you are doing after that, especially with the `annot` dataframe. The code seems way overly complicated at that point since what you want to display is simply inside the Series `values` and only needs to be formatted. I will play with it some more and see what I can figure. Seems that `annot` and the zipping and other stuff is way more complex than need be. Maybe I'm wrong. – Daniel Goldfarb Jun 16 '23 at 11:32
  • `annot.values` is almost entirely `nan`. Is that intended to be some kind of cache? If not, then for sure the code is way overcomplicated. If so, if it is intended to be a cache, why not prefil the entire thing? I would try to rework that part of the code and get rid of `texts` and `annot`. Simply format the Series `values` and display. – Daniel Goldfarb Jun 16 '23 at 11:38
  • If @Daniel Goldfarb's answer works, you should accept their answer. – jared Jun 20 '23 at 04:03

1 Answers1

1

I've simplified your code a little bit. The following works for me:

import yfinance as yf
import mplfinance as mpf
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import pandas as pd
import numpy as np

# Dates to get stock data
start_date = "2020-01-01"
end_date = "2023-06-15"

# Fetch Tesla stock data
tesla_data = yf.download("TSLA", start=start_date, end=end_date)
tesla_weekly_data = tesla_data.resample("W").agg(
        {"Open": "first", "High": "max", "Low": "min", "Close": "last", "Volume": "sum"}
    ).dropna()

# Get the latest closing price
latest_price = tesla_weekly_data['Close'][-1]

# Create additional plot
close_price = tesla_weekly_data['Close']
apd = mpf.make_addplot(close_price, color='cyan', width=2)

# Plot the candlestick chart
fig, axes = mpf.plot(tesla_weekly_data,
                     type='candle',
                     addplot=apd,
                     style='yahoo',
                     title='Tesla Stock Prices',
                     ylabel='Price',
                     xlabel='Date',
                     volume=True,
                     ylabel_lower='Volume',
                     volume_panel=1,
                     figsize=(16, 8),
                     returnfig=True
                     )

# Move the y-axis labels to the left side
axes[0].yaxis.tick_left()
axes[1].yaxis.tick_left()

# Adjust the position of the y-axis label for price
axes[0].yaxis.set_label_coords(-0.08, 0.5)

# Adjust the position of the y-axis label for volume
axes[1].yaxis.set_label_coords(-0.08, 0.5)

# Set y-axis label for price and volume
axes[0].set_ylabel('Price', rotation=0, labelpad=20)
axes[1].set_ylabel('Volume', rotation=0, labelpad=20)

# Make the legend box
handles = axes[0].get_legend_handles_labels()[0]
red_patch = mpatches.Patch(color='red')
green_patch = mpatches.Patch(color='green')
cyan_patch = mpatches.Patch(color='cyan')
handles = handles[:2] + [red_patch, green_patch, cyan_patch]
labels = ["Price Up", "Price Down", "Closing Price"]
axes[0].legend(handles=handles, labels=labels)

# Add a box to display the current price
latest_price_text = f"Current Price: ${latest_price:.2f}"
box_props = dict(boxstyle='round', facecolor='white', edgecolor='black', alpha=0.8)
axes[0].text(0.02, 0.95, latest_price_text, transform=axes[0].transAxes,
             fontsize=12, verticalalignment='top', bbox=box_props)

# Function to create hover annotations
def hover_annotations(data):

    annot_visible = False
    annot = axes[0].text(0, 0, '', visible=False, ha='left', va='top')

    def onmove(event):
        nonlocal annot_visible
        nonlocal annot

        if event.inaxes == axes[0]:
            index = int(event.xdata)
            if index >= len(data.index):
                index = -1
            elif index < 0:
                index = 0
            values = data.iloc[index]
            mytext = (f"{values.name.date().strftime('%m/%d/%Y'):}\n"+
                      f"O: {values['Open']:.2f}\n"+
                      f"H: {values['High']:.2f}\n"+
                      f"L: {values['Low']:.2f}\n"+
                      f"C: {values['Close']:.2f}\n"+
                      f"V: {values['Volume']:.0f}"
                     )

            annot_visible = True
        else:
            mytext = ''
            annot_visible = False

        annot.set_position((event.xdata,event.ydata))
        annot.set_text(mytext)
        annot.set_visible(annot_visible)
        fig.canvas.draw_idle()

    fig.canvas.mpl_connect('motion_notify_event', onmove)

    return annot


# Attach hover annotations to the plot
annotations = hover_annotations(tesla_weekly_data)

# Display the chart
plt.show()

I don't know what the problem was, but I completely got rid of the array of texts and the DataFrame annot (but I reused the name annot for a single annotation, what you were previously calling t). I also got rid of all the looping through these things, and the complicated formatting lambdas which seemed to me very unneccessary. At any rate, the above works for me.

Daniel Goldfarb
  • 6,937
  • 5
  • 29
  • 61