1#!/usr/bin/env python 2# -*- coding: utf-8; py-indent-offset:4 -*- 3############################################################################### 4# 5# Copyright (C) 2015, 2016 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 27# The above could be sent to an independent module 28import backtrader as bt 29import backtrader.feeds as btfeeds 30import backtrader.indicators as btind 31 32 33class MultiDataStrategy(bt.Strategy): 34 ''' 35 This strategy operates on 2 datas. The expectation is that the 2 datas are 36 correlated and the 2nd data is used to generate signals on the 1st 37 38 - Buy/Sell Operationss will be executed on the 1st data 39 - The signals are generated using a Simple Moving Average on the 2nd data 40 when the close price crosses upwwards/downwards 41 42 The strategy is a long-only strategy 43 ''' 44 params = dict( 45 period=15, 46 stake=10, 47 printout=True, 48 ) 49 50 def log(self, txt, dt=None): 51 if self.p.printout: 52 dt = dt or self.data.datetime[0] 53 dt = bt.num2date(dt) 54 print('%s, %s' % (dt.isoformat(), txt)) 55 56 def notify_order(self, order): 57 if order.status in [bt.Order.Submitted, bt.Order.Accepted]: 58 return # Await further notifications 59 60 if order.status == order.Completed: 61 if order.isbuy(): 62 buytxt = 'BUY COMPLETE, %.2f' % order.executed.price 63 self.log(buytxt, order.executed.dt) 64 else: 65 selltxt = 'SELL COMPLETE, %.2f' % order.executed.price 66 self.log(selltxt, order.executed.dt) 67 68 elif order.status in [order.Expired, order.Canceled, order.Margin]: 69 self.log('%s ,' % order.Status[order.status]) 70 pass # Simply log 71 72 # Allow new orders 73 self.orderid = None 74 75 def __init__(self): 76 # To control operation entries 77 self.orderid = None 78 79 # Create SMA on 2nd data 80 sma = btind.MovAv.SMA(self.data1, period=self.p.period) 81 # Create a CrossOver Signal from close an moving average 82 self.signal = btind.CrossOver(self.data1.close, sma) 83 84 def next(self): 85 if self.orderid: 86 return # if an order is active, no new orders are allowed 87 88 if self.p.printout: 89 print('Self len:', len(self)) 90 print('Data0 len:', len(self.data0)) 91 print('Data1 len:', len(self.data1)) 92 print('Data0 len == Data1 len:', 93 len(self.data0) == len(self.data1)) 94 95 print('Data0 dt:', self.data0.datetime.datetime()) 96 print('Data1 dt:', self.data1.datetime.datetime()) 97 98 if not self.position: # not yet in market 99 if self.signal > 0.0: # cross upwards 100 self.log('BUY CREATE , %.2f' % self.data1.close[0]) 101 self.buy(size=self.p.stake) 102 103 else: # in the market 104 if self.signal < 0.0: # crosss downwards 105 self.log('SELL CREATE , %.2f' % self.data1.close[0]) 106 self.sell(size=self.p.stake) 107 108 def stop(self): 109 print('==================================================') 110 print('Starting Value - %.2f' % self.broker.startingcash) 111 print('Ending Value - %.2f' % self.broker.getvalue()) 112 print('==================================================') 113 114 115def runstrategy(): 116 args = parse_args() 117 118 # Create a cerebro 119 cerebro = bt.Cerebro() 120 121 # Get the dates from the args 122 fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') 123 todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') 124 125 # Create the 1st data 126 data0 = btfeeds.YahooFinanceCSVData( 127 dataname=args.data0, 128 fromdate=fromdate, 129 todate=todate) 130 131 # Add the 1st data to cerebro 132 cerebro.adddata(data0) 133 134 # Create the 2nd data 135 data1 = btfeeds.YahooFinanceCSVData( 136 dataname=args.data1, 137 fromdate=fromdate, 138 todate=todate) 139 140 # Add the 2nd data to cerebro 141 cerebro.adddata(data1) 142 143 # Add the strategy 144 cerebro.addstrategy(MultiDataStrategy, 145 period=args.period, 146 stake=args.stake) 147 148 # Add the commission - only stocks like a for each operation 149 cerebro.broker.setcash(args.cash) 150 151 # Add the commission - only stocks like a for each operation 152 cerebro.broker.setcommission(commission=args.commperc) 153 154 # And run it 155 cerebro.run(runonce=not args.runnext, 156 preload=not args.nopreload, 157 oldsync=args.oldsync) 158 159 # Plot if requested 160 if args.plot: 161 cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False) 162 163 164def parse_args(): 165 parser = argparse.ArgumentParser(description='MultiData Strategy') 166 167 parser.add_argument('--data0', '-d0', 168 default='../../datas/orcl-2003-2005.txt', 169 help='1st data into the system') 170 171 parser.add_argument('--data1', '-d1', 172 default='../../datas/yhoo-2003-2005.txt', 173 help='2nd data into the system') 174 175 parser.add_argument('--fromdate', '-f', 176 default='2003-01-01', 177 help='Starting date in YYYY-MM-DD format') 178 179 parser.add_argument('--todate', '-t', 180 default='2005-12-31', 181 help='Starting date in YYYY-MM-DD format') 182 183 parser.add_argument('--period', default=15, type=int, 184 help='Period to apply to the Simple Moving Average') 185 186 parser.add_argument('--cash', default=100000, type=int, 187 help='Starting Cash') 188 189 parser.add_argument('--runnext', action='store_true', 190 help='Use next by next instead of runonce') 191 192 parser.add_argument('--nopreload', action='store_true', 193 help='Do not preload the data') 194 195 parser.add_argument('--oldsync', action='store_true', 196 help='Use old data synchronization method') 197 198 parser.add_argument('--commperc', default=0.005, type=float, 199 help='Percentage commission (0.005 is 0.5%%') 200 201 parser.add_argument('--stake', default=10, type=int, 202 help='Stake to apply in each operation') 203 204 parser.add_argument('--plot', '-p', action='store_true', 205 help='Plot the read data') 206 207 parser.add_argument('--numfigs', '-n', default=1, 208 help='Plot using numfigs figures') 209 210 return parser.parse_args() 211 212 213if __name__ == '__main__': 214 runstrategy() 215