Multi Example
A couple of topics in the Community seem to be oriented as to how to keep track of orders, especially when several data feeds are in play and also including when multiple orders are working together like in the case of bracket orders
The script below tries to serve as a sample by allowing the user to:
-
Use 3 data feeds
-
Use either
- A single buy order of type
Market
whenusebracket=False
or
- A bracket order set (
usebracket=True
In this case how the bracket set is created can be specified
-
Issuing 3 orders (1 parent + 2 children) when
rawbracket=True
-
Making a call to
buy_bracket
whenrawbracket=False
The main bracket order has an expiry period after
valid
days (default is10
) - A single buy order of type
-
A position will be entered (or at least attempted to enter) when the
weekday
matches theenter
value defined for each data feed which defaults to[1, 3, 4]
-
A open position will be exited
- With a regular
close
after aholding
period. The period is defined individually for each of the data feeds in a list with the parameterhold
which defaults to[7, 10, 15]
This will trigger the cancelation of the
stop
side of any bracket order if present (which will automatically cancel the other side)- Or if brackets are in use, when either the
stop
(limit losses) or thelimit
(take profit) side gets executed (the system will automatically cancel the other side)
- With a regular
-
Orders are kept in
-
dict
which uses thedata
as the key -
contains a
list
perdata
entry with the orders which are open for eachdata
Once the orders have been issued, the management is done in
notify_order
-
Additionally:
-
A
TestSizer
is used which will return different values forbuy
andsell
orders as a testBecause only long operations are initiated, only the
buy
sizing will be returned and the output log will contain no traces of thesell
sizing.
A profuse log of operations, positions and order management is created to aid in understanding what’s happening.
Let’s see a sample execution with the default values of usebracket=True
and
rawbracket=True
(to ease the plot, volume will be removed):
$ ./mult-values.py --plot volume=False
2001-01-02 d0 Position 0
2001-01-02 Data d0 OType buy Sizing to 1
2001-01-02 d0 Main 1 Stp 2 Lmt 3
2001-01-02 d1 Position 0
2001-01-02 d2 Position 0
2001-01-03 d0 Order 1 Status Accepted
2001-01-03 d0 Order 2 Status Accepted
2001-01-03 d0 Order 3 Status Accepted
2001-01-03 d0 Order 1 Status Completed
-- No longer alive main Ref
2001-01-03 d0 Position 1
2001-01-03 d1 Position 0
2001-01-03 d2 Position 0
2001-01-04 d0 Order 3 Status Completed
-- No longer alive limit Ref
2001-01-04 d0 Order 2 Status Canceled
-- No longer alive stop Ref
...
...
...
2006-12-27 d0 Order 2036 Status Accepted
2006-12-27 d0 Order 2037 Status Accepted
2006-12-27 d0 Order 2038 Status Accepted
2006-12-27 d0 Position 0
2006-12-27 d1 Position 0
2006-12-27 d2 Position 0
2006-12-28 d0 Position 0
2006-12-28 d1 Position 0
2006-12-28 d2 Position 0
2006-12-29 d0 Position 0
2006-12-29 d1 Position 0
2006-12-29 d2 Position 0
A 2nd execution sets rawbracket=False
$ ./mult-values.py --plot volume=False --strat rawbracket=False
Which outputs exactly the same result, this time having used buy_bracket
.
And finally disabling bracket usage:
./mult-values.py --strat usebracket=False --plot volume=False
Conclusion
This should serve as a good example of order management, with multiple data feeds and order sets.
Sample usage
$ ./mult-values.py --help
usage: mult-values.py [-h] [--data0 DATA0] [--data1 DATA1] [--data2 DATA2]
[--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]
Multiple Values and Brackets
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data0 to read in (default:
../../datas/nvda-1999-2014.txt)
--data1 DATA1 Data1 to read in (default:
../../datas/yhoo-1996-2014.txt)
--data2 DATA2 Data1 to read in (default:
../../datas/orcl-1995-2014.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2001-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2007-01-01)
--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
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class TestSizer(bt.Sizer):
params = dict(stake=1)
def _getsizing(self, comminfo, cash, data, isbuy):
dt, i = self.strategy.datetime.date(), data._id
s = self.p.stake * (1 + (not isbuy))
print('{} Data {} OType {} Sizing to {}'.format(
dt, data._name, ('buy' * isbuy) or 'sell', s))
return s
class St(bt.Strategy):
params = dict(
enter=[1, 3, 4], # data ids are 1 based
hold=[7, 10, 15], # data ids are 1 based
usebracket=True,
rawbracket=True,
pentry=0.015,
plimits=0.03,
valid=10,
)
def notify_order(self, order):
if order.status == order.Submitted:
return
dt, dn = self.datetime.date(), order.data._name
print('{} {} Order {} Status {}'.format(
dt, dn, order.ref, order.getstatusname())
)
whichord = ['main', 'stop', 'limit', 'close']
if not order.alive(): # not alive - nullify
dorders = self.o[order.data]
idx = dorders.index(order)
dorders[idx] = None
print('-- No longer alive {} Ref'.format(whichord[idx]))
if all(x is None for x in dorders):
dorders[:] = [] # empty list - New orders allowed
def __init__(self):
self.o = dict() # orders per data (main, stop, limit, manual-close)
self.holding = dict() # holding periods per data
def next(self):
for i, d in enumerate(self.datas):
dt, dn = self.datetime.date(), d._name
pos = self.getposition(d).size
print('{} {} Position {}'.format(dt, dn, pos))
if not pos and not self.o.get(d, None): # no market / no orders
if dt.weekday() == self.p.enter[i]:
if not self.p.usebracket:
self.o[d] = [self.buy(data=d)]
print('{} {} Buy {}'.format(dt, dn, self.o[d][0].ref))
else:
p = d.close[0] * (1.0 - self.p.pentry)
pstp = p * (1.0 - self.p.plimits)
plmt = p * (1.0 + self.p.plimits)
valid = datetime.timedelta(self.p.valid)
if self.p.rawbracket:
o1 = self.buy(data=d, exectype=bt.Order.Limit,
price=p, valid=valid, transmit=False)
o2 = self.sell(data=d, exectype=bt.Order.Stop,
price=pstp, size=o1.size,
transmit=False, parent=o1)
o3 = self.sell(data=d, exectype=bt.Order.Limit,
price=plmt, size=o1.size,
transmit=True, parent=o1)
self.o[d] = [o1, o2, o3]
else:
self.o[d] = self.buy_bracket(
data=d, price=p, stopprice=pstp,
limitprice=plmt, oargs=dict(valid=valid))
print('{} {} Main {} Stp {} Lmt {}'.format(
dt, dn, *(x.ref for x in self.o[d])))
self.holding[d] = 0
elif pos: # exiting can also happen after a number of days
self.holding[d] += 1
if self.holding[d] >= self.p.hold[i]:
o = self.close(data=d)
self.o[d].append(o) # manual order to list of orders
print('{} {} Manual Close {}'.format(dt, dn, o.ref))
if self.p.usebracket:
self.cancel(self.o[d][1]) # cancel stop side
print('{} {} Cancel {}'.format(dt, dn, self.o[d][1]))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0, name='d0')
data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **kwargs)
data1.plotinfo.plotmaster = data0
cerebro.adddata(data1, name='d1')
data2 = bt.feeds.YahooFinanceCSVData(dataname=args.data2, **kwargs)
data2.plotinfo.plotmaster = data0
cerebro.adddata(data2, name='d2')
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
cerebro.broker.setcommission(commission=0.001)
# Sizer
# cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
cerebro.addsizer(TestSizer, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Multiple Values and Brackets'
)
)
parser.add_argument('--data0', default='../../datas/nvda-1999-2014.txt',
required=False, help='Data0 to read in')
parser.add_argument('--data1', default='../../datas/yhoo-1996-2014.txt',
required=False, help='Data1 to read in')
parser.add_argument('--data2', default='../../datas/orcl-1995-2014.txt',
required=False, help='Data1 to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='2001-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='2007-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()