天天看點

Python量化交易學習筆記(40)——backtrader的resample淺析

本文主要包含以下三部分内容:

  1. backtrader的日志功能。
  2. backtrader的交易月曆。
  3. backtrader的resample結果淺析。

日志功能

可以通過下面的代碼在backtrader中添加日志功能:

cerebro.addwriter(bt.WriterFile, out = 'log.csv', csv = True)      

日志資訊将被輸出到工作目錄下的log.csv檔案中,輸出内容包括:

  • 種子資料(Data Feeds)
  • 政策資料(lines和參數)
  • 名額和觀察者(Observer)資料(lines和參數)
  • 分析資料(參數和分析結果資料)

交易月曆

交易月曆适用于以下場景:

  1. 由日線資料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清單中,來實作整年的周線資料的正确合成。

  1. 日内分時資料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'      
  1. 标黃的D、L、T列分别表示加載的30分鐘時間标簽資料、由30分鐘資料合成的60分鐘時間标簽資料、加載的60分鐘時間标簽資料。
  2. 對比D列和T列可以發現,在加載的60分鐘資料中,6月1日首個時間點(10:00)資料為空,在時間點10:30出現第一個資料。在D列時間标簽變為11:00時,T列時間标簽仍為10:30。在D列時間标簽變為11:30時,T列時間标簽才變為11:30。這樣就保證了,在同時加載多個周期資料進行回測時,不會發生使用未來資料的情況。
  3. 對比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()加載合成的資料,再進行多周期政策的回測。