Streamlit

Streamlit 是一個開源python web框架,擅長搭配機器學習與資料科學的展示,不需要具備任何前端網頁技術與框架,就可以透過python純後端的語法輕鬆架構出dashboard與app!Streamlit身為土生土長的python語言,不意外的擁有pandasnumpy等資料處理套件,同時也可以結合了python強大的視覺化套件如matplotlibseabornplotly等等、甚至是geopandascartopy等地理資訊套件。

image.png

Streamlit可以以簡單的python後端語法建構前端的網頁APP,並且可以結合Github,將編寫好的 streamlit app 免費部署到官方的伺服器上,詳細的安裝方法、各個元件語法可參考Streamlit Ducument。在此篇中,我將以實際的專案出發,介紹如何透過串連Finmind API取得股票與股利資訊,並以streamlit製作計算機APP。

FindMind

稍微google了一下,不難發現有許多台股資訊的資料庫,其中以FinLab,與FinMind最為大宗,兩者都支援python 語言,也都是半開源的專案,也各自有屬於自己的免費與付費方案,當然也一定有相異之處,但比較就不比了,反正能達成目的即可,各位讀者不妨看完此篇後依據自己個人的需求選擇適合自己資料庫API。以下我將介紹我如何透過FinMind取得股利資訊

FindMind API套用官網的說明,主要提供以台股為主,超過 50 種金融開源數據( open data ),希望讓大數據、資料分析,減少資料收集的門檻。image.png 而我們免費仔要做就只是註冊一組帳密,便可以取得一小時600次的request額度,對於我們開發這種個人小型測試專案應該是很夠用了,當然也可以根據自己的需求購買更高階的方案,畢竟經營與維護資料庫不容易,使用者付費也是合情合理。

FinMind有著輕量、簡易的特點,使用者要做的就只是登入FinMind後將個人的token輸入以下的程式碼即可輕鬆的連接資料庫API。

from FinMind.data import DataLoader
import streamlit as st
import pandas as pd

#use token login package
# 初始化 FinMind DataLoader
FinMindapi = DataLoader()
FinMindapi.login_by_token(api_token='your token')

以下我以自己的需求為例,透過API取得台股的股息資訊,並包成一個函式方便後續使用。

#FinMind API 查詢股票現金股利
def query_dividend_data(stock_code, selected_year,start_date, end_date):
    try:
        #連接台股股利資料庫
        df = FinMindapi.taiwan_stock_dividend(stock_id=stock_code, start_date=start_date, end_date=end_date)
        if not df.empty:
            # 返回 API 查詢的整個 DataFrame
            df=df[["stock_id","date","CashEarningsDistribution"]]
            df=df.rename(columns={"stock_id":"股票代號","date":"除息日期","CashEarningsDistribution":"現金股利"})
            return df
        else:
            st.warning(f"{selected_year}年度找不到股票代號 {stock_code} 的相關資料")
            return df
    except Exception as e:
        # st.error(f"查詢股票代號 {stock_code} 時發生錯誤:{e}")
        return pd.DataFrame(columns=["股票代號","除息日期","現金股利","股數","總額"])

Calculator APP

話不多說,先看看成品。Stock Calculator by Streamlit image-2.png image.png 這只是一個簡單的範例,初步來說分成三個部分,pages分頁、左側投資組合出入欄、右側內容圖表輸出。 以下將分別說明。

Multipages

Streamlit 如假包換,支援分頁切換!想知道其他應用歡迎收看官方document,簡單來說只要在專案的根目錄建立一個名叫pages的資料夾。

├── Chart.py
├── Query_FinMind_Data.py
├── README.md
├── pages
│   └── 選擇持有時間.py
├── requirements.txt
└── 依年度計算.py

並在其中放置另一個頁面的內容,streamlit將以檔名呈現在頁面上。是的,你沒看錯,streamlit竟然支援中文的檔名,如果過去有邊寫過python 的朋友應該都被中文編碼、空格等檔名陷阱給坑過,但streamlit竟然可以支援並正確顯示,雖然不知道它怎麼做到的,但著實令我驚豔,在發現後便與大家分享。

Stock Input

以下分享如何在左側欄位建立彈性的股票代號與股數輸入,雖然不是什麼複雜的演算法,但當初在進行開發時著實思考了良久,也參考了官方相關的論壇與前輩們的討論才完成,分享給大家。

def add_field():
    st.session_state.fields_size += 1
def delete_field():
    st.session_state.fields_size -= 1

# 年度的 Slide bar
selected_year = st.sidebar.slider("選擇年度", 2019, 2023, value=2022)

# 計算 start_date 和 end_date
start_date = f"{selected_year}-01-01"
end_date = f"{selected_year}-12-31"
#region 左側的sidebar
with st.sidebar:
    st.title('投資組合')
    if "fields_size" not in st.session_state:
        st.session_state.fields_size = 0
        st.session_state.fields = []
        st.session_state.deletes = []
    # c_up contains the stock input
    # c_down contains the add and remove buttons
    c_up = st.container()
    c_down = st.container()
    with c_up:
        c1 = st.container() # c1 contains input choices
        c2 = st.container() # c2 contains submit button
    with c_down:
        col_l,_,col_r = st.columns((6,10,6))
        with col_l:
            st.button("➕增加一筆", on_click=add_field)
        with col_r:
            if st.session_state.fields_size>0:
                st.button("❌刪除一筆", on_click=delete_field)

    for i in range(st.session_state.fields_size):
        with c1:
            col_stock_code, col_shares= st.columns((8,6))
            with col_stock_code:
                # 輸入股票代號
                st.session_state.fields.append(st.text_input(f"股票代號_ {i+1}", key=f"Stock_Code_{i+1}"))
            with col_shares:
                # 輸入股票股數
                st.session_state.fields.append(st.number_input(f"股數_ {i+1}", key=f"Shares_{i+1}", min_value=0, value=0))

Content

在右側的內容,我簡單安排了DataFrame的展示、長條圖、圓餅圖。streamlit可以透過st.dataframe輕易的展示dataframe表格,充分的運用了python的一大利器--pandas的功能,還可以調整各式參數。

st.dataframe(stocks_df, hide_index=True, use_container_width=True)
if st.session_state.fields_size!=0:
    # 以長條圖顯示現金股利金額
    st.subheader("現金股利金額長條圖")
    Chart.plot_dividends_bar_chart(stocks_df)
    # 圓餅圖顯示各股票的現金股利總額分布
    st.subheader("各股票現金股利佔比分布圖")
    st.write(f"{selected_year} 年度共拿到"+str(round(stocks_df["總額"].sum()))+"元")
    Chart.plot_dividends_pie_chart(stocks_df)

除此之外還可以結合視覺化套件,這裡我使用plotly進行繪圖,主要是看中套件的網頁互動性,有強大UI功能,可以方便使用者進行調整。在這裡我依據個人習慣與程式碼簡潔包成了函式物件,看似粉複雜但其中的繪圖關鍵語法卻相當簡單。

import plotly.graph_objects as go
###繪製長條圖
# 創建 Figure
fig = go.Figure()
# 加入長條圖
fig.add_trace(go.Bar(
    x=grouped_df['月份'],
    y=grouped_df["總額"].fillna(0),
    name='',#去掉名稱
    hovertemplate='%{x}月: %{y}元',
    marker_color='skyblue'
))
# 設定標題和圖例
fig.update_layout(xaxis_title='月份',yaxis_title='總額 (元)',title_text="每月股利現金流統計表", title_x=0.45, xaxis=dict(tickmode='linear'))
# 顯示圖表
st.plotly_chart(fig, use_container_width=True)
###繪製長條圖end

###繪製圓餅圖
fig_pie = go.Figure(data=[go.Pie(
    labels=dividends_df["股票代號"],
    values=dividends_df["總額"],
    hovertemplate="股票代號: %{label} <br>股利: %{value}元 <br>佔比: %{percent}%"
)])
st.plotly_chart(fig_pie, use_container_width=True)
###繪製圓餅圖

以上只是點出一些關鍵之處,至於資料如何彙整,欄位如何整理不在本本文討論的範圍,若有興趣可以直接參考Github專案 Stock Calculator

Deployment

到這裡,你應該已經能成功部署在local終端機上,但若想將APP直接分享給別人,進行雲端的部署就相當重要,然而應該不是每個人都能力維護local端的伺服器,所幸stramlit官方與Github提供了免費的部署方式,可以讓大家將存在github的streamlit repository部署到strealit cloud中,詳細可以看Streamlit Deploy。 簡單來說只要準備相關depadency 準備好即可。

your-repository/
├── your_app.py
└── requirements.txt

值得注意的是,streamlit 與 Github 秉持著開源的精神,官方的說明是只能部署一個Github的private專案,但public專案則沒有限制。然而經過的實測若先部署public repository再將專案改為private,部署的APP似乎仍算public,因此沒有數量限制XDD。

結語

到這裡,你已經成功學會如何串連 FinMind API 建立一個streamlit的Web APP。建立存股計算機只是依據我的需求而建,我也不是財經相關背景,只是有感於自己存股時的痛點--要來回翻查各個股票代號,一直無法方便的統計自己可以領多少股利!於是這個小專案便誕生了。我相信這絕對只是一個簡單的應用,在未來也許可以開發選股策略、程式交易等APP,也歡迎各位先進開發屬於自己的APP,也歡迎各位與我交流。

boba-icon
請我喝珍奶!