我們之前有教過怎麼樣取當日所有股票股價的方法,但是假如我們想要做歷史回測,除了慢慢一天天抓,也可以使用 pandas_datareader
這個 package(可以用pip install pandas_datareader
來安裝)
跟之前股價爬蟲的比較
之前我們教的股價爬蟲,是採取一天天下載的方式,今天教的方法是幾支股票,一次下載全部歷史股價!這個方法的優點是超簡單,而且下載速度又快,缺點是有些下市的股票的股價沒有辦法取得,會有生存者偏差,而且資料比較不齊全,但偶爾玩玩,練習一下 pandas
是很不錯的!
首先先用 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()
就這樣,真的超簡單吧!假如你之前不會…現在跟你講了,別打我XDD
這個方法爬到的資料真的比較不齊全啦!
用 pandas 計算 60日收盤價格
如何用 pandas 快速算出平均線呢?
get data
# 近60日收盤
c60 = c.rolling(60, min_periods=1).mean()
# 畫圖
c['2015':].plot()
c60['2015':].plot()
第二行的 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')
- 買入訊號(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()
小總結
- 我沒有考慮手續費喔!考慮了以後,這個方法應該不會太好,這篇主要是帶大家練習pandas!
- 利用 Pandas 來攝取資料 1 行
- 利用 Pandas 簡易回測 3 行
- 學習
series
的操作
第一次看,應該會覺得pandas怎麼這麼厲害,但又很無奈自己無法玩轉操弄它。不用擔心,只要常常看這個系列,就會慢慢對pandas有感覺囉!