为何mplfinance悬停注释显示NaN而非Open/High等行情数值?
特斯拉周K线图悬停注释显示NaN问题解决
运行基于yfinance、mplfinance编写的Python代码绘制特斯拉周K线图时,图表的悬停注释无法正常显示Open、High、Low、Close及Volume的实际数值,反而出现NaN错误,具体情况如下图所示:

原错误代码
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()
问题根源
- x轴索引匹配错误:原代码中
index = int(event.xdata)直接将matplotlib的x轴像素坐标转换为整数索引,但mplfinance绘制的图表x轴实际是DatetimeIndex,并非连续整数,导致取到的索引与数据行不匹配,最终读取到NaN值。 - 注释文本逻辑混乱:为每个数据行创建注释文本对象,且遍历所有文本更新内容,逻辑冗余且错误,无法正确对应当前悬停的行。
修正方案
修改hover_annotations函数,简化注释逻辑并正确匹配x轴索引:
# Function to create hover annotations def hover_annotations(data): # 仅创建一个注释文本对象,避免冗余 annot = axes[0].text(0, 0, '', visible=False, ha='left', va='top', bbox=dict(boxstyle='round', facecolor='white', edgecolor='gray', alpha=0.9)) annot_visible = False def onmove(event): nonlocal annot_visible if event.inaxes == axes[0]: x_val = event.xdata if np.isnan(x_val): return # 通过x轴坐标匹配对应的DatetimeIndex行 idx = data.index.get_indexer([data.index[int(x_val)]], method='nearest')[0] if idx < 0 or idx >= len(data): return values = data.iloc[idx] # 构建当前行的注释文本 annot_text = [] for label, formatter in hover_label_format: col_name = label.rstrip(': ') value = values[col_name] if not np.isnan(value): annot_text.append(f"{label}{formatter(value)}") # 更新注释的位置和内容 annot.set_position((event.xdata, event.ydata)) annot.set_text('\n'.join(annot_text)) annot_visible = True else: annot_visible = False annot.set_visible(annot_visible) fig.canvas.draw_idle() fig.canvas.mpl_connect('motion_notify_event', onmove) return annot
修正后的完整代码
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 = axes[0].text(0, 0, '', visible=False, ha='left', va='top', bbox=dict(boxstyle='round', facecolor='white', edgecolor='gray', alpha=0.9)) annot_visible = False def onmove(event): nonlocal annot_visible if event.inaxes == axes[0]: x_val = event.xdata if np.isnan(x_val): return # 通过x轴坐标匹配对应的DatetimeIndex行 idx = data.index.get_indexer([data.index[int(x_val)]], method='nearest')[0] if idx < 0 or idx >= len(data): return values = data.iloc[idx] # 构建当前行的注释文本 annot_text = [] for label, formatter in hover_label_format: col_name = label.rstrip(': ') value = values[col_name] if not np.isnan(value): annot_text.append(f"{label}{formatter(value)}") # 更新注释的位置和内容 annot.set_position((event.xdata, event.ydata)) annot.set_text('\n'.join(annot_text)) annot_visible = True else: annot_visible = False 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()
内容的提问来源于stack exchange,提问作者Damian
相关产品推荐
相关产品推荐

