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

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

thumbnail 2 1

跟之前股價爬蟲的比較

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

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

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

用 pandas 計算 60日收盤價格

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

# 近60日收盤
c60 = c.rolling(60, min_periods=1).mean()

# 畫圖
c['2015':].plot()
c60['2015':].plot()
avg

第二行的 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')
eq 2 1

  • 買入訊號(line 5)是如何建立的呢?原本的 c 和 c60 都是 float series,然而這邊的 signal 是一個 boolean series,代表當天的c 比 c60還要大,每一天都會有一個布林值,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()
tb

小總結

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

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

FinLab - 韓承佑

嗨大家好,我是韓承佑,FinLab創辦人,畢業於巴黎薩克雷大學資工博士,目前擔任臺灣量化交易協會 學術顧問、台北商業大學 創新育成中心 創業技術顧問與上市科技公司 量化交易顧問。當初,我喜歡寫程式、無意間因為軟體比賽接觸Fintech,從此開始了財經跟程式的學習之路。我們成立 FinLab 量化投資部落格,用自己研發的軟體,對台灣股市做大量快速的實驗。希望可以在量化投資的路上,當大家的「武器製造商」!