MultiTrades
One can now add a unique identifier to each trade, even if running on the same data.
Following a request at Tick Data and Resampling
release 1.1.12.88 of backtrader
support “MultiTrades”, ie: the ability to
assign a tradeid
to orders. This id is passed on to Trades
which makes
it possible to have different categories of trades and have them simultaneously
open.
The tradeid
can be specified when:
-
Calling Strategy.buy/sell/close with kwarg
tradeid
-
Calling Broker.buy/sell with kwarg
tradeid
-
Creating an Order instance with kwarg
tradeid
If not specified the default value is:
tradeid = 0
To test a small script has been implemented, visualizing the result with the
implementation of a custom MTradeObserver
which assigns different markers on
the plot according tradeid
(for the test values 0, 1 and 2 are used)
The script supports using the three ids (0, 1, 2) or simply use 0 (default)
An execution without enabling multiple ids:
$ ./multitrades.py --plot
With the resulting chart showing all Trades carry id 0
and therefore cannot
be diferentiated.
A second execution enables multitrades by cycling amongs 0, 1 and 2:
$ ./multitrades.py --plot --mtrade
And now 3 different markers alternate showing each Trade can be distinguished
using the tradeid
member.
Note
backtrader
tries to use models which mimic reality. Therefore “trades”
are not calculated by the Broker
instance which only takes care of
oders.
Trades are calculated by the Strategy.
And hence tradeid
(or something similar) may not be supported by a real
life broker in which case manually keeping track of the unique orde id
assigned by the broker would be needed.
Now, the code for the custom observer
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import math
import backtrader as bt
class MTradeObserver(bt.observer.Observer):
lines = ('Id_0', 'Id_1', 'Id_2')
plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)
plotlines = dict(
Id_0=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
Id_1=dict(marker='o', markersize=8.0, color='red', fillstyle='full'),
Id_2=dict(marker='s', markersize=8.0, color='blue', fillstyle='full')
)
def next(self):
for trade in self._owner._tradespending:
if trade.data is not self.data:
continue
if not trade.isclosed:
continue
self.lines[trade.tradeid][0] = trade.pnlcomm
The main script usage:
$ ./multitrades.py --help
usage: multitrades.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--mtrade] [--period PERIOD]
[--onlylong] [--cash CASH] [--comm COMM] [--mult MULT]
[--margin MARGIN] [--stake STAKE] [--plot]
[--numfigs NUMFIGS]
MultiTrades
optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format
--mtrade Activate MultiTrade Ids
--period PERIOD Period to apply to the Simple Moving Average
--onlylong, -ol Do only long operations
--cash CASH Starting Cash
--comm COMM Commission for operation
--mult MULT Multiplier for futures
--margin MARGIN Margin for each future
--stake STAKE Stake to apply in each operation
--plot, -p Plot the read data
--numfigs NUMFIGS, -n NUMFIGS
Plot using numfigs figures
The code for the script.
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import itertools
# The above could be sent to an independent module
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import mtradeobserver
class MultiTradeStrategy(bt.Strategy):
'''This strategy buys/sells upong the close price crossing
upwards/downwards a Simple Moving Average.
It can be a long-only strategy by setting the param "onlylong" to True
'''
params = dict(
period=15,
stake=1,
printout=False,
onlylong=False,
mtrade=False,
)
def log(self, txt, dt=None):
if self.p.printout:
dt = dt or self.data.datetime[0]
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# To control operation entries
self.order = None
# Create SMA on 2nd data
sma = btind.MovAv.SMA(self.data, period=self.p.period)
# Create a CrossOver Signal from close an moving average
self.signal = btind.CrossOver(self.data.close, sma)
# To alternate amongst different tradeids
if self.p.mtrade:
self.tradeid = itertools.cycle([0, 1, 2])
else:
self.tradeid = itertools.cycle([0])
def next(self):
if self.order:
return # if an order is active, no new orders are allowed
if self.signal > 0.0: # cross upwards
if self.position:
self.log('CLOSE SHORT , %.2f' % self.data.close[0])
self.close(tradeid=self.curtradeid)
self.log('BUY CREATE , %.2f' % self.data.close[0])
self.curtradeid = next(self.tradeid)
self.buy(size=self.p.stake, tradeid=self.curtradeid)
elif self.signal < 0.0:
if self.position:
self.log('CLOSE LONG , %.2f' % self.data.close[0])
self.close(tradeid=self.curtradeid)
if not self.p.onlylong:
self.log('SELL CREATE , %.2f' % self.data.close[0])
self.curtradeid = next(self.tradeid)
self.sell(size=self.p.stake, tradeid=self.curtradeid)
def notify_order(self, order):
if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
return # Await further notifications
if order.status == order.Completed:
if order.isbuy():
buytxt = 'BUY COMPLETE, %.2f' % order.executed.price
self.log(buytxt, order.executed.dt)
else:
selltxt = 'SELL COMPLETE, %.2f' % order.executed.price
self.log(selltxt, order.executed.dt)
elif order.status in [order.Expired, order.Canceled, order.Margin]:
self.log('%s ,' % order.Status[order.status])
pass # Simply log
# Allow new orders
self.order = None
def notify_trade(self, trade):
if trade.isclosed:
self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
elif trade.justopened:
self.log('TRADE OPENED, SIZE %2d' % trade.size)
def runstrategy():
args = parse_args()
# Create a cerebro
cerebro = bt.Cerebro()
# Get the dates from the args
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
# Create the 1st data
data = btfeeds.BacktraderCSVData(
dataname=args.data,
fromdate=fromdate,
todate=todate)
# Add the 1st data to cerebro
cerebro.adddata(data)
# Add the strategy
cerebro.addstrategy(MultiTradeStrategy,
period=args.period,
onlylong=args.onlylong,
stake=args.stake,
mtrade=args.mtrade)
# Add the commission - only stocks like a for each operation
cerebro.broker.setcash(args.cash)
# Add the commission - only stocks like a for each operation
cerebro.broker.setcommission(commission=args.comm,
mult=args.mult,
margin=args.margin)
# Add the MultiTradeObserver
cerebro.addobserver(mtradeobserver.MTradeObserver)
# And run it
cerebro.run()
# Plot if requested
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False)
def parse_args():
parser = argparse.ArgumentParser(description='MultiTrades')
parser.add_argument('--data', '-d',
default='../../datas/2006-day-001.txt',
help='data to add to the system')
parser.add_argument('--fromdate', '-f',
default='2006-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t',
default='2006-12-31',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--mtrade', action='store_true',
help='Activate MultiTrade Ids')
parser.add_argument('--period', default=15, type=int,
help='Period to apply to the Simple Moving Average')
parser.add_argument('--onlylong', '-ol', action='store_true',
help='Do only long operations')
parser.add_argument('--cash', default=100000, type=int,
help='Starting Cash')
parser.add_argument('--comm', default=2, type=float,
help='Commission for operation')
parser.add_argument('--mult', default=10, type=int,
help='Multiplier for futures')
parser.add_argument('--margin', default=2000.0, type=float,
help='Margin for each future')
parser.add_argument('--stake', default=1, type=int,
help='Stake to apply in each operation')
parser.add_argument('--plot', '-p', action='store_true',
help='Plot the read data')
parser.add_argument('--numfigs', '-n', default=1,
help='Plot using numfigs figures')
return parser.parse_args()
if __name__ == '__main__':
runstrategy()