Backtrader同步不同市場

  |  

使用次數越多, backtrader 必須面對的想法和意外場景的混合就越多。對於每個新平臺,一個挑戰是要看看平臺是否能夠達到開發開始時設定的期望,靈活性和易用性是目標,Python被選為基石。

工單#76 提出了一個問題,即是否可以完成具有不同交易日曆的同步市場。直接嘗試這樣做失敗了,問題建立者想知道為什麼不 backtrader 查看日期。

在給出任何答案之前,必須考慮以下幾點:

  • 未對齊的天數的指示器的行為

對後者的回答是:

  • 該平台盡可能地 datetime 不可知論,不會查看字段的內容來評估這些概念

考慮到股票市場價格是datetime 序列的事實,上述可以保持 true 達到一定的限度。對於多個數據,以下設計注意事項適用:

  • 添加到cerebro的第 1數據是datamaster

  • 所有其他數據必須在時間上對齊/同步,它永遠無法超越(就datetime 而言) datamaster

將上面的3個項目符號放在一起,可以提供問題創建者所經歷的組合。場景:

  • 歷年:2012

  • 數據0:^GSPC (或朋友的標準普爾500指數)

  • 資料1:^GDAXI (或朋友的Dax指數)

透過以下方式執行自訂文稿以檢視資料是如何同步的backtrader

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

輸出:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
0002,  True, data0, 2012-01-04T23:59:59, 2012-01-04T23:59:59, data1
0003,  True, data0, 2012-01-05T23:59:59, 2012-01-05T23:59:59, data1
0004,  True, data0, 2012-01-06T23:59:59, 2012-01-06T23:59:59, data1
0005,  True, data0, 2012-01-09T23:59:59, 2012-01-09T23:59:59, data1
0006,  True, data0, 2012-01-10T23:59:59, 2012-01-10T23:59:59, data1
0007,  True, data0, 2012-01-11T23:59:59, 2012-01-11T23:59:59, data1
0008,  True, data0, 2012-01-12T23:59:59, 2012-01-12T23:59:59, data1
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-17T23:59:59, 2012-01-16T23:59:59, data1
0011, False, data0, 2012-01-18T23:59:59, 2012-01-17T23:59:59, data1
...

一旦2012-01-16 交易日曆發散。這是 data0 datamaster^GSPC),即使 data1^GDAXI)會有一個柱線來兌現 2012-01-16,這不是標準普爾500指數 的交易日

最好的是,backtrader 當下一個交易日 ^GSPC 進來時,可以對上述設計限制做些什麼, 2012-01-17 交付下一個尚未處理的日期 ^GDAXI2012-01-16.

同步問題隨著每個發散日而累積。在它的末尾2012 如下所示:

...
0249, False, data0, 2012-12-28T23:59:59, 2012-12-19T23:59:59, data1
0250, False, data0, 2012-12-31T23:59:59, 2012-12-20T23:59:59, data1

原因應該很明顯:歐洲人比美國人交易的天數更多。

在票證 #76https://github.com/mementum/backtrader/issues/76 中,海報顯示了什麼 zipline 。讓我們來看看 2012-01-13 - 2012-01-17 難題:

0009 : True : 2012-01-13 : close 1289.09 - 2012-01-13 :  close 6143.08
0010 : False : 2012-01-13 : close 1289.09 - 2012-01-16 :  close 6220.01
0011 : True : 2012-01-17 : close 1293.67 - 2012-01-17 :  close 6332.93

起泡的藤壺!2012-01-13 的數據只是簡單地 複製, 而沒有明顯要求用戶許可。恕我直言,這不應該是因為平臺的最終用戶無法撤消此自發添加。

注意

除了簡要介紹zipline之外,作者不知道這是否是腳本開發人員配置的標準行為,以及是否可以撤消

一旦我們看到其他人,讓我們再次backtrader嘗試使用積累的智慧:歐洲人比美國人更頻繁地交易。讓我們反轉 和 ^GDAXI 的角色^GSPC,看看結果:

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

輸出(跳到2012-01-13 直接):

...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-16T23:59:59, 2012-01-13T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

再次起泡藤壺!backtrader還複製了 2012-01-13 (在本例^GSPC中 ) 的值data1作為 (現在 ^GDAXI) 交付的2012-01-16匹配data0項。

甚至更好:

  • 在下一個日期實現同步: 2012-01-17

同樣的重新同步很快就會再次出現:

...
0034,  True, data0, 2012-02-17T23:59:59, 2012-02-17T23:59:59, data1
0035, False, data0, 2012-02-20T23:59:59, 2012-02-17T23:59:59, data1
0036,  True, data0, 2012-02-21T23:59:59, 2012-02-21T23:59:59, data1
...

其次是不太容易的重新同步:

...
0068,  True, data0, 2012-04-05T23:59:59, 2012-04-05T23:59:59, data1
0069, False, data0, 2012-04-10T23:59:59, 2012-04-09T23:59:59, data1
...
0129, False, data0, 2012-07-04T23:59:59, 2012-07-03T23:59:59, data1
0130,  True, data0, 2012-07-05T23:59:59, 2012-07-05T23:59:59, data1
...

這樣的情節不斷重複,直到 last^GDAXI 交付:

...
0256,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1
...

此同步問題backtrader 的原因是不會複製數據。

  • datamaster一旦交付了一個新的酒吧,另datas一個就會被要求交付

  • 如果沒有柱線可以傳遞電流datetime datamaster (例如,因為它將被超越),那麼下一個最佳數據就是,可以說,重新傳遞

    這是一個已經看到的酒吧date

正確同步

但並非所有的希望都消失了。backtrader 可以交付。讓我們使用 篩檢程式backtrader 這項技術允許在數據到達平臺最深處之前對其進行操作,例如計算指標。

注意

delivering 是一個感知問題,因此交付的 backtrader 可能不是接收者所期望的 delivery

實際代碼

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime


class WeekDaysFiller(object):
    '''Bar Filler to add missing calendar days to trading days'''
    # kickstart value for date comparisons
    lastdt = datetime.datetime.max.toordinal()

    def __init__(self, data, fillclose=False):
        self.fillclose = fillclose
        self.voidbar = [float('Nan')] * data.size()  # init a void bar

    def __call__(self, data):
        '''Empty bars (NaN) or with last close price are added for weekdays with no
        data

        Params:
          - data: the data source to filter/process

        Returns:
          - True (always): bars are removed (even if put back on the stack)

        '''
        dt = data.datetime.dt()  # current date in int format
        lastdt = self.lastdt + 1  # move the last seen data once forward

        while lastdt < dt:  # loop over gap bars
            if datetime.date.fromordinal(lastdt).isoweekday() < 6:  # Mon-Fri
                # Fill in date and add new bar to the stack
                if self.fillclose:
                    self.voidbar = [self.lastclose] * data.size()
                self.voidbar[-1] = float(lastdt) + data.sessionend
                data._add2stack(self.voidbar[:])

            lastdt += 1  # move lastdt forward

        self.lastdt = dt  # keep a record of the last seen date

        self.lastclose = data.close[0]
        data._save2stack(erase=True)  # dt bar to the stack and out of stream
        return True  # bars are on the stack (new and original)

測試文稿已經具備了使用它的功能:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler

--filler 添加到 WeekDaysFillerdata0 data1中。輸出:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010,  True, data0, 2012-01-16T23:59:59, 2012-01-16T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

第1 難題 - 2012-01-13 2012-01-17 已經消失了。並且整個集合是同步的:

...
0256,  True, data0, 2012-12-25T23:59:59, 2012-12-25T23:59:59, data1
0257,  True, data0, 2012-12-26T23:59:59, 2012-12-26T23:59:59, data1
0258,  True, data0, 2012-12-27T23:59:59, 2012-12-27T23:59:59, data1
0259,  True, data0, 2012-12-28T23:59:59, 2012-12-28T23:59:59, data1
0260,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1

值得注意的事情:

  • 正如我們^GSPClines250(指數交易250日)2012data0

  • data0 256 我們有^GDAXIlines(指數交易256日)2012

  • 隨著到位,WeekDaysFiller 兩個數據的長度已擴展到 260

    添加52 * 2 (週末和週末中的幾天),我們最終會得到 364.在一年中正常 365 日子之前的剩餘日子肯定是星期六或星期日。

篩選器填充NaN 給定數據未進行交易的天數的值。讓我們把它畫出來:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot

充滿的日子是顯而易見的:

  • 柱線之間的間隙就在那裡

  • 對於 volume 圖來說,差距更加明顯

第2個圖將嘗試回答頂部的問題:指標會發生什麼?請記住,新柱線的值已指定為 NaN (這就是為什麼它們不顯示):

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10

重新起泡藤壺!簡單移動平均線打破了時空連續體,在沒有連續性解的情況下跳過了一些柱。這當然是用Not a Number(又名NaN)填充的效果:數學運算不再有意義。

如果使用lastNaN看到的收盤價:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10 --fillclose

在整個260天內,使用常規SMA,該情節看起來要好得多。

結論

將兩種工具與不同的交易日歷同步是一個決策和妥協的問題。backtrader 需要時間一致的數據來處理多個數據,不同的交易日歷無濟於事。

使用這裡描述的WeekDaysFiller 可以緩解這種情況,但它絕不是普遍的靈丹妙藥,因為用哪些值來填補是一個長期和長期考慮的問題。

腳本代碼和用法

可作為以下來源backtrader的樣品:

$ ./weekdaysaligner.py --help
usage: weekdaysaligner.py [-h] [--online] --data0 DATA0 [--data1 DATA1]
                          [--sma SMA] [--fillclose] [--filler] [--filler0]
                          [--filler1] [--fromdate FROMDATE] [--todate TODATE]
                          [--plot]

Sample for aligning with trade

optional arguments:
  -h, --help            show this help message and exit
  --online              Fetch data online from Yahoo (default: False)
  --data0 DATA0         Data 0 to be read in (default: None)
  --data1 DATA1         Data 1 to be read in (default: None)
  --sma SMA             Add a sma to the datas (default: 0)
  --fillclose           Fill with Close price instead of NaN (default: False)
  --filler              Add Filler to Datas 0 and 1 (default: False)
  --filler0             Add Filler to Data 0 (default: False)
  --filler1             Add Filler to Data 1 (default: False)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default:
                        2012-01-01)
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format (default: 2012-12-31)
  --plot                Do plot (default: False)

代碼:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile

# from wkdaysfiller import WeekDaysFiller
from weekdaysfiller import WeekDaysFiller


class St(bt.Strategy):
    params = (('sma', 0),)

    def __init__(self):
        if self.p.sma:
            btind.SMA(self.data0, period=self.p.sma)
            btind.SMA(self.data1, period=self.p.sma)

    def next(self):
        dtequal = (self.data0.datetime.datetime() ==
                   self.data1.datetime.datetime())

        txt = ''
        txt += '%04d, %5s' % (len(self), str(dtequal))
        txt += ', data0, %s' % self.data0.datetime.datetime().isoformat()
        txt += ', %s, data1' % self.data1.datetime.datetime().isoformat()
        print(txt)


def runstrat():
    args = parse_args()

    fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
    todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')

    cerebro = bt.Cerebro(stdstats=False)

    DataFeed = btfeeds.YahooFinanceCSVData
    if args.online:
        DataFeed = btfeeds.YahooFinanceData

    data0 = DataFeed(dataname=args.data0, fromdate=fromdate, todate=todate)

    if args.data1:
        data1 = DataFeed(dataname=args.data1, fromdate=fromdate, todate=todate)
    else:
        data1 = data0.clone()

    if args.filler or args.filler0:
        data0.addfilter(WeekDaysFiller, fillclose=args.fillclose)

    if args.filler or args.filler1:
        data1.addfilter(WeekDaysFiller, fillclose=args.fillclose)

    cerebro.adddata(data0)
    cerebro.adddata(data1)

    cerebro.addstrategy(St, sma=args.sma)
    cerebro.run(runonce=True, preload=True)

    if args.plot:
        cerebro.plot(style='bar')


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for aligning with trade ')

    parser.add_argument('--online', required=False, action='store_true',
                        help='Fetch data online from Yahoo')

    parser.add_argument('--data0', required=True, help='Data 0 to be read in')
    parser.add_argument('--data1', required=False, help='Data 1 to be read in')

    parser.add_argument('--sma', required=False, default=0, type=int,
                        help='Add a sma to the datas')

    parser.add_argument('--fillclose', required=False, action='store_true',
                        help='Fill with Close price instead of NaN')

    parser.add_argument('--filler', required=False, action='store_true',
                        help='Add Filler to Datas 0 and 1')

    parser.add_argument('--filler0', required=False, action='store_true',
                        help='Add Filler to Data 0')

    parser.add_argument('--filler1', required=False, action='store_true',
                        help='Add Filler to Data 1')

    parser.add_argument('--fromdate', '-f', default='2012-01-01',
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('--todate', '-t', default='2012-12-31',
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--plot', required=False, action='store_true',
                        help='Do plot')

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()

推薦閱讀

相關文章

Backtrader規範與非規範

這個問題或多或少地出現了幾次:這樣: backtrader如何最好/規範地實現這一點或那樣? 作為 backtrader 的目標之一,可以靈活地 支援盡可能多的情況和用例,答案很簡單:“至少在幾種方式上”。

Backtrader期貨補償與現貨補償

版本1.9.32.116 增加了對社區中呈現的有趣用例 的支援 以期貨開始交易,包括實物交割 讓一個指標告訴你一些事情 如果需要, close 現貨價格操作,有效地取消實物交割,無論是為了接收貨物還是為了必須交付貨物(並希望獲利)來頭寸。

BacktraderPyFolio 集成

注意 2017年2月 pyfolio API 已更改,不再 create_full_tear_sheet 具有 gross_lev 作為命名參數的參數。

Backtrader教程:篩檢程式

此功能是 backtrader 的相對較新的補充,必須安裝到已經存在的內部結構中。這使得它不像希望的那樣靈活且100%功能齊全,但在許多情況下它仍然可以達到目的。 儘管該實現試圖允許隨插即用的篩檢程式連結,但預先存在的內部結構使得很難確保始終可以實現。因此,某些篩選器可能是連結的,而其他一些篩選器可能不是。

Backtrader數據多時間幀

有時投資決策是使用不同的時間框架做出的: 每周評估趨勢 每天執行條目 或者5分鐘對60分鐘。 這意味著需要將多個時間幀的數據組合在 backtrader 中以支援此類組合。 對它的本機支持已經內置。

Backtrader智慧質押

版本 1.6.4.93 標誌著 backtrader 的一個重要里程碑,即使版本號的更改很小。 職位大小調整是閱讀Van K. Tharp的《Trade Your Way To Financial Freedom 》後,為這個專案奠定基礎的事情之一。

Backtrader教程:經紀商

經紀商模擬器該類比支援不同的訂單類型,根據當前現金檢查提交的訂單現金需求,跟蹤每次反覆運算的cerebro 現金和價值,並在不同數據上保持當前位置。

Backtrader教程:觀察者 - 統計

在內部backtrader 運行的策略主要處理 data feeds 和 指標。 Data feeds 被添加到Cerebro 實例中,並最終成為策略輸入的一部分(解析並用作實例的屬性),而指標則由策略本身聲明和管理。

Backtrader卡爾曼等

注意 對以下指令的支援從提交開始 發佈1.9.30.x 將是包含它的第1個版本 。 backtrader的原始目標之一是成為純python,即:僅使用標準發行版中可用的軟體包。只有一個例外是matplotlib在沒有重新發明輪子的情況下進行繪圖。

Backtrader教程:指標 - ta-lib

即使 backtrader 提供了已經 high 數量的內置指標,並且開發指標主要是定義輸入,輸出和以自然的方式編寫公式的問題,有些人也希望使用TA-LIB。