matplotlib 对mplfinance图表中的区域进行着色

lg40wkob  于 2023-03-03  发布在  其他
关注(0)|答案(1)|浏览(212)

我使用的是matplotlib 3.7.0版、mplfinance 0.12.9b7版和Python 3.10。
我试图对图的区域进行着色,尽管我的逻辑似乎正确,但着色区域未显示在图上。
这是我的代码:

import yfinance as yf
import mplfinance as mpf
import pandas as pd

# Download the stock data
df = yf.download('TSLA', start='2022-01-01', end='2022-03-31')

# Define the date ranges for shading
red_range = ['2022-01-15', '2022-02-15']
blue_range = ['2022-03-01', '2022-03-15']

# Create a function to shade the chart regions
def shade_region(ax, region_dates, color):
    region_dates.sort()

    start_date = region_dates[0]
    end_date = region_dates[1]

    # plot vertical lines
    ax.axvline(pd.to_datetime(start_date), color=color, linestyle='--')
    ax.axvline(pd.to_datetime(end_date), color=color, linestyle='--')

    # create fill
    xmin, xmax = ax.get_xlim()
    ymin, ymax = ax.get_ylim()
    ax.fill_between(pd.date_range(start=start_date, end=end_date), ymin, ymax, alpha=0.2, color=color)
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)

# Plot the candlestick chart with volume
fig, axlist = mpf.plot(df, type='candle', volume=True, style='charles', 
                        title='TSLA Stock Price', ylabel='Price ($)', ylabel_lower='Shares\nTraded', 
                        figratio=(2,1), figsize=(10,5), tight_layout=True, returnfig=True)

# Get the current axis object
ax = axlist[0]

# Shade the regions on the chart
shade_region(ax, red_range, 'red')
shade_region(ax, blue_range, 'blue')

# Show the plot
mpf.show()

为什么选定区域未着色,如何解决此问题?

y3bcpkx1

y3bcpkx11#

问题是,当show_nontrading=False(未指定时为默认值)时,X轴 * 不是 * 预期的日期,因此您指定的垂直线和fill_between**实际上会偏离图表。
最简单的解决方案是设置show_nontrading=True

fig, axlist = mpf.plot(df, type='candle', volume=True, style='charles', 
                       title='TSLA Stock Price', ylabel='Price ($)',
                       ylabel_lower='Shares\nTraded', figratio=(2,1), 
                       figsize=(10,5), tight_layout=True, returnfig=True,
                       show_nontrading=True)

# Get the current axis object
ax = axlist[0]

# Shade the regions on the chart
shade_region(ax, red_range, 'red')
shade_region(ax, blue_range, 'blue')

# Show the plot
mpf.show()

对于这个问题,还有另外两种解决方案,如果您愿意的话,允许您离开show_nontrading=False

    • 1.第一个解决方案是使用mplfinance的kwargs**,***不要***使用returnfig。
  • vlines kwarg
  • fill_between kwarg.
    • 这是首选解决方案**,因为让mplfinance完成Axes对象的所有操作始终是一个好主意,除非您无法通过其他方式完成某些操作。

下面是一个修改代码的示例:

red_range = ['2022-01-15', '2022-02-15']
blue_range = ['2022-03-01', '2022-03-15']

vline_dates  = red_range + blue_range
vline_colors = ['red','red','blue','blue']
vline_dict   = dict(vlines=vline_dates,colors=vline_colors,line_style='--')

ymax = max(df['High'].values)
ymin = min(df['Low'].values)

# create a dataframe from the datetime index 
# for using in generating the fill_between `where` values:
dfdates = df.index.to_frame()

# generate red boolean where values:
where_values = pd.notnull(dfdates[(dfdates >= red_range[0]) & (dfdates <= red_range[1])].Date.values)

# put together the red fill_between specification:
fb_red = dict(y1=ymin,y2=ymax,where=where_values,alpha=0.2,color='red')

# generate blue boolean where values:
where_values = pd.notnull(dfdates[(dfdates >= blue_range[0]) & (dfdates <= blue_range[1])].Date.values)

# put together the red fill_between specification:
fb_blue = dict(y1=ymin,y2=ymax,where=where_values,alpha=0.2,color='blue')

# Plot the candlestick chart with volume
mpf.plot(df, type='candle', volume=True, style='charles', 
         title='TSLA Stock Price', ylabel='Price ($)', label_lower='Shares\nTraded', 
         figratio=(2,1), figsize=(10,5), tight_layout=True, 
         vlines=vline_dict, 
         fill_between=[fb_red,fb_blue])

    • 请注意,**最左边的垂直线和阴影区域之间有一个微小的空间。这是因为您选择的日期("2022 - 01 - 15")是周末(并且是3天的周末)。如果您将日期更改为"2022 - 01 - 14"或"2022 - 01 - 18",它将正常工作,如下所示:

    • 2.最后一个解决方案需要returnfig=True。这是不推荐的解决方案**但它确实有效。

首先,了解以下几点很重要:当show_nontrading * 未 * 指定时,它默认为**False,这意味着尽管您看到x轴上显示的日期时间,实际值是 Dataframe 的行号. Click here for a more detailed explanation
因此,在代码中,不指定日期,而是指定日期出现的行号
指定行号的最简单方法是使用函数
date_to_iloc(df.index.to_series(),date)**,定义如下:

def date_to_iloc(dtseries,date):
    '''Convert a `date` to a location, given a date series w/a datetime index. 
       If `date` does not exactly match a date in the series then interpolate between two dates.
       If `date` is outside the range of dates in the series, then raise an exception
      .
    '''
    d1s = dtseries.loc[date:]
    if len(d1s) < 1:
        sdtrange = str(dtseries[0])+' to '+str(dtseries[-1])
        raise ValueError('User specified line date "'+str(date)+
                         '" is beyond (greater than) range of plotted data ('+sdtrange+').')
    d1 = d1s.index[0]
    d2s = dtseries.loc[:date]
    if len(d2s) < 1:
        sdtrange = str(dtseries[0])+' to '+str(dtseries[-1])
        raise ValueError('User specified line date "'+str(date)+
                         '" is before (less than) range of plotted data ('+sdtrange+').')
    d2 = dtseries.loc[:date].index[-1]
    # If there are duplicate dates in the series, for example in a renko plot
    # then .get_loc(date) will return a slice containing all the dups, so:
    loc1 = dtseries.index.get_loc(d1)
    if isinstance(loc1,slice): loc1 = loc1.start
    loc2 = dtseries.index.get_loc(d2)
    if isinstance(loc2,slice): loc2 = loc2.stop - 1
    return (loc1+loc2)/2.0

该函数将转换为系列的数据框索引作为输入。因此,对代码进行以下更改将允许该函数使用此方法工作:

# Define the date ranges for shading
red_range = [date_to_iloc(df.index.to_series(),dt) for dt in ['2022-01-15', '2022-02-15']]
blue_range = [date_to_iloc(df.index.to_series(),dt) for dt in ['2022-03-01', '2022-03-15']]

...

    ax.axvline(start_date, color=color, linestyle='--')
    ax.axvline(end_date, color=color, linestyle='--')

...

    ax.fill_between([start_date,end_date], ymin, ymax, alpha=0.2, color=color)

其他一切保持不变,您将获得:

相关问题