本益比河流圖|Plotly-Filled Area Plot應用教學

本益比河流圖的用途

取標的一段時間內的本益比上下限,再決定要分割幾層,用來判斷獲利或成長性穩定的股票所處於的本益比區間。

高成長股或獲利不夠穩定的企業不適用,本益比河流圖趨勢變化會太過劇烈。

本益比河流圖上升或層距擴大代表獲利逐漸成長,買在河流圖中下緣是理想位置。

本益比河流圖下降或層距縮小代表獲利逐步下掉,若股價位於河流圖上緣則風險偏高。

適合使用本益比的穩定企業
不適合使用本益比的不穩定企業

如何繪製?

Plotly套件提供了Filled Area Plot函數讓我們可以應用繪製本益比河流圖。

使用Scatter物件帶入fill參數就能將折線圖填充區域顏色。

import plotly.graph_objects as go
# 設定畫布
fig = go.Figure()
# 使用add_trace加入線段
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[0, 2, 3, 5], fill='tozeroy')) # fill down to xaxis
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[3, 5, 1, 7], fill='tonexty')) # fill to trace0 y

fig.show()

我們將步驟拆解為二,第一步是生成繪製所需要的資料,第二步是將資料套入繪圖function。

生成資料

pe/close先求出eps,再用split_range設定本益比上下限要分幾層河流。河流邊界為各區間本益比倍數,與股價相乘後為該邊界本益比對應的股價序列。

from finlab import data
import pandas as pd


stock_id='2330'
#幾層河流
split_range=8

close = data.get('price:收盤價')
pe = data.get('price_earning_ratio:本益比')
df = pe[stock_id]
max_value = df.max()
min_value = df.min()
# 求本益比上下限間距
quan_value = (max_value - min_value) / split_range
# 求本益比各河流倍數
river_borders = [round(min_value + quan_value * i, 2) for i in range(0, split_range + 1)]
# 算出eps
result = (close[stock_id] / df).dropna().to_frame()
index_name = 'pe/close'
result.columns = [index_name]
result['close'] = close[stock_id]
result['pe'] = pe[stock_id]
# 各本益比對應價格
for r in river_borders:
    col_name = f"{r} pe"
    result[col_name] = result[index_name] * r
result = round(result, 2)

資料目標範例如下列dataframe:

繪圖

from finlab import data
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px

df=result.copy()
# 挑出欄位有數字的來畫河流圖,過濾pe/close,close,pe
col_name_set = [i for i in df.columns if any(map(str.isdigit, i))]

fig = go.Figure()

# 執行迴圈填入資料序列
for n, c in enumerate(col_name_set):
    # 第一道邊界下不要填充
    if n == 0:
        fill_mode = None
    else:
      fill_mode = 'tonexty'
    # colors.qualitative.Prism填入河流圖階層顏色
    fig.add_trace(
        go.Scatter(x=df.index, y=df[c], fill=fill_mode, line=dict(width=0, color=px.colors.qualitative.Prism[n]),
                    name=c))
    
# 製作客製化data項目用來製作hovertemplate,顯示浮動資料
customdata = [(c, p) for c, p in zip(df['close'], df['pe'])]
hovertemplate = "<br>date:%{x|%Y/%m/%d}<br>close:%{customdata[0]}" + f"<br>'pe'" + ":%{customdata[1]}"
fig.add_trace(go.Scatter(x=df.index, y=df['close'], line=dict(width=2.5, color='#2e4391'), customdata=customdata,
                          hovertemplate=hovertemplate, name='close'))

# 取股名製作title
security_categories = data.get('security_categories').set_index(['stock_id'])
stock_name = security_categories.loc[stock_id]['name']
fig.update_layout(title=f"{stock_id} {stock_name} {'PE'} River Chart",
                  template="ggplot2",
                  yaxis=dict(
                      title='price',
                  ),
                  # hovermode='x unified',
                  )
# 顯示十字線
fig.update_xaxes(showspikes=True)
fig.update_yaxes(showspikes=True)

注意以下程式,若第一道邊界下沒設None,會變成以下形式,最低一層會被填入很大一塊區域,與河流間距不相符,有礙觀察,因此我們要讓第一道邊界以下不要填充才比較好。

for n, c in enumerate(col_name_set):
    fill_mode = 'tonexty'

封裝函數與多元應用

除了本益比河流圖,常用的還有股價淨值比河流圖,計算和應用方式和本益比河流圖大同小異,我們可以將上面的程式封裝成函數,設定一個mode參數去選擇我們要哪一種河流圖。

完整程式如下:

from finlab import data
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px

# 控制資料範圍
def df_date_filter(df, start=None, end=None):
    if start:
        df = df[df.index >= start]
    if end:
        df = df[df.index <= end]
    return df


def get_pe_river_data(start=None, end=None, stock_id='2330', mode='pe', split_range=6):
    if mode not in ['pe', 'pb']:
        print('mode error')
        return None
    close = df_date_filter(data.get('price:收盤價'), start, end)
    pe = df_date_filter(data.get('price_earning_ratio:本益比'), start, end)
    pb = df_date_filter(data.get('price_earning_ratio:股價淨值比'), start, end)
    df = eval(mode)
    if stock_id not in df.columns:
        print('stock_id input is not in data.')
        return None
    df = df[stock_id]
    max_value = df.max()
    min_value = df.min()
    quan_value = (max_value - min_value) / split_range
    river_borders = [round(min_value + quan_value * i, 2) for i in range(0, split_range + 1)]
    result = (close[stock_id] / df).dropna().to_frame()
    index_name = f'{mode}/close'
    result.columns = [index_name]
    result['close'] = close[stock_id]
    result['pe'] = pe[stock_id]
    result['pb'] = pb[stock_id]
    for r in river_borders:
        col_name = f"{r} {mode}"
        result[col_name] = result[index_name] * r
    result = round(result, 2)
    return result

def plot_tw_stock_river(start=None, end=None, stock_id='2330', mode='pe', split_range=8):
    """Plot River chart for tw_stock

    Use maximum or minimum PE(PB) to calculate River.
    it is good for judging the  high and low in the historical interval.

    Args:
      start(str): The date of data start point.ex:2021-01-02
      end(str):The date of data end point.ex:2021-01-05
      stock_id(str): Target id in tw stock market ex:2330.
      mode(str): 'pe' or 'pb'.
      split_range(int):the quantity of river borders.
    Returns:
        figure
    """
    df = get_pe_river_data(start, end, stock_id, mode, split_range)
    if df is None:
        print('data error')
        return None
    col_name_set = [i for i in df.columns if any(map(str.isdigit, i))]

    fig = go.Figure()
    for n, c in enumerate(col_name_set):
        if n == 0:
            fill_mode = None
        else:
            fill_mode = 'tonexty'
        fig.add_trace(
            go.Scatter(x=df.index, y=df[c], fill=fill_mode, line=dict(width=0, color=px.colors.qualitative.Prism[n]),
                       name=c))
    customdata = [(c, p) for c, p in zip(df['close'], df[mode])]
    hovertemplate = "<br>date:%{x|%Y/%m/%d}<br>close:%{customdata[0]}" + f"<br>{mode}" + ":%{customdata[1]}"
    fig.add_trace(go.Scatter(x=df.index, y=df['close'], line=dict(width=2.5, color='#2e4391'), customdata=customdata,
                             hovertemplate=hovertemplate, name='close'))

    security_categories = data.get('security_categories').set_index(['stock_id'])
    stock_name = security_categories.loc[stock_id]['name']
    fig.update_layout(title=f"{stock_id} {stock_name} {mode.upper()} River Chart",
                      template="ggplot2",
                      yaxis=dict(
                          title='price',
                      ),
                      # hovermode='x unified',
                      )
    fig.update_xaxes(showspikes=True)
    fig.update_yaxes(showspikes=True)

    return fig


# plot_tw_stock_river(stock_id='2330',mode='pe',split_range=8)
plot_tw_stock_river(stock_id='2330',mode='pb',split_range=8)
股價淨值比河流圖

總結

使用finlab和plotly套件可以做出各式各樣視覺化的應用,只要學會一些小技巧,就能客製化屬於自己的看盤工具。

對本篇程式有興趣的可以參考文底連結附檔,記得要先註冊Finlab量化平台才可有權限使用程式喔!

colab程式範例

喜歡我們的文章的話,那更別錯過我們精心製作的優質系列課程喔!

上一篇
下一篇

Genie

Python elf, love magic of python. Explore the financial world.