本文主要包含以下三部分内容:
-
- backtrader的日志功能。
- backtrader的交易月曆。
- backtrader的resample結果淺析。
日志功能
可以通過下面的代碼在backtrader中添加日志功能:
cerebro.addwriter(bt.WriterFile, out = 'log.csv', csv = True)
日志資訊将被輸出到工作目錄下的log.csv檔案中,輸出内容包括:
-
- 種子資料(Data Feeds)
- 政策資料(lines和參數)
- 名額和觀察者(Observer)資料(lines和參數)
- 分析資料(參數和分析結果資料)
交易月曆
交易月曆适用于以下場景:
-
由日線資料resample得到周線資料時,使用交易月曆可以準确識别每周的最後1根日線。
以2019年1月1日至12月31日的資料為例,在政策的next()方法中列印一下内容:
print('Strategy len {} datetime {}'.format(
len(self), self.datetime.date()), end=' ')
print('Data0 len {} datetime {}'.format(
len(self.data0), self.data0.datetime.date()), end=' ')
if len(self.data1):
print('Data1 len {} datetime {}'.format(
len(self.data1), self.data1.datetime.date()))
else:
print()
當不使用交易月曆時,部分輸出結果為:
Strategy len 1 datetime 2019-01-02 Data0 len 1 datetime 2019-01-02
Strategy len 2 datetime 2019-01-03 Data0 len 2 datetime 2019-01-03
Strategy len 3 datetime 2019-01-04 Data0 len 3 datetime 2019-01-04
Strategy len 4 datetime 2019-01-07 Data0 len 4 datetime 2019-01-07 Data1 len 1 datetime 2019-01-04
Strategy len 5 datetime 2019-01-08 Data0 len 5 datetime 2019-01-08 Data1 len 1 datetime 2019-01-04
Strategy len 6 datetime 2019-01-09 Data0 len 6 datetime 2019-01-09 Data1 len 1 datetime 2019-01-04
Strategy len 7 datetime 2019-01-10 Data0 len 7 datetime 2019-01-10 Data1 len 1 datetime 2019-01-04
Strategy len 8 datetime 2019-01-11 Data0 len 8 datetime 2019-01-11 Data1 len 1 datetime 2019-01-04
Strategy len 9 datetime 2019-01-14 Data0 len 9 datetime 2019-01-14 Data1 len 2 datetime 2019-01-11
...
在輸出的每一行中,第一個日期為政策所使用的日期,第二個日期為目前日線的日期,第三個日期為目前月線的日期。
其中,第3行1月4日為星期五,應該出現第一根周線資料,但是輸出結果中卻沒有周線資料。第8行1月11日為周五,從1月7日至1月11日的5根日線可以合成1根周線,後面的周線資料也應該更新為1月11日,但是輸出結果卻是1月4日。
産生以上結果的原因是,backtrader把2019年1月1日當作了交易日,隻是沒有讀入資料。這樣1月1日至4日,再加上7日共計5根日線,就在1月7日合成了第一根周線;同樣1月8日至11日,再加上14日共計5根日線,在1月14日合成了第二根周線。這顯然不是想要的結果。
解決上述問題的方案是,通過繼承bt.TradingCalendar定義新的月曆,然後在月曆中設定節假日。如以下代碼所示,将2019年1月1日添加到holidays清單中,然後在cerebro添加該月曆:
class AStockCalendar(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2019, 1, 1),
],
open=datetime.time(9, 30),
close=datetime.time(15, 0),
)
cerebro.addcalendar(AStockCalendar())
當使用交易月曆時,部分輸出結果為:
Strategy len 1 datetime 2019-01-02 Data0 len 1 datetime 2019-01-02
Strategy len 2 datetime 2019-01-03 Data0 len 2 datetime 2019-01-03
Strategy len 3 datetime 2019-01-04 Data0 len 3 datetime 2019-01-04 Data1 len 1 datetime 2019-01-04
Strategy len 4 datetime 2019-01-07 Data0 len 4 datetime 2019-01-07 Data1 len 1 datetime 2019-01-04
Strategy len 5 datetime 2019-01-08 Data0 len 5 datetime 2019-01-08 Data1 len 1 datetime 2019-01-04
Strategy len 6 datetime 2019-01-09 Data0 len 6 datetime 2019-01-09 Data1 len 1 datetime 2019-01-04
Strategy len 7 datetime 2019-01-10 Data0 len 7 datetime 2019-01-10 Data1 len 1 datetime 2019-01-04
Strategy len 8 datetime 2019-01-11 Data0 len 8 datetime 2019-01-11 Data1 len 2 datetime 2019-01-11
Strategy len 9 datetime 2019-01-14 Data0 len 9 datetime 2019-01-14 Data1 len 2 datetime 2019-01-11
...
可以看到,在1月4日(星期五)合成了第一根周線,在1月11日(星期五)合成了第二根周線,得到了正确的結果。
同樣,可以把2019年的其他節假日都添加到holidays清單中,來實作整年的周線資料的正确合成。
-
日内分時資料resample合成日線資料時,使用交易月曆處理提前收盤的情況。
美股在感恩節後的第一天,會在下午1點休市,這樣如果不做處理,使用分鐘資料合成日線資料時就會出問題,可以通過添加以下月曆來解決:
class CustomCalendar(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
],
earlydays=[
(datetime.date(2016, 11, 25),
datetime.time(9, 30), datetime.time(13, ,0))
],
open=datetime.time(9, 30),
close=datetime.time(16, 0),
)
以2016年為例,月曆中設定了2016年的節假日,一般每日交易時間為9:30至16:00,11月25日的交易時間為9:30至13:00。
resample淺析
借助于日志功能,對resample功能進行簡單分析。這裡以30分鐘資料為資料源,使用resample來合成60分鐘資料,同時加載已下載下傳好的60分鐘資料用于對比resample結果,部分代碼如下:
data = load_data(stk_code, fromdate, todate, '30')
cerebro.adddata(data, name = stk_code + '_30m')
cerebro.resampledata(data, name = stk_code + '_30to60m', timeframe = bt.TimeFrame.Minutes, compression = 60)
data = load_data(stk_code, fromdate, todate, '60')
cerebro.adddata(data, name = stk_code + '_60m'
-
- 标黃的D、L、T列分别表示加載的30分鐘時間标簽資料、由30分鐘資料合成的60分鐘時間标簽資料、加載的60分鐘時間标簽資料。
- 對比D列和T列可以發現,在加載的60分鐘資料中,6月1日首個時間點(10:00)資料為空,在時間點10:30出現第一個資料。在D列時間标簽變為11:00時,T列時間标簽仍為10:30。在D列時間标簽變為11:30時,T列時間标簽才變為11:30。這樣就保證了,在同時加載多個周期資料進行回測時,不會發生使用未來資料的情況。
- 對比D列和L列發現,resample結果未按預期得到10:30、11:30、14:00、15:00四個時段的60分鐘資料,而是得到10:00、11:00、12:00、14:00、15:00五個整點時間段的資料。筆者嘗試了調整函數resampledata()的bar2edge、rightedge、adjbartime、boundoff參數,也嘗試了使用交易月曆功能設定每日交易起止時間,均未能實作預期的resample結果。目前的解決方案是,在合成分鐘級資料時,不使用backtrader的resampledata()函數,而是先離線合成所需周期的資料,然後使用cerebro.adddata()加載合成的資料,再進行多周期政策的回測。