最近轉換到 Python 環境下開發選股系統
確實比以前用 Excel VBA 硬刻要輕鬆許多
今天整理一下使用回測套件的一些心得
在Python環境上,常見的交易回測套件有下列三個
[1] backtesting.py [官網]
- 相當簡單的說明文檔
- 簡潔、上手容易
- 網路上沒有太多的討論,主要看GitHub裡的討論 [Discussions]
[2] backtrader [官網]
- 使用人口眾多
- 據說在回測功能上可以有許多的參數調整
- 當然入門的門檻就高些
[3] FinMind
- 台灣的開發者,值得推薦一下
- 主要是在尋找台股股價資料源時看到,對於量化投資的入門者而言,是相當值得期待的平台
- 但是因為筆者已經具備有自己多年下來的投資架構,故只是採用其資料源
筆者直接就使用了 Backtesting.py
原因也很簡單
因為一用就上手,也沒有什麼特別覺得欠缺的
所以也就持續用下來了
以下就彙整一下使用上的心得
[1] 在 Colab 安裝
# Colab沒有預設,第一次用得自己安裝
!pip install backtesting > log.txt
#引入回測和交易策略功能
from backtesting import Backtest, Strategy
#從lib子模組引入判斷均線交會功能
from backtesting.lib import crossover
#從test子模組引入繪製均線功能
from backtesting.test import SMA
[2] Strategy的撰寫
以筆者現在在用的策略撰寫說明,紅字標示的部分,就是它的關鍵語法
這是一個只做多的策略,寫得很簡單
各位可以網路上在多看看範例
class MA_Slope_v20230727(Strategy): #交易策略命名
def init(self):
super().init() #繼承Strategy的初始化函式
def next(self):
if (self.data.sma_buy > self.data.sma_buy_lag
and self.data.Close > self.data.sma_100
and self.data.sma_buy > self.data.sma_100
and self.data.sma_sell > self.data.sma_100
and self.data.sma_sell > self.data.sell_down) and (not self.position):
self.buy() # Buy
elif self.position.is_long and (self.position.pl_pct<-0.10
or (self.position.pl_pct>0 and self.data.sma_sell < self.data.sma_100)
or (self.position.pl_pct>0 and self.data.sma_sell < self.data.sell_down)):
self.position.close() # Buy Exit
self.position.is_long 是指目前部位是否為做多
self.position.pl_pct 是指目前部位的損益率
這些都可以在 [官網這裡] 去了解,筆者也只是找了需要的功能了解一下該如何用
[3] 執行回測
第一個要注意的是,backtesting.py 有資料欄位名稱的要求,以下是個例子
#將欄位名稱更換為basktesting套件的設定, 並指定給df_back
df_back = my_stock.rename(columns={"date":"Date","開盤價": "Open", "最高價": "High", "最低價": "Low", "收盤價": "Close", "成交量_股": "Volume"})
第二個是回溯測試的參數設定
# 設定
bt = Backtest(df_back, MA_Slope_v0727, cash=100000, commission=0, exclusive_orders=False,
trade_on_close=False)
# 'cash' is the initial cash to start with.
# 'commission' is the commission ratio. E.g. if your broker's commission is 1% of trade value, set commission to 0.01. Note, if you wish to account for bid-ask spread, you can approximate doing so by increasing the commission, e.g. set it to 0.0002 for commission-less forex trading where the average spread is roughly 0.2‰ of asking price.
# 'margin' is the required margin (ratio) of a leveraged account. No difference is made between initial and maintenance margins. To run the backtest using e.g. 50:1 leverge that your broker allows, set margin to 0.02 (1 / leverage).
# If 'trade_on_close' is True, market orders will be filled with respect to the current bar's closing price instead of the next bar's open.
# => True=賣出訊號於第T日出現,則以T日收盤價賣出 / False=賣出訊號於第T日出現,則以T+1日開盤價賣出 => 對盤後才計算買賣訊號者,False比較合理
# If 'hedging is True', allow trades in both directions simultaneously. If False, the opposite-facing orders first close existing trades in a FIFO manner.
# If 'exclusive_orders' is True, each new order auto-closes the previous trade/position, making at most a single trade (long or short) in effect at each time.
# 執行
output = bt.run()
回溯結果就存在 output 裡,以下是取用的範例
print(output)
print(output['_trades'])
print(output['_equity_curve'])
my_data=[all_stock.iloc[my_j-1,0],my_ticker,all_stock.iloc[my_j-1,2],output['Buy & Hold Return [%]'],output['Return [%]'],
my_god_index,output['# Trades'],output['Win Rate [%]'],output['Profit Factor'],
output['Return (Ann.) [%]'],output['Volatility (Ann.) [%]'],output['Sharpe Ratio'],output['Start'].strftime("%Y-%m-%d"),output['End'].strftime("%Y-%m-%d"),
my_set_ma_buy,my_set_ma_sell,my_set_loc_BetweenMaxMin,my_set_loc_BelowMax,my_set_shift,my_set_buy_up,my_set_sell_down,output['_strategy']]
my_last_trade=[all_stock.iloc[my_j-1,0],my_ticker,all_stock.iloc[my_j-1,2],my_market_value,output['Start'].strftime("%Y-%m-%d"),output['# Trades'],
output['Win Rate [%]'],output['Profit Factor'],my_trade_active]+output['_trades'].iloc[-1].values.tolist() # list 的相加
以上,供各位參考。