10.1 Observers and Statistics
Observers 是backtrader中通过手机相关信息用于统计目的观察变量,可以在plot中展示,常见的plot中通常包含3类Observers :
-
Cash and Value (what’s happening with the money in the broker)
-
Trades (aka Operations)
-
Buy/Sell Orders
在Cerebro中将stdstats 设置为True时,plot会自动添加这三类Observers
Example:
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)
Accessing the Observers
可以通过self.stats.observers的方式访问
Observer Implementation
The implementation is very similar to that of an Indicator:
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()
Steps:
-
Derive from Observer (and not from Indicator)
-
Declare lines and params as needed (Broker has 2 lines but no params)
-
There will be an automatic attribute _owner which is the strategy holding the observer
Observers come in action:
-
After all Indicators have been calculated
-
After the Strategy next method has been executed
-
That means: at the end of the cycle … they observe what has happened 在其他操作执行结束之后, Observers 观察记录发生了什么
In the Broker case it’s simply blindly recording the broker cash and portfolio values at each point in time.
Developing Observers
To produce a meaningful observer, the implementation can use the following information:
- self._owner is the currently strategy being executed
As such anything within the strategy is available to the observer
- Default internal things available in the strategy which may be useful:
- broker -> attribute giving access to the broker instance the strategy creates order against
As seen in Broker, cash and portfolio values are collected by invoking the methods getcash and getvalue
-
_orderspending -> list orders created by the strategy and for which the broker has notified an event to the strategy.
The BuySell observer traverses the list looking for orders which have executed (totally or partially) to create an average execution price for the given point in time (index 0) -
_tradespending -> list of trades (a set of completed buy/sell or sell/buy pairs) which is compiled from the buy/sell orders
An Observer can obviously access other observers over the self._owner.stats path.
Example: Custom OrderObserver
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
Saving/Keeping the statistics
As of now backtrader has not implemented any mechanism to track the values of observers storing them into files. The best way to do it:
-
Open a file in the start method of the strategy
-
Write the values down in the next method of the strategy
Considering the DrawDown observer, it could be done like this
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')