Evaluating external historical performance

With release 1.9.55.122 , backtrader can now be used to evaluate the performance of an external set of orders. This can be used for example:

To evaluate a set of orders/trades which for which judgmental trading (i.e.: human discretional decision) was used

To evaluate orders created in another platform and verify the analyzers of that platform

And obviously in the other direction to evaluate the things returned by backtrader against well-known results from other platforms

Usage pattern

... cerebro.adddata(mydata) ... cerebro.add_order_history(orders, notify=True or False) ... cerebro.run()

The obvious question here is how orders has to look like. Let’s quote the docs:

orders : is an iterable (ex: list, tuple, iterator, generator) in which each element will be also an iterable (with length) with the following sub-elements (2 formats are possible) [datetime, size, price] or [datetime, size, price, data] Note : it must be sorted (or produce sorted elements) by datetime ascending where: datetime is a python date/datetime instance or a string with format YYYY-MM-DD[THH:MM:SS[.us]] where the elements in brackets are optional size is an integer (positive to buy, negative to sell) price is a float/integer data if present can take any of the following values None - The 1 st data feed will be used as target integer - The data with that index (insertion order in Cerebro ) will be used string - a data with that name, assigned for example with cerebro.addata(data, name=value) , will be the target



In the case of notify :

notify (default: True) If True the 1st strategy inserted in the system will be notified of the artificial orders created following the information from each order in orders

Note Notice how the example above is adding a data feed. Yes this is needed.

A practical example of how orders could look like

ORDER_HISTORY = ( ('2005-02-01', 1, 2984.63), ('2005-03-04', -1, 3079.93), ... ('2006-12-18', 1, 4140.99), )

An iterable with 3 elements, which could have been perfectly loaded from a CSV file.

An example

The sample below does two things:

Execute a simple SMA Crossover strategy Add a history of orders which executes the same operations as the SMA CrossOver strategy In this 2nd case an empty strategy is added to receive order and trade notifications over notify_order and notify_trade

In both cases a set of analyzers ( TimeReturn in Months and Years and a TradeAnalyzer ) are loaded … and they should return the same values.

Run 1: SMA Crossover

$ ./order-history.py --plot --cerebro writer=True

Which produces a chart

And some textual output (capped for brevity):

Creating Signal Strategy 2005-02-01,1,2984.63 2005-03-04,-1,3079.93 ... 2006-12-01,-1,3993.03 profit 177.9000000000001 2006-12-18,1,4140.99 =============================================================================== Cerebro: ... - timereturn1: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Params: - timeframe: 8 - compression: None - _doprenext: True - data: None - firstopen: True - fund: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 0.03580099999999975 - 2006-12-31: 0.01649448108275653 ....................................................................... - tradeanalyzer: - Params: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - total: - total: 14 - open: 1 - closed: 13 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - streak: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - won: - current: 2 - longest: 2 ...

Run 2: Order history

$ ./order-history.py --plot --cerebro writer=True --order-history

Which produces a chart which seems to have no differences

And some textual output (capped again for brevity):

Creating Empty Strategy 2005-02-01,1,2984.63 2005-03-04,-1,3079.93 ... ....................................................................... - timereturn1: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Params: - timeframe: 8 - compression: None - _doprenext: True - data: None - firstopen: True - fund: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 0.03580099999999975 - 2006-12-31: 0.01649448108275653 ....................................................................... - tradeanalyzer: - Params: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - total: - total: 14 - open: 1 - closed: 13 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - streak: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - won: - current: 2 - longest: 2 ...

And the values as expected match those of the reference.

Conclusion

Measuring the performance of judgmental trading can be measured for example. This is sometimes used in combination with algotrading, where the algo generates signals, but the human has the final decision on whether the signal has to translate into an actual trade.

Sample Usage

$ ./order-history.py --help usage: order-history.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--order-history] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Order History Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: ../../datas/2005-2006-day-001.txt) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --order-history use order history (default: False) --cerebro kwargs kwargs in key=value format (default: ) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: )

Sample Code