開發動機
常見美股財經部落客分享Finviz精美強大的圖表,像是美股板塊地圖,一目瞭然各板塊漲跌幅表現,依照顯示顏色紅綠深淺,很快就可以知道市場的熱門族群分佈。族群面積大小照市值排行,輕易看出各板塊的權值股代表。一張圖可說包山包海,掌握不同維度的資料,可以快速掌握市場動態,不用一直滑app查半天。
但在台股的網站上,看不太到這類功能,好在學了Python,知道Python在資料處理和資料視覺化非常強大,就來自己開發一下。
Plotly範例解析
搜尋一下Plotly,這類圖表稱為TreeMap,很快就找到精美的範例。
且程式碼精簡的不可思議:
import plotly.express as px
import numpy as np
df = px.data.gapminder().query("year == 2007")
df["world"] = "world" # in order to have a single root node
fig = px.treemap(df, path=['world', 'continent', 'country'], values='pop',
color='lifeExp', hover_data=['iso_alpha'],
color_continuous_scale='RdBu',
color_continuous_midpoint=np.average(df['lifeExp'], weights=df['pop']))
fig.show()
稍微解析一下px.treemap,完整用法可參考:
帶入DataFrame,path為Top Down排序,最前面的value是最外圍的方塊,範例由世界-洲-國家來排序,由外而內樹狀分佈。
value是決定方塊大小,這裡是用pop(人口)。
color是用lifeExp(壽命)來決定,方塊顏色會依照該國平均壽命大小對應到color_continuous_scale的樣式。
hover_data是互動圖表的功能,滑鼠移到方塊上時閃現的資訊,通常不用特別設定。
color_continuous_midpoin是決定lifeExp對應color_continuous_scale顏色條的中間點數值為何?例如範例中的’RdBu’色條,數值由大到小是藍-白-紅,而我們希望用全世界人類平均餘命來定義中間點白色數值,則可寫成np.average(df[‘lifeExp’], weights=df[‘pop’]),越大於平均的數字越藍,越小的越紅。
台股版塊程式撰寫
主程式
class TwStockTreeMap:
def __init__(self, close, basic_info, start=None, end=None):
self.start = start
self.end = end
self.close = close
self.basic_info = basic_info
# dataframe filter by selected date
def df_date_filter(self, df, start=None, end=None):
if start:
df = df[df.index >= start]
if end:
df = df[df.index <= end]
return df
# map stock_name from basic info(stock_id +name)
def map_stock_name(self, basic_info, s):
target = basic_info[basic_info['stock_id'].str.find(s) > -1]
if len(target) > 0:
s = target['stock_id'].values[0]
return s
def create_data(self):
close_data = self.df_date_filter(self.close, self.start, end=self.end)
return_ratio = (close_data.iloc[-1] / close_data.iloc[0]).dropna().replace(np.inf, 0)
return_ratio = round((return_ratio - 1) * 100, 2)
return_ratio = pd.concat([return_ratio, close_data.iloc[-1]], axis=1).dropna()
return_ratio = return_ratio.reset_index()
return_ratio.columns = ['stock_id', 'return_ratio', 'close']
return_ratio['stock_id'] = return_ratio['stock_id'].apply(lambda s: self.map_stock_name(basic_info, s))
return_ratio = return_ratio.merge(self.basic_info[['stock_id', '產業類別', '市場別', '實收資本額(元)']], how='left',
on='stock_id')
return_ratio = return_ratio.rename(columns={'產業類別': 'category', '市場別': 'market', '實收資本額(元)': 'base'})
return_ratio['market_value'] = round(return_ratio['base'] / 10 * return_ratio['close'] / 100000000, 2)
return_ratio = return_ratio.dropna(thresh=5)
return_ratio['country'] = 'TW-Stock'
return_ratio['return_ratio_text_info']=return_ratio['return_ratio'].astype(str).apply(lambda s: '+' + s if '-' not in s else s) + '%'
return return_ratio
def create_fig(self, relative_market_strength=False):
df = self.create_data()
if relative_market_strength is True:
color_continuous_midpoint = np.average(df['return_ratio'], weights=df['base'])
else:
color_continuous_midpoint = 0
fig = px.treemap(df, path=['country', 'market', 'category', 'stock_id'], values='market_value',
color='return_ratio',
color_continuous_scale='Tealrose',
color_continuous_midpoint=color_continuous_midpoint,
custom_data=['return_ratio_text_info','close'],
title=f'TW-Stock Market TreeMap({self.start}~{self.end})',
width=1350,
height=900)
fig.update_traces(textposition='middle center',
textfont_size=24,
texttemplate= "%{label}<br>%{customdata[0]}<br>%{customdata[1]}"
)
return fig
資料處理邏輯
有了官方範例,我們很容易依樣畫葫蘆,只要用出同樣邏輯的dataframe就可套入。path的[‘world’, ‘continent’, ‘country’]換成[‘country’, ‘market’, ‘category’, ‘stock_id’],ex:台股-上市-半導體-台積電,分為四層。
邏輯確定後就可開始撰寫dataframe,只會用到收盤價算報酬率、企業基本資料(公開資訊觀測站資料源)抓股名、上市櫃分類、產業類別、實收資本額。計算市值時將單位化成億元單位。ETF由於沒有產業類別與實收資本額,會被排除掉。詳見create_data()程式碼。
繪圖程式修改
詳見create_fig()的內容,短短的程式碼就能弄出厲害的圖表:
1.value採用market_value(市值)。
2.color採用return_ratio(報酬率)。
3.color_continuous_scale採用’Tealrose’,因為由大到小,是紅到綠,與習慣相符。
4.color_continuous_midpoin設為0,因為我們希望漲的都是紅色系,跌的都是綠色系,若用原本範例的畫法,則是以相對市場平均強弱來顯現。
5.設置custom_data=[‘return_ratio_text_info’,’close’],客製化texttemplate會用到,不然只會有stock_id
6.加入title、width、height畫布樣式,設定標題與長寬。
7.fig.update_traces(textposition='middle center', textfont_size=24,texttemplate= "%{label}<br>%{customdata[0]}<br>%{customdata[1]})
,textposition設定資料顯示字體位置,textfont_size字體大小,texttemplate使用custom_data自訂方塊內顯示的文字資訊。
plotly_express_treemap強大的地方是還會幫你自動計算整個板塊市值的市場占比,且點擊每一個方塊會自動縮放,放大各類股和企業區塊,有一些市值比較小的公司一開始看不到,透過點擊放大,就可以看出資料,這是原先finviz無法達成的互動性效果。
程式若在Colab跑,可用colab的form語法跑出簡易的操作介面,選取區間日期,查整體市場該期間的報酬情況。
#@title 台股漲跌與市值板塊圖
start= '2021-05-20' #@param {type:"date"}
end = '2021-05-21' #@param {type:"date"}
relative_market_strength = "False" #@param ["False", "True"] {type:"raw"}
繪圖輸出
是不是跟Finviz有87分像呢?有興趣的同學也可以把報酬率改成本益比或其他指標,或改成多指標選單,讓板塊圖的功能更加完善喔!