1#!/usr/bin/env python
2# -*- coding: utf-8; py-indent-offset:4 -*-
3###############################################################################
4#
5# Copyright (C) 2017 Daniel Rodriguez
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20###############################################################################
21from __future__ import (absolute_import, division, print_function,
22                        unicode_literals)
23
24import argparse
25import datetime
26
27import backtrader as bt
28
29
30class St(bt.Strategy):
31    params = dict(
32        ma=bt.ind.SMA,
33        p1=10,
34        p2=30,
35        stoptype=bt.Order.StopTrail,
36        trailamount=0.0,
37        trailpercent=0.0,
38        limitoffset=0.0,
39    )
40
41    def __init__(self):
42        ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
43        self.crup = bt.ind.CrossUp(ma1, ma2)
44        self.order = None
45
46    def next(self):
47        if not self.position:
48            if self.crup:
49                o = self.buy()
50                self.order = None
51                print('*' * 50)
52
53        elif self.order is None:
54            if self.p.stoptype == bt.Order.StopTrailLimit:
55                price = self.data.close[0]
56                plimit = self.data.close[0] + self.p.limitoffset
57            else:
58                price = None
59                plimit = None
60
61            self.order = self.sell(exectype=self.p.stoptype,
62                                   price=price,
63                                   plimit=plimit,
64                                   trailamount=self.p.trailamount,
65                                   trailpercent=self.p.trailpercent)
66
67            if self.p.trailamount:
68                tcheck = self.data.close - self.p.trailamount
69            else:
70                tcheck = self.data.close * (1.0 - self.p.trailpercent)
71            print(','.join(
72                map(str, [self.datetime.date(), self.data.close[0],
73                          self.order.created.price, tcheck])
74                )
75            )
76            print('-' * 10)
77        else:
78            if self.p.trailamount:
79                tcheck = self.data.close - self.p.trailamount
80            else:
81                tcheck = self.data.close * (1.0 - self.p.trailpercent)
82            print(','.join(
83                map(str, [self.datetime.date(), self.data.close[0],
84                          self.order.created.price, tcheck])
85                )
86            )
87
88
89def runstrat(args=None):
90    args = parse_args(args)
91
92    cerebro = bt.Cerebro()
93
94    # Data feed kwargs
95    kwargs = dict()
96
97    # Parse from/to-date
98    dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
99    for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
100        if a:
101            strpfmt = dtfmt + tmfmt * ('T' in a)
102            kwargs[d] = datetime.datetime.strptime(a, strpfmt)
103
104    # Data feed
105    data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
106    cerebro.adddata(data0)
107
108    # Broker
109    cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
110
111    # Sizer
112    cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
113
114    # Strategy
115    cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
116
117    # Execute
118    cerebro.run(**eval('dict(' + args.cerebro + ')'))
119
120    if args.plot:  # Plot if requested to
121        cerebro.plot(**eval('dict(' + args.plot + ')'))
122
123
124def parse_args(pargs=None):
125    parser = argparse.ArgumentParser(
126        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
127        description=(
128            'StopTrail Sample'
129        )
130    )
131
132    parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
133                        required=False, help='Data to read in')
134
135    # Defaults for dates
136    parser.add_argument('--fromdate', required=False, default='',
137                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
138
139    parser.add_argument('--todate', required=False, default='',
140                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
141
142    parser.add_argument('--cerebro', required=False, default='',
143                        metavar='kwargs', help='kwargs in key=value format')
144
145    parser.add_argument('--broker', required=False, default='',
146                        metavar='kwargs', help='kwargs in key=value format')
147
148    parser.add_argument('--sizer', required=False, default='',
149                        metavar='kwargs', help='kwargs in key=value format')
150
151    parser.add_argument('--strat', required=False, default='',
152                        metavar='kwargs', help='kwargs in key=value format')
153
154    parser.add_argument('--plot', required=False, default='',
155                        nargs='?', const='{}',
156                        metavar='kwargs', help='kwargs in key=value format')
157
158    return parser.parse_args(pargs)
159
160
161if __name__ == '__main__':
162    runstrat()
163