跳至主要内容

Python 交易回測工具 Backtesting.py 簡易教學

· 6 分鐘閱讀
Eric Cheng
Eric Cheng
JAVA 後端工程師

計畫要寫所有「可能有用」的技術指標的回測文,但要完成這個目的,需要先有合適的工具,算是個簡單然後沒幾篇文章的系列文來介紹這些工具吧

  1. 首先要能取得股價:請參考【如何使用Python取得歷史股價,簡介yfinance、ffn、FinMind】
  2. 要回測技術指標,當然要能產生取得技術指標:請參考【如何使用Python產生技術指標?TA-Lib簡易教學】

然後有了股價、技術指標,再來就是回測工具了...

簡介 Backtesting.py​

【官網】、【Github】

最基本的,要使用 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 可以看到以下的資料

backtesting.py 範例程式結果 1

有在看這個 blog 回測文的,應該對大部份的指標都還算熟悉,這裏就不一一解釋每個名詞了,比較有趣的是裏面有個「Buy & Hold Return」,可以比較制訂出來的策略和單純的買入後持有之間的比較

以及可以直接繪圖,用圖形化更清楚的表示結果

backtesting.py 範例程式結果 2

置換資料源​

看完 sample code 看似好像會有股價資料了,但仔細看 GOOG 的時間是 2004~2013,或者把 GOOG 換成 AAPL 就直接報錯了,看了一下原始碼,lib 只提供了 GOOG 和 EURUSD 的資料,所以資料源是需要自己找的

backtesting.py 測試資料

好在我們本來就知道怎麼取得股價了,【如何使用Python取得歷史股價,簡介yfinance、ffn、FinMind】,現在實作把回測的股價換成台股的 0050

先看一下範例程式中 GOOG 的欄位

backtesting.py 測試資料欄位

要用 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,沒別的了

backtesting.py SMA function

好在我們也本來就知道怎麼取得技術指標,【如何使用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()

完整的回測結果就出來了 backtesting.py 0050 KD

應該有人對這個結果有點熟悉,沒錯,這就是一年多前【K<20買,K>80賣真能打敗大盤?用KD指標回測0050歷史股價】這篇文章的程式碼, 拖了一年多,終於把教學文件寫出來了.. XD

小結​

先這樣吧,完整程式碼我放在【Colab】,希望有幫到有興趣的人



加入fb粉絲團!第一時間取得網站更新訊息

版權聲明

,轉載請註明出處
本文連結: https://havocfuture.tw/blog/python-backtesting-py