Python港股報價系統開發練習 (Pandas+Google Colab範例)

Chris K.Y. Fung
數碼文明推廣教室
17 min readNov 30, 2021

--

圖片來源:由 Gerd AltmannPixabay 上發布

前言

金融科技 (FinTech) 正在逐漸普及,自動交易程式 (又稱「程式交易」) 是在行業之中火熱的一個領域,利用電腦系統進行股票買賣的自動操盤,可 24/7 處理大數據,甚至透過機器學習 (Machine learning) 與人工智能 (AI) 來分析及比較投資策略,使得能超越人力,突破僅能同時追蹤及分析 2–3 隻股票的局限。

交易程式不論有多強大的功能,都有一個不可或缺的元件,用於從線上獲取股票報價的大數據。本文將分享如何開發一個簡易的報價程式,使用 Python 程式語言向數據庫要求股票資訊,以及創建一個模塊來管理股票組合。

👇 親自試試看 👨‍💻

Python 是一款比較容易學習的程式語言,且擁有豐富的開源軟體包可供免費利用,包括統計模型、人工智能、機器學習、深度學習、繪製圖表等領域的套件。Python 的應用性極廣,十分適合入門者學習,通向橫向擴充拼湊出個性化的應用程式。

Python 有多個版本,3.x 版本已取代 2.x 版主成為現在的主流。一般 Linux 作業系統都已預先安裝 Python 3.x 解釋器,可直接開啟 Python 環境及腳本文件;在 Mac 及 Windows 作業系統上,則需要手動安裝 Python 3.x。有時候,因特定的專案需要不同的版本,而導致版本衝突,使在本機上安裝的 Python 環境變得很糟糕。因此,我較建議新手使用 Google Colaboratory (簡稱 Colab),善用這個免費的雲端平台來學習 Python。

Open Jupyter Notebook in Google Colab

建議在你進入實作練習之前,最好已具備以下的開發經驗:

  • Python 語言基礎
  • Google Colab 介面之基本操作
  • 對 Git 與 GitHub 有基本認識

實戰練習詳解

Google Colab 是一個基於 Jupyter 的互動式 Python 開發及運行環境,通過單元格來編輯及運行程式碼,以及插入文本、圖片、數學公式等內容,構成一個筆記本文件。

初始化專案環境

在 Colab 打開本練習的筆記本之後,你會在第一個單元格看到以下的指令碼:

!git clone -l -s https://github.com/chriskyfung/pandas_stock_notebook.git%cd pandas_stock_notebook

Colab 是在 Google Cloud 的 VM (虛擬機) 上運行,每次開啟專案時都會產生一個生命週期,在工作階段結束之後,除了筆記本自身的內容,其他文件皆不會自動保存。

點擊位於這個單元格左上方的 ▶ 圖示 (或按 Shift + Enter) 運行指令碼,將從 GitHub 複製本練習所需的其他檔案至你的 Colab 暫存空間,並變更工作目錄到已下載的文件夾。

檔案清單 > pandas_stock_notebook文件夾
從 GitHub 下載的練習用檔案

接下來,根據配置文件 requirements.txt 安裝所需的 Python 軟體包,如 numpypandaspandas-datareaderpandas-market-calendars、python-dotenv 等。在筆記本裏運行以下的指令,將安裝這些依存的套件:

!pip install -r requirements.txt

若是初次安裝 numpy pandas,Colab 系統會顯示錯誤訊息 (如下圖)。

ERROR: pip’s dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. google-colab 1.0.0 requires pandas~=1.1.0; python_version >= “3.0”, but you have pandas 1.2.5 which is incompatible. You must restart the runtime in order to use newly installed versions.

點擊 👆 在系統訊息左下角的「RESTART RUNTIME」按鈕,然後在彈出提示框點選「是」,將重新啟動執行階段。

重新啟動執行階段 — 
確定要重新啟動執行階段嗎?執行階段狀態 (包括所有本機變數) 將會遺失。

使用環境變數管理數據平台的 API 密鑰

在本練習中,我們將會利用 Quandl 這個金融及經濟數據的服務平台,透過它的 API 從數據庫下載港股報價數據。

Quandl 是為投資專業提供金融,經濟和備選數據的首選,擁有海量的經濟和金融數據。

當存取 Quandl 的 API 用戶端時,須要 Quandl 提供的密鑰來進行用戶認證。如果你尚未持有 Quandl 帳號,可於以下的網址註冊一個免費用戶帳號:
https://data.nasdaq.com/sign-up

Create your account (Step 1 of 3) — Choose your purpose for using Nasdaq Data Link: Business, Academic, or Personal.

登入 Quandl 帳號之後,到 https://data.nasdaq.com/account/profile 便能夠找到你的 API KEY。

Quandl API KEY

在開發時,一般都會用環境變數 (environment variable) 來存儲 API 密鑰,以避免將這類敏感的資料遺留在程式碼中,因一時大意要清除就一同提交 (commit) 至公開程式資料庫,而不小心將密鑰洩漏了。

在專案文件夾裏,新增一個叫 .env 的純文字檔案,然後使用文字編輯器加入 QUANDL_API_KEY 等於你的 API 密鑰,如下所示:

QUANDL_API_KEY=<your-quandl-api-key>

我們已在 .gitignore 文件設置忽略 .env 檔案,當選擇「檔案 > 在 GitHub 儲存副本」提交專案至你的遠端程式資料庫,亦會將這個檔案排除在外。

由於不會被自動保存一下,因此請記得要在每次開啟專案時,要再次新增或從你的電腦上傳已備份的 .env 檔案。

在備妥 .env 檔案之後,在筆記本中執行以下的指令來利用 python-dotenv 將環境變數從文件內容中讀取出來:

%load_ext dotenv
%dotenv

查詢港股最近價格之步驟

從 Quandl 的免費數據庫,我們僅能獲取港股的暨盤後 (end-of-day) 資訊,而其數據於每個交易日 (香港時間) 5:30 PM 進行更新。假設今天是交易日,即要在收市之後 1 小時才能夠獲得當日之數據。在每一次要求數據時,需要提交一個股票代號,以及用於指定所需之範圍的開始日期結束日期

因為並不是每一天都是交易日,所以我們必須注意日期與日數之間的轉換。以查詢最近 7 天的報價為例,假設今日是星期三,我們很容易就直覺地錯誤輸入上星期三的日期,但因為星期六和星期日是休假,結果發現只得到 5 天的數據資料。若不小心,在處理如平衡值之類的指標時,就會計算出錯誤的結果。

要正確找出數據範圍的方法,你可以:

  • 到港交所的網站,對著交易所日曆數數手指。
  • 先以一個較大的日期範圍從線上數據庫取出資料至緩存,接著按資料筆數從中抽取或複製出所需的範圍。
  • 利用 pandas-market-calendars 軟體包來取得及核對交易所的假期、延遲開市和提前收市之日曆資料。

1. 使用 datetime 建立日期物件

遇到要計算日期和時間,我們可使用 Python 標準函式庫的 datetime 模塊。以下的例子,利用了模塊中的 today() 函式獲取今天的日期,然後通過配合 timedetla(days=7) 函式運算出 7 天前的日期。

from datetime import date, timedeltapast7days = {
'start_date' : date.today() - timedelta(days=7),
'end_date' : date.today()
}
print( "今天是", past7days['end_date'])
print("7天前是", past7days['start_date'])
輸出的結果: 過去 7 天之日期範圍

2. 使用 pandas-datareader 獲取股票報價數據

Pandas 是一個被廣泛用於金融領域,特別是用於時間序列數據 (time-series data) 分析的 Python 軟體包,配合 pandas-datareader 使用可簡易地從各種線上資源中提取數據至 Pandas DataFrame 中。

在本練習中,我們將採用 pandas-datareader 來訪問 Quandl 的線上數據庫。雖然則可以採用 Quandl 官方軟體包,但是我認為 pandas-datareader 更簡單易用,且能夠確保數據以 Pandas Dataframe 格式緩存,不必費神在匯出設定/格式轉換之類的細節之上。

另一點是 pandas-datareader 亦支持從 Yahoo! Finance (雅虎財經) 獲取股票報價數據。Yahoo 報價數據允許公開匿名訪問,可省去密鑰認證這個麻煩的手續,因而較受在開發上沒有經驗的初學者所喜歡。換而言之,採用 pandas-datareader 能夠讓你能夠根據個人偏好,靈活運用多個不同的數據源 (data sources)。

📌 備註:

我最初也是使用 Yahoo! Finance 為數據源,後來才轉到用 Quandl 的。
原因是 Yahoo 的數據庫已經不再是來自官方的 API,而是以網頁爬蟲等間接的方法作出更新及維護。一旦 Yahoo! Finance 的網頁設計有所改洞,存取功能就很大機會失效。我就曾經遇過這種情況,害得我有超過一星期都無法更新數據,造成很大的麻煩,因此我選擇不再採用這個脆弱的管道。

在使用 pandas-datareader 的時候,我們普遍都會將 pandas 模槐一起載入,以執行後續的數據處理。如下方的示例,首先匯入這兩個模塊,接下來再呼叫 DataReader() 函式從 Quandl 獲取股票的數據。

import pandas as pd
import pandas_datareader as pdr
stock_code = 'HKEX/00001'
start_date = past7days['start_date']
end_date = past7days['end_date']
df = pdr.DataReader(stock_code, 'quandl', start_date, end_date)display(df)
display(df.iloc[0])

為了讓你更易明瞭,我將 stock_codestart_dateend_date 這三個參數的值,分了 3 行以變數來定義它們:

  • stock_code 的值被指派為 ‘HKEX/00001’ (長和的股票代號),HKEX 字首代表是在香港交易所上市,而斜槓號 (/)之後為 5 位數字的編號。

    💡 Tips:
    於該數據庫內,所有股票代號與其相應的名稱、描述及有效時間範圍,皆可於下方連結所下載取得的 CSV 文件之中檢索:
    🔗 https://data.nasdaq.com/api/v3/databases/HKEX/metadata
  • start_dateend_date的值則被分別指派為上一步的 past7days 物件內之同名子物件。

DataReader() 回傳的結果將存放至被命名為 df 的變數,以代表它是一個 dataframe 類型的物件。我們可以用 display() 函式輸出結果,因為數據是按反向時間排序,所以運用 Pandas 語法從 df 中抽取第一筆資料,將會篩選出最近一個交易日的報價資訊。

輸出的結果: display(df)
輸出的結果: display(df.iloc[0])

非 NaN 的報價資訊,包括:

  • Nominal Price 經調整收市價
  • Bid 買入價
  • Ask 賣出價
  • High 最高價
  • Low 最低價
  • Previous Close 上日收市價
  • Share Volume 成交量
  • Turnover 成交金額

建立模塊📦封裝重複使用的程式碼

由於將全部程式碼都編寫到一個筆記本上,將會因為過於冗長而不利於檢查和編輯。因此,在開發時會將程式碼分割為小塊,另存至獨立的 .py 文件,再以匯入模組的方式加到主程序來使用。

在本練習的專案文件夾中,有一個被命名為 stocks.py 的檔案,它是一個由小弟開發的小模塊,內含一個稱為 stock_profile 的類別,提供以下的用途:

  • 從 JSON 格式文件匯入股票組合的追蹤清單
  • 列出股票組合清單內的所在股票代號
  • 「股票代號 ⇔ 股票名稱/簡稱」的轉換
  • 從 Quandl 線上數據庫獲取港股報價數據

當要使用該模塊的時候,在主程序裏加入以下的程式碼:

from stocks import stock_profilestocks = stock_profile()

這樣就會從 stocks.py 加載 stock_profile 類別,並且建立一個被命名為 stocks的實例 (instance)。

從 JSON 文件讀入股票組合清單 & 股票名稱索引

當要追蹤多隻股票,我們可以建立一個 JSON 文件 (例如 stocks.json ) 來管理這個股票組合。首先,在文件中加入以下的內容結構:

{
"listing": {
...
}
}

然後,以 key-value pairs (鍵-值對) 在 ... 的位置輸入各股票代號與其相應的股票名稱/簡稱,如下方的例子:

{
"listing": {
"0388.hk": "港交所",
"0700.hk": "騰訊,Tencent",
"0857.hk": "中石油",
"0883.hk": "中海油",
"0939.hk": "建設銀行,建行",
"0941.hk": "中國移動,中移動",
"1211.hk": "比亞迪股份,比亞迪",
"1810.hk": "小米",
"2382.hk": "舜宇光學,舜宇"
}
}

這裏的鍵-值對將轉換為「股票名稱 ⇔ 代號」的索引字典。因為我較為習慣 Yahoo 的代號格式,所以保留了 .hk 字尾,但模塊會將它自動砍掉,僅提取數字的部份。此外,每個股票代號可匹配多個股票名稱/簡稱,模塊會以逗號為分隔符號,使用分割後的字串來編製索引,並以第一組分割字串為股票的預設顯示名稱。

在主程序裏,執行以下的程式碼將從 JSON 文件讀入股票組合清單,並在記憶體上建立股票名稱與代號轉換之索引字典:

stocks.loadJsonProfile('./stocks.json')

接下來,在使用以下的程式碼就能夠列出股票組合清單內的所有股票代號:

tickers = stocks.getAllStockCodes()# ['0388.hk', '0700.hk', '0857.hk', '0883.hk', '0939.hk', '0941.hk', '1211.hk', '1810.hk', '2382.hk']

批次載入股票組合清單內所有股票的數據

只要使用模塊內的 cacheFromQuandl() 函式,就能夠自動從 Quandl 獲取多隻股票的數據並輸出結果。以下是它的基本用法:

stocks.cacheFromQuandl(tickers, start_date, end_date)

參數:

  • tickers (必須) 股票代號的陣列
  • start_date (必須) 載入範圍的開始日期
  • end_date (選用) 載入範圍的結束日期

例如:我們想要查詢 2021年 7月 2日的報價,在筆記本上執行以下的程式碼就會逐一將股票組合清單內的股票報價數據顯示出來:

from datetime import date
from stocks import stock_profile
stocks = stock_profile()
stocks.loadJsonProfile('./stocks.json')
tickers = stocks.getAllStockCodes()
stocks.cacheFromQuandl(tickers, date(2021,7,2), date(2021,7,2))
輸出的結果(僅部份): cacheFromQuandl(tickers, date(2021,7,2), date(2021,7,2))

股票報價數據已被緩存,接下來就是如何加以利用。譬如,添加你的算法對數據進行處理和分行,又或者編寫一個小程序將快取數據匯出至檔案等等。

原始碼

你可以到我的 GitHub 下載本文範例所使用的原始碼檔案:

如果您喜歡這篇文章,請為此文章 👏 👏 👏 👏 👏 👏 👏,並分享至你的 Facebook 及 Twitter。

假如您有任何疑問或建議,歡迎標註留言或透過 Facebook 專頁 聯繫我 🙂

授權

本文章及原始碼採用 創用CC授權條款-姓名標示-非商業性-相同方式分享 4.0 國際 (CC BY-NC-SA 4.0) 授予他人分享、使用,甚至創作衍生作品的權利。

CC BY-NC-SA 4.0 標示

你可自由:

  • 分享 — 以任何媒介或格式重製及散布本素材
  • 修改 — 重混、轉換本素材、及依本素材建立新素材

只要你遵守授權下列條款規定:

--

--

Chris K.Y. Fung
數碼文明推廣教室

📝集中分享提升生產力、自動化至開發等技能📢📈 博客及開發人員,於香港理工大學MPhil畢業,並多年在大學從事學術研究,喜歡學習多元化知識和技術。歡迎訂閱・標註回應,共享交流啟發性話題🐱‍🏍 about.me/chriskyfungfb.com/chriskyfung 😪📘