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 24 25import argparse 26import datetime 27 28import backtrader as bt 29 30 31class TestSizer(bt.Sizer): 32 params = dict(stake=1) 33 34 def _getsizing(self, comminfo, cash, data, isbuy): 35 dt, i = self.strategy.datetime.date(), data._id 36 s = self.p.stake * (1 + (not isbuy)) 37 print('{} Data {} OType {} Sizing to {}'.format( 38 dt, data._name, ('buy' * isbuy) or 'sell', s)) 39 40 return s 41 42 43class St(bt.Strategy): 44 params = dict( 45 enter=[1, 3, 4], # data ids are 1 based 46 hold=[7, 10, 15], # data ids are 1 based 47 usebracket=True, 48 rawbracket=True, 49 pentry=0.015, 50 plimits=0.03, 51 valid=10, 52 ) 53 54 def notify_order(self, order): 55 if order.status == order.Submitted: 56 return 57 58 dt, dn = self.datetime.date(), order.data._name 59 print('{} {} Order {} Status {}'.format( 60 dt, dn, order.ref, order.getstatusname()) 61 ) 62 63 whichord = ['main', 'stop', 'limit', 'close'] 64 if not order.alive(): # not alive - nullify 65 dorders = self.o[order.data] 66 idx = dorders.index(order) 67 dorders[idx] = None 68 print('-- No longer alive {} Ref'.format(whichord[idx])) 69 70 if all(x is None for x in dorders): 71 dorders[:] = [] # empty list - New orders allowed 72 73 def __init__(self): 74 self.o = dict() # orders per data (main, stop, limit, manual-close) 75 self.holding = dict() # holding periods per data 76 77 def next(self): 78 for i, d in enumerate(self.datas): 79 dt, dn = self.datetime.date(), d._name 80 pos = self.getposition(d).size 81 print('{} {} Position {}'.format(dt, dn, pos)) 82 83 if not pos and not self.o.get(d, None): # no market / no orders 84 if dt.weekday() == self.p.enter[i]: 85 if not self.p.usebracket: 86 self.o[d] = [self.buy(data=d)] 87 print('{} {} Buy {}'.format(dt, dn, self.o[d][0].ref)) 88 89 else: 90 p = d.close[0] * (1.0 - self.p.pentry) 91 pstp = p * (1.0 - self.p.plimits) 92 plmt = p * (1.0 + self.p.plimits) 93 valid = datetime.timedelta(self.p.valid) 94 95 if self.p.rawbracket: 96 o1 = self.buy(data=d, exectype=bt.Order.Limit, 97 price=p, valid=valid, transmit=False) 98 99 o2 = self.sell(data=d, exectype=bt.Order.Stop, 100 price=pstp, size=o1.size, 101 transmit=False, parent=o1) 102 103 o3 = self.sell(data=d, exectype=bt.Order.Limit, 104 price=plmt, size=o1.size, 105 transmit=True, parent=o1) 106 107 self.o[d] = [o1, o2, o3] 108 109 else: 110 self.o[d] = self.buy_bracket( 111 data=d, price=p, stopprice=pstp, 112 limitprice=plmt, oargs=dict(valid=valid)) 113 114 print('{} {} Main {} Stp {} Lmt {}'.format( 115 dt, dn, *(x.ref for x in self.o[d]))) 116 117 self.holding[d] = 0 118 119 elif pos: # exiting can also happen after a number of days 120 self.holding[d] += 1 121 if self.holding[d] >= self.p.hold[i]: 122 o = self.close(data=d) 123 self.o[d].append(o) # manual order to list of orders 124 print('{} {} Manual Close {}'.format(dt, dn, o.ref)) 125 if self.p.usebracket: 126 self.cancel(self.o[d][1]) # cancel stop side 127 print('{} {} Cancel {}'.format(dt, dn, self.o[d][1])) 128 129 130def runstrat(args=None): 131 args = parse_args(args) 132 133 cerebro = bt.Cerebro() 134 135 # Data feed kwargs 136 kwargs = dict() 137 138 # Parse from/to-date 139 dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S' 140 for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']): 141 if a: 142 strpfmt = dtfmt + tmfmt * ('T' in a) 143 kwargs[d] = datetime.datetime.strptime(a, strpfmt) 144 145 # Data feed 146 data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **kwargs) 147 cerebro.adddata(data0, name='d0') 148 149 data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **kwargs) 150 data1.plotinfo.plotmaster = data0 151 cerebro.adddata(data1, name='d1') 152 153 data2 = bt.feeds.YahooFinanceCSVData(dataname=args.data2, **kwargs) 154 data2.plotinfo.plotmaster = data0 155 cerebro.adddata(data2, name='d2') 156 157 # Broker 158 cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')')) 159 cerebro.broker.setcommission(commission=0.001) 160 161 # Sizer 162 # cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')')) 163 cerebro.addsizer(TestSizer, **eval('dict(' + args.sizer + ')')) 164 165 # Strategy 166 cerebro.addstrategy(St, **eval('dict(' + args.strat + ')')) 167 168 # Execute 169 cerebro.run(**eval('dict(' + args.cerebro + ')')) 170 171 if args.plot: # Plot if requested to 172 cerebro.plot(**eval('dict(' + args.plot + ')')) 173 174 175def parse_args(pargs=None): 176 parser = argparse.ArgumentParser( 177 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 178 description=( 179 'Multiple Values and Brackets' 180 ) 181 ) 182 183 parser.add_argument('--data0', default='../../datas/nvda-1999-2014.txt', 184 required=False, help='Data0 to read in') 185 186 parser.add_argument('--data1', default='../../datas/yhoo-1996-2014.txt', 187 required=False, help='Data1 to read in') 188 189 parser.add_argument('--data2', default='../../datas/orcl-1995-2014.txt', 190 required=False, help='Data1 to read in') 191 192 # Defaults for dates 193 parser.add_argument('--fromdate', required=False, default='2001-01-01', 194 help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') 195 196 parser.add_argument('--todate', required=False, default='2007-01-01', 197 help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') 198 199 parser.add_argument('--cerebro', required=False, default='', 200 metavar='kwargs', help='kwargs in key=value format') 201 202 parser.add_argument('--broker', required=False, default='', 203 metavar='kwargs', help='kwargs in key=value format') 204 205 parser.add_argument('--sizer', required=False, default='', 206 metavar='kwargs', help='kwargs in key=value format') 207 208 parser.add_argument('--strat', required=False, default='', 209 metavar='kwargs', help='kwargs in key=value format') 210 211 parser.add_argument('--plot', required=False, default='', 212 nargs='?', const='{}', 213 metavar='kwargs', help='kwargs in key=value format') 214 215 return parser.parse_args(pargs) 216 217 218if __name__ == '__main__': 219 runstrat() 220