在 backtrader 內部運行的策略主要處理數據 和 指標。
數據被添加到Cerebro 實例中,並最終成為策略輸入的一部分(解析並用作實例的屬性),而指標由策略本身聲明和管理。
到目前為止,所有 backtrader 樣圖都有3件事似乎是理所當然的,因為它們沒有在任何地方聲明:
-
現金和價值(經紀人中的錢發生了什麼)
-
交易(又名運營)
-
買入/賣出訂單
它們存在於Observers
子模組 backtrader.observers
中。它們之所以存在,是因為 Cerebro 支援一個參數來自動添加(或不添加)它們到策略中:
stdstats
(預設值: True)
如果遵循預設值,Cerebro將執行以下等效的用戶代碼:
import backtrader as bt ... cerebro = bt.Cerebro() # default kwarg: stdstats=True cerebro.addobserver(backtrader.observers.Broker) cerebro.addobserver(backtrader.observers.Trades) cerebro.addobserver(backtrader.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) ...
顯而易見的問題是如何訪問Broker
observer。例如,如何從策略的方法完成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
還是刪除它們。
讓我們採用通常的策略,當價格高於aSimpleMovingAverage
時close
買入,如果相反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):
視覺輸出顯示回撤的演變
與部分文字輸出:
... 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
注意
從文字輸出和代碼中可以看出,DrawDown
observer實際上有2個lines:
-
drawdown
-
maxdrawdown
選擇不是繪製maxdrawdown
line,而是使其仍然可供使用者使用。
實際上, last 值maxdrawdown
也可以在直接屬性(不是 line)中使用,其名稱為 maxdd
開發 Observers
observer的Broker
實施情況如上所示。要生成有意義的observer,實現可以使用以下資訊:
-
self._owner
是當前正在執行的策略因此,策略中的任何內容都可供 observer
-
原則中可用的預設內部內容可能很有用:
broker
->屬性,用於訪問策略創建訂單的代理實例
如 所示
Broker
,現金和投資組合價值是通過調用方法getcash
和getvalue
_orderspending
->列出由策略創建的訂單,並且經紀人已向策略通知事件。
BuySell
observer遍曆清單,查找已執行(全部或部分)的訂單,以創建給定時間點(索引 0)的平均執行價格_tradespending
->交易清單(一組已完成的買入/賣出或賣出/買入對),該列表是根據買入/賣出訂單編譯的
Observer顯然可以通過路徑訪問其他observersself._owner.stats
。
自訂訂單觀察器
標準BuySell
observer只關心已執行的操作。我們可以創建一個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(日曆)天
生成的圖表。
在新子圖表(紅色方塊)中可以看到,有幾個訂單已經過期,我們也可以理解,在“創建”和“執行”之間恰好有幾天。
注意
從分支中的development
提交1560fa8802 開始,如果在創建訂單時未設置價格,則收盤價將用作參考價格。
這對市場訂單沒有影響,但始終保持order.create.price
可用,並簡化了 buy
最後,該策略的代碼應用了新的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 的值將其存儲到檔中。最好的方法:
-
在策略的方法中
start
Open檔 -
在策略的方法中
next
寫下值
考慮到DrawDown
observer,可以這樣完成:
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 檔中。