Backtrader教程:觀察者 - 統計

  |  

在內部backtrader 運行的策略主要處理 data feeds指標

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

到目前為止,所有backtrader 示例圖表都繪製了3個東西,這些東西似乎被認為是理所當然的,因為它們沒有在任何地方聲明:

  • 現金和價值(經紀人中的錢發生了什麼)

  • 交易(又名運營)

  • 買入/賣出訂單

它們存在於Observers 子模組 backtrader.observers中。它們之所以存在,是因為 Cerebro 支援一個參數來自動將它們添加(或不添加)到策略中:

  • stdstats (預設值: True

如果遵循預設值Cerebro將執行以下等效的用戶代碼:

import backtrader as bt

...

cerebro = bt.Cerebro()  # default kwarg: stdstats=True

cerebro.addobserver(bt.observers.Broker)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell)

讓我們看看通常的圖表,其中包含這3個默認 observers (即使沒有發出訂單,因此沒有交易發生,現金和投資組合價值也沒有變化)

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

import backtrader as bt
import backtrader.feeds as btfeeds

if __name__ == '__main__':
    cerebro = bt.Cerebro(stdstats=False)
    cerebro.addstrategy(bt.Strategy)

    data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
    cerebro.adddata(data)

    cerebro.run()
    cerebro.plot()

現在,讓我們在創建Cerebro實例時將 False 的值stdstats更改為 (也可以在調用run時完成):

cerebro = bt.Cerebro(stdstats=False)

現在的圖表不同了。

存取 Observers

如上所示, Observers 在默認情況下已經存在,並且收集可用於統計目的的資訊,這就是為什麼可以通過策略的屬性 observers (稱為:

  • stats

它只是一個佔位元。如果我們還記得上面列出的預設Observers 之一:

...
cerebro.addobserver(backtrader.observers.Broker)
...

顯而易見的問題是如何訪問Brokerobserver。例如,如何從策略的方法完成next此操作:

class MyStrategy(bt.Strategy):

    def next(self):

        if self.stats.broker.value[0] < 1000.0:
           print('WHITE FLAG ... I LOST TOO MUCH')
        elif self.stats.broker.value[0] > 10000000.0:
           print('TIME FOR THE VIRGIN ISLANDS ....!!!')

Broker observer就像數據一樣,指標和策略本身也是一個Lines物件。在本例中,Broker有2個lines

  • cash

  • value

Observer 實施

實現與指標非常相似:

class Broker(Observer):
    alias = ('CashValue',)
    lines = ('cash', 'value')

    plotinfo = dict(plot=True, subplot=True)

    def next(self):
        self.lines.cash[0] = self._owner.broker.getcash()
        self.lines.value[0] = value = self._owner.broker.getvalue()

步驟:

  • 派生自Observer (而不是從 Indicator

  • 根據需要聲明 lines 和參數(Broker 有 2 個 lines 但沒有參數)

  • 將有一個自動屬性_owner ,它是持有 observer

Observers 行動:

  • 在計算完所有指標后

  • 在執行策略next 方法后

  • 這意味著:在週期結束時...他們觀察 發生了什麼

在這種情況下,Broker 它只是盲目地記錄經紀人在每個時間點的現金和投資組合價值。

向戰略添加Observers

如上所述,Cerebro 正在使用參數 stdstats 來決定是否添加3個預設 Observers,減輕最終使用者的工作。

在混合物中添加其他 Observers 是可能的,無論是沿著stdstats 還是刪除它們。

讓我們採用通常的策略,當價格高於aSimpleMovingAverageclose買入,如果相反true,則賣出。

通過一個「添加」:

  • DrawDown是生態系統中backtrader已經存在observer
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime
import os.path
import time
import sys


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


class MyStrategy(bt.Strategy):
    params = (('smaperiod', 15),)

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # SimpleMovingAverage on main data
        # Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
        sma = btind.SMA(period=self.p.smaperiod)

        # CrossOver (1: up, -1: down) close / sma
        self.buysell = btind.CrossOver(self.data.close, sma, plot=True)

        # Sentinel to None: new ordersa allowed
        self.order = None

    def next(self):
        # Access -1, because drawdown[0] will be calculated after "next"
        self.log('DrawDown: %.2f' % self.stats.drawdown.drawdown[-1])
        self.log('MaxDrawDown: %.2f' % self.stats.drawdown.maxdrawdown[-1])

        # Check if we are in the market
        if self.position:
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()

        elif self.buysell > 0:
            self.log('BUY CREATE, %.2f' % self.data.close[0])
            self.buy()


def runstrat():
    cerebro = bt.Cerebro()

    data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
    cerebro.adddata(data)

    cerebro.addobserver(bt.observers.DrawDown)

    cerebro.addstrategy(MyStrategy)
    cerebro.run()

    cerebro.plot()


if __name__ == '__main__':
    runstrat()

視覺輸出顯示回撤的演變

與部分文字輸出:

...
2006-12-14T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-15T23:59:59+00:00, DrawDown: 0.22
2006-12-15T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-18T23:59:59+00:00, DrawDown: 0.00
2006-12-18T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-19T23:59:59+00:00, DrawDown: 0.00
2006-12-19T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-20T23:59:59+00:00, DrawDown: 0.10
2006-12-20T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-21T23:59:59+00:00, DrawDown: 0.39
2006-12-21T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-22T23:59:59+00:00, DrawDown: 0.21
2006-12-22T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-27T23:59:59+00:00, DrawDown: 0.28
2006-12-27T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-28T23:59:59+00:00, DrawDown: 0.65
2006-12-28T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-29T23:59:59+00:00, DrawDown: 0.06
2006-12-29T23:59:59+00:00, MaxDrawDown: 2.62

注意

如文字輸出和代碼所示,DrawDownobserver實際上有 2 個lines

  • drawdown

  • maxdrawdown

選擇不是繪製maxdrawdownline,而是使其仍然可供使用者使用。

實際上,的 lastmaxdrawdown 也可以在直接屬性(不是 line)中使用,其名稱為 maxdd

開發 Observers

observerBroker實施情況如上所示。為了生成有意義的observer,實現可以使用以下資訊:

  • self._owner 是當前正在執行的策略

    因此,策略中的任何內容都可供 observer

  • 原則中可用的預設內部內容可能很有用:

    • broker ->屬性,用於訪問策略創建訂單的代理實例

    如 所示Broker,現金和投資組合價值是通過調用方法 getcashgetvalue

    • _orderspending ->列出由策略創建的訂單,並且經紀人已向策略通知事件。

    BuySell observer遍曆清單,查找已執行(全部或部分)的訂單,以創建給定時間點(索引 0)的平均執行價格

    • _tradespending ->交易清單(一組已完成的買入/賣出或賣出/買入對),該列表是根據買入/賣出訂單編譯的

Observer顯然可以通過路徑訪問其他observersself._owner.stats

自訂訂單觀察器

標準BuySellobserver只關心已執行的操作。我們可以創建一個observer顯示訂單的創建時間和到期時間。

為了提高可見性,顯示不會沿價格繪製,而是在單獨的軸上繪製。

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

import math

import backtrader as bt


class OrderObserver(bt.observer.Observer):
    lines = ('created', 'expired',)

    plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)

    plotlines = dict(
        created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
        expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
    )

    def next(self):
        for order in self._owner._orderspending:
            if order.data is not self.data:
                continue

            if not order.isbuy():
                continue

            # Only interested in "buy" orders, because the sell orders
            # in the strategy are Market orders and will be immediately
            # executed

            if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
                self.lines.created[0] = order.created.price

            elif order.status in [bt.Order.Expired]:
                self.lines.expired[0] = order.created.price

自定義 observer 只關心買入 訂單,因為這是一種僅為了獲利而買入的策略。賣出訂單是市價訂單,將立即執行。

Close-SMA 交叉策略更改為:

  • 創建一個限價訂單,其價格低於信號時刻 close 價格的1.0%

  • 訂單有效期為 7(日曆)天

生成的圖表。

在新子圖表(紅色方塊)中可以看到,有幾個訂單已經過期,我們也可以理解,在“創建”和“執行”之間恰好有幾天。

最後,此策略的代碼應用新的observer

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

import datetime

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

from orderobserver import OrderObserver


class MyStrategy(bt.Strategy):
    params = (
        ('smaperiod', 15),
        ('limitperc', 1.0),
        ('valid', 7),
    )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
            self.order = order
            return

        if order.status in [order.Expired]:
            self.log('BUY EXPIRED')

        elif order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))

            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))

        # Sentinel to None: new orders allowed
        self.order = None

    def __init__(self):
        # SimpleMovingAverage on main data
        # Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
        sma = btind.SMA(period=self.p.smaperiod)

        # CrossOver (1: up, -1: down) close / sma
        self.buysell = btind.CrossOver(self.data.close, sma, plot=True)

        # Sentinel to None: new ordersa allowed
        self.order = None

    def next(self):
        if self.order:
            # pending order ... do nothing
            return

        # Check if we are in the market
        if self.position:
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()

        elif self.buysell > 0:
            plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
            valid = self.data.datetime.date(0) + \
                datetime.timedelta(days=self.p.valid)
            self.log('BUY CREATE, %.2f' % plimit)
            self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)


def runstrat():
    cerebro = bt.Cerebro()

    data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
    cerebro.adddata(data)

    cerebro.addobserver(OrderObserver)

    cerebro.addstrategy(MyStrategy)
    cerebro.run()

    cerebro.plot()


if __name__ == '__main__':
    runstrat()

保存/保留統計資訊

截至目前backtrader ,尚未實施任何機制來跟蹤 observers 的值將其存儲到檔中。最好的方法:

  • 在策略的方法中startOpen

  • 在策略的方法中next 寫下值

考慮到DrawDownobserver,它可以像這樣完成

class MyStrategy(bt.Strategy):

    def start(self):

        self.mystats = open('mystats.csv', 'wb')
        self.mystats.write('datetime,drawdown, maxdrawdown\n')

    def next(self):
        self.mystats.write(self.data.datetime.date(0).strftime('%Y-%m-%d'))
        self.mystats.write(',%.2f' % self.stats.drawdown.drawdown[-1])
        self.mystats.write(',%.2f' % self.stats.drawdown.maxdrawdown-1])
        self.mystats.write('\n')

要保存索引 0 的值,一旦處理完所有 observers ,就可以將寫入檔的自定義 observer 作為 last observer 添加到系統中,以將值保存到 csv 檔。

注意

Writer功能可以自動執行此任務。

推薦閱讀

相關文章

Backtrader教程:觀察者 - 參考

基準 backtrader類 .observers.基準() 此 observer 存儲策略的回報和參考資產的回報,參考資產是傳遞到系統的數據之一。

Backtrader教程:指標 - 開發

如果必須開發任何東西(除了一個或多個獲勝策略之外),那麼這個東西就是一個自定義指標。 根據作者的說法,平臺內的這種開發很容易。 需要滿足以下條件: 從指標派生的類(直接或從現有的子類派生) 定義它將保持lines 指標必須至少具有 1 line。

Backtrader期貨補償與現貨補償

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

BacktraderPyFolio 集成

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

Backtrader教程:Cerebro - 優化 - 改進

backtrader版本1.8.12.99改進了在多處理過程中管理data feeds和結果的方式。

Backtrader數據多時間幀

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

Backtrader條形同步

文獻和/或行業中缺乏標準公式不是問題,因為問題實際上可以總結為: 條形同步 工單 #23 提出了一些問題,即是否可以 backtrader 考慮計算 相對體積 指標。 請求者需要將給定時刻的 volume 與前一個交易日的相同時刻進行比較。

Backtrader交叉回溯測試陷阱

在backtrader 社區中 ,傾向於重複的事情是,用戶解釋了複製在例如 TradingView 中獲得的回溯測試結果的意願,這些天非常流行,或者其他一些回溯測試平臺。

Backtrader蟒蛇隱藏的力量3

Last,但並非最不重要的一點是,在這個系列中,關於如何在 backtrader 中使用Python的隱藏功能是一些神奇變數是如何出現的。

Backtrader信貸利息

在某些情況下,真實經紀人的現金金額可能會減少,因為資產操作包括利率。例子: 賣空股票 交易所買賣基金包括多頭和空頭 這意味著不僅交易構成了系統的盈利能力,因為信貸上的利息在帳戶上佔有一席之地。 為了涵蓋這種情況, backtrader 包括(從發佈1.8.8.96開始)功能來考慮這一點。