計畫要寫所有「可能有用」的技術指標的回測文,但要完成這個目的,需要先有合適的工具,算是個簡單然後沒幾篇文章的系列文來介紹這些工具吧
- 首先要能取得股價:請參考【如何使用Python取得歷史股價,簡介yfinance、ffn、FinMind】
- 要回測技術指標,當然要能產生取得技術指標:請參考【如何使用Python產生技術指標?TA-Lib簡易教學】
然後有了股價、技術指標,再來就是回測工具了...
簡介 Backtesting.py
最基本的,要使用 library,要先執行安裝
pip3 install backtesting
官網 sample code 說明
官網上直接有 sample code,那當然就是先直接跑一次看看結果,這篇文章撰寫時的 sample code 如下
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG
class SmaCross(Strategy):
n1 = 10
n2 = 20
def init(self):
close = self.data.Close
self.sma1 = self.I(SMA, close, self.n1)
self.sma2 = self.I(SMA, close, self.n2)
def next(self):
if crossover(self.sma1, self.sma2):
self.buy()
elif crossover(self.sma2, self.sma1):
self.sell()
bt = Backtest(GOOG, SmaCross,
cash=10000, commission=.002,
exclusive_orders=True)
output = bt.run()
bt.plot()
很簡單的範例,說明一下這個範例做了哪些事
- 從 backtesting.test 取得 GOOG 的股價
- 從 backtesting.test 使用 SMA(移動平均) function 取得 10 日和 20 日的移動平均值
- 從 backtesting.lib 使用 crossover function 執行策略:
當 10 日移動平均往上穿越 20 日移動平均時「買進」
當 10 日移動平均往下穿越 20 日移動平均時「賣出」 - 範例的三個參數:
cash: 初始的現金
commission: 交易成本,就交易一次的手續費、交易稅的總合
exclusive_orders: True 代表每筆新交易會關閉上一次的交易
print output 可以看到以下的資料
有在看這個 blog 回測文的,應該對大部份的指標都還算熟悉,這裏就不一一解釋每個名詞了,比較有趣的是裏面有個「Buy & Hold Return」,可以比較制訂出來的策略和單純的買入後持有之間的比較
以及可以直接繪圖,用圖形化更清楚的表示結果
置換資料源
看完 sample code 看似好像會有股價資料了,但仔細看 GOOG 的時間是 2004~2013,或者把 GOOG 換成 AAPL 就直接報錯了,看了一下原始碼,lib 只提供了 GOOG 和 EURUSD 的資料,所以資料源是需要自己找的
好在我們本來就知道怎麼取得股價了,【如何使用Python取得歷史股價,簡介yfinance、ffn、FinMind】,現在實作把回測的股價換成台股的 0050
先看一下範例程式中 GOOG 的欄位
要用 backtesting.py 資料的欄位要一樣,要不然會出現data must be a pandas.DataFrame with columns 'Open', 'High', 'Low', 'Close', and (optionally) 'Volume'
的錯誤訊息
FinMind 的資料就要做些轉換,像是將欄位第一個字母改成大寫 ex: open => Open,以及將 index 改為日期的型態,完整程式如下
from FinMind.data import DataLoader
import pandas as pd
dl = DataLoader()
df = dl.taiwan_stock_daily(stock_id='0050', start_date='2003-01-01', end_date='2023-02-25')
df = df.rename(columns={"date": "Date"})
df.set_index("Date" , inplace=True)
df = df.rename(columns={"open": "Open", "max": "High", "min": "Low", "close": "Close", "Trading_Volume": "Volume"})
df = df.rename(columns={"Trading_Volume": "Volume"})
df = df.set_index(pd.DatetimeIndex(pd.to_datetime(df.index)))
置換技術指標
sample code 中有 SMA(移動平均),好像也有技術指標了?實際上看一下 Github 原始碼,可以看到它其實就只有一個 SMA functoin,沒別的了
好在我們也本來就知道怎麼取得技術指標,【如何使用Python產生技術指標?TA-Lib簡易教學】,現在實作日K<20買進,日K>80賣出
完整程式如下
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from FinMind.data import DataLoader
import pandas as pd
import talib
from talib import abstract
## 取得資料
dl = DataLoader()
df = dl.taiwan_stock_daily(stock_id='0050', start_date='2003-01-01', end_date='2023-02-25')
## 整理資料格式
df = df.rename(columns={"date": "Date"})
df.set_index("Date" , inplace=True)
df = df.set_index(pd.DatetimeIndex(pd.to_datetime(df.index)))
## backtesting.py 格式
df1 = df.rename(columns={"open": "Open", "max": "High", "min": "Low", "close": "Close", "Trading_Volume": "Volume"})
## ta-lib 格式
df2 = df.rename(columns={"max": "high", "min": "low", "Trading_Volume": "Volume"})
## 取得 KD 值
df_kd = abstract.STOCH(df2,fastk_period=9, slowk_period=3,slowd_period=3)
## 合併資料
df1 = pd.merge(df1, df_kd, on="Date")
## KD 策略
class KdCross(Strategy):
def init(self):
super().init()
def next(self):
if crossover(20, self.data.slowk): ## K<20 買進
self.buy()
elif crossover(self.data.slowk, 80): ## K>80 平倉
self.position.close()
bt = Backtest(df1, KdCross, cash=10000, commission=.001798) ## 交易成本 0.1798%
output = bt.run()
bt.plot()
完整的回測結果就出來了
應該有人對這個結果有點熟悉,沒錯,這就是一年多前【K<20買,K>80賣真能打敗大盤?用KD指標回測0050歷史股價】這篇文章的程式碼, 拖了一年多,終於把教學文件寫出來了.. XD
小結
先這樣吧,完整程式碼我放在【Colab】,希望有幫到有興趣的人