跳至主要內容
FinLab

利用 Pandas 輕鬆取得股價並回測:pandas_datareader 量化入門教學

我們之前有教過怎麼樣取當日所有股票股價的方法,但是假如我們想要做歷史回測,除了慢慢一天天抓,也可以使用 pandas_datareader 這個 package(可以用 pip install pandas_datareader 來安裝)

利用 Pandas 取得歷史股價教學縮圖

跟之前股價爬蟲的比較

之前我們教的股價爬蟲,是採取一天天下載的方式,今天教的方法是幾支股票,一次下載全部歷史股價!這個方法的優點是超簡單,而且下載速度又快,缺點是有些下市的股票的股價沒有辦法取得,會有生存者偏差,而且資料比較不齊全,但偶爾玩玩,練習一下 pandas 是很不錯的!

線上用 colab 練習此文章中的程式碼

首先先用 pandas_datareader 取得資料

取得資料又更簡單了!先匯入要用的包

import

顯示程式碼
from pandas_datareader import data # pip install pandas_datareader
import matplotlib.pyplot as plt    # pip install matplotlib
import pandas as pd                # pip install pandas
%matplotlib inline

然後:get data

顯示程式碼
data = data.DataReader("^TWII", "yahoo", "2000-01-01","2018-01-01")
c = data['Close']
c.plot()

台股大盤 TWII 加權指數收盤價走勢圖

就這樣,真的超簡單吧!假如你之前不會…現在跟你講了,別打我 XDD

這個方法爬到的資料真的比較不齊全啦!

用 pandas 計算 60日收盤價格

如何用 pandas 快速算出平均線呢? get data

顯示程式碼
# 近60日收盤
c60 = c.rolling(60, min_periods=1).mean()
 
# 畫圖
c['2015':].plot()
c60['2015':].plot()

台股大盤收盤價與 60 日移動平均線比較圖

第二行的 c 就是收盤價 close 的簡稱,是一個 series ,代表每一天收盤價的時間序列,可以上 pandas 官網上查詢 相關的用法,其中有一個好用的 function 叫做 rolling 其實它的含意就是隨時間移動窗格,將窗格中的收盤價取:

  • 60天最大值(c.rolling(60).max()
  • 60天平均(c.rolling(60).mean()
  • 60天最小值(c.rolling(60).min()

那為何我們還需要一個 min_periods=1 這個參數呢?因為照原本的設定,60天內只要有一個值是 NaN,則平均值就是 NaN,只要一筆資料有問題,你就有60天算不出平均值,所以 min_periods=1 就是在說,只要60天裡面有一天不是 NaN 就強制算的意思。

第五行跟第六行是畫圖,因為我們不想畫整整18年的圖,畫最近三年就好了,所以利用 [start:end] 來選擇時間,我們希望從 2015 年到此資料的最後一筆,所以 end 放空白(跟 python array 一樣的選取方式,只是改用日期)。

用 pandas 算出買入訊號

假如當日收盤 > 近60日收盤,則當日收盤瞬間買,不然則空手

這樣子的回測要怎麼寫呢?三行解決,有沒有比 multichart 還簡單!?

backtest

顯示程式碼
# 進60日收盤
c60 = c.rolling(60, min_periods=1).mean()
 
# 買入訊號
signal = (c > c60)
 
# 回測並跟大盤比較
(c.shift(-1) / c)[signal].cumprod().plot(color='red')
(c.shift(-1) / c).cumprod().plot(color='blue')

60 日均線策略回測資產曲線與大盤比較圖

  • 買入訊號(line 5)是如何建立的呢?原本的 cc60 都是 float series,然而這邊的 signal 是一個 boolean series,代表當天的 cc60 還要大,每一天都會有一個布林值,True 代表要在收盤價買入,而 False 代表在收盤價空手
  • 為何我們用一行(line 8)就可以回測呢?首先,我們將數值變成成長率 c.shift(-1)/c,其中 c.shift(-1) 代表明天的收盤價,而 c 代表今天的收盤價。這個成長率是一個近似於 1 的數值,大於1代表明天漲,小於1代表明天跌。我們將所有的成長率照著時間乘起來,就會還原成原本的大盤 c,然而我們只有在 signal = True 的時候持有大盤,資產才會隨著增長率變動。xx[signal] 的意思就是選取一個 sub-series,將 signal = False 的天給去除。所以我們只選 signal = True 的每一天相對應的成長率乘起來(cumprod()),就會是回測結果了!
  • 最後一行(line 9)是做什麼的?用來畫出大盤的,假設我們沒有用 xx[signal] 篩選,等於每天都買入的狀況,利用 cumprod 把每一天的成長率都乘起來。其實這行也可以寫成 (c/c[0]).plot(color='blue') 都是互通的,各位可以試試看。

這邊比較複雜,建議把 c(c.shift(-1)/c)signal,這些數值都 print 出來比較一番吧! get data

顯示程式碼
pd.DataFrame({'c':c, 'c60':c60, '增長率':c.shift(-1)/c, 'signal':signal}).head()

收盤價、60日均線、增長率與買入訊號 DataFrame 比較表格

小總結

  • 我沒有考慮手續費喔!考慮了以後,這個方法應該不會太好,這篇主要是帶大家練習 pandas!
  • 利用 Pandas 來攝取資料 1 行
  • 利用 Pandas 簡易回測 3 行
  • 學習 series 的操作

第一次看,應該會覺得 pandas 怎麼這麼厲害,但又很無奈自己無法玩轉操弄它。不用擔心,只要常常看這個系列,就會慢慢對 pandas 有感覺囉!

想建立自己的策略?

用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案

免費開始