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 when usebracket=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 when rawbracket=False

    The main bracket order has an expiry period after valid days (default is 10)

  • A position will be entered (or at least attempted to enter) when the weekday matches the enter value defined for each data feed which defaults to [1, 3, 4]

  • A open position will be exited

    • With a regular close after a holding period. The period is defined individually for each of the data feeds in a list with the parameter hold 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 the limit (take profit) side gets executed (the system will automatically cancel the other side)

  • Orders are kept in

    • dict which uses the data as the key
    • contains a list per data entry with the orders which are open for each data

    Once the orders have been issued, the management is done in notify_order

Additionally:

  • A TestSizer is used which will return different values for buy and sell orders as a test

    Because only long operations are initiated, only the buy sizing will be returned and the output log will contain no traces of the sell 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()

Comments

Fork me on GitHub