利用Pandas輕鬆取得股價並回測

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

真的很簡單!

跟之前股價爬蟲的比較

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

首先先用 pandas_datareader 取得資料

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

import
1
2
3
4
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
1
2
3
data = data.DataReader("^TWII", "yahoo", "2000-01-01","2018-01-01")
c = data['Close']
c.plot()
台股 2000~2018 每天收盤價

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

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

用 pandas 計算 60日收盤價格

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

get data
1
2
3
4
5
6
# 近60日收盤
c60 = c.rolling(60, min_periods=1).mean()
# 畫圖
c['2015':].plot()
c60['2015':].plot()

台股 2000~2018 每天收盤價

第二行的 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
1
2
3
4
5
6
7
8
9
10
# 進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')
紅色是策略的權益函數,藍色是大盤
  • 買入訊號(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
1
pd.DataFrame({'c':c, 'c60':c60, '增長率':c.shift(-1)/c, 'signal':signal}).head()

印出來比較一下!

小總結

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

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

假如覺得文章不錯,那更不能錯過我們的影音課程喔!
或我們按個 鼓勵一下吧!