Files
2020-03-02 14:07:01 +01:00

493 lines
18 KiB
Python

from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import sys
# The above could be sent to an independent module
import backtrader as bt
from backtrader.utils import flushfile # win32 quick stdout flushing
from mql5_zmq_backtrader.mt5data import MTraderData
from mql5_zmq_backtrader.mt5store import MTraderStore
StoreCls = MTraderStore
DataCls = MTraderData
class TestStrategy(bt.Strategy):
params = dict(
smaperiod=5,
trade=False,
stake=0.1,
exectype=bt.Order.Market,
stopafter=0,
valid=None,
cancel=0,
donotcounter=False,
sell=False,
usebracket=False,
)
def __init__(self):
# To control operation entries
self.orderid = list()
self.order = None
self.counttostop = 0
self.datastatus = 0
self.last_pos = None
self.last_value = 0
# Create SMA on 2nd data
self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)
print('--------------------------------------------------')
print('Strategy Created')
print('--------------------------------------------------')
def notify_fund(self, cash, value, fundvalue, shares):
if value != self.last_value:
print(cash, value, fundvalue, shares)
self.last_value = value
def notify_data(self, data, status, *args, **kwargs):
print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
if status == data.LIVE:
self.counttostop = self.p.stopafter
self.datastatus = 1
def notify_store(self, msg, *args, **kwargs):
print('*' * 5, 'STORE NOTIF:', msg)
def notify_order(self, order):
if order.status in [order.Completed, order.Cancelled, order.Rejected]:
self.order = None
print('{}: Order ref: {} / Type {} / Status {}'.format(
self.data.datetime.date(0),
order.ref, 'Buy' * order.isbuy() or 'Sell',
order.getstatusname()))
def notify_trade(self, trade):
print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
print(trade)
print('-' * 50, 'TRADE END')
def prenext(self):
self.next(frompre=True)
def next(self, frompre=False):
# Visualize data0
txt = list()
txt.append(str(self.getdatanames()[0]))
txt.append('%04d' % len(self.data0))
dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
txt.append('{:f}'.format(self.data.datetime[0]))
txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
txt.append('{:f}'.format(self.data.open[0]))
txt.append('{:f}'.format(self.data.high[0]))
txt.append('{:f}'.format(self.data.low[0]))
txt.append('{:f}'.format(self.data.close[0]))
txt.append('{:6d}'.format(int(self.data.volume[0])))
txt.append('{:d}'.format(int(self.data.openinterest[0])))
txt.append('{:f}'.format(self.sma[0]))
print(', '.join(txt))
# Visualize data1
if len(self.datas) > 1 and len(self.data1):
txt = list()
txt.append(str(self.getdatanames()[1]))
txt.append('%04d' % len(self.data1))
dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
txt.append('{}'.format(self.data1.datetime[0]))
txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
txt.append('{}'.format(self.data1.open[0]))
txt.append('{}'.format(self.data1.high[0]))
txt.append('{}'.format(self.data1.low[0]))
txt.append('{}'.format(self.data1.close[0]))
txt.append('{}'.format(self.data1.volume[0]))
txt.append('{}'.format(self.data1.openinterest[0]))
txt.append('{}'.format(float('NaN')))
print(', '.join(txt))
if self.counttostop: # stop after x live lines
self.counttostop -= 1
if not self.counttostop:
self.env.runstop()
return
if not self.p.trade:
print('No trading. Parameter trade:', self.p.trade)
return
if self.datastatus and not self.position and len(self.orderid) < 1:
if not self.p.usebracket:
if not self.p.sell:
# price = round(self.data0.close[0] * 0.90, 2)
price = self.data0.close[0] - 5
self.order = self.buy(size=self.p.stake,
exectype=self.p.exectype,
price=price,
valid=self.p.valid,
magic=31416)
else:
# price = round(self.data0.close[0] * 1.10, 4)
price = self.data0.close[0] - 0.05
self.order = self.sell(size=self.p.stake,
exectype=self.p.exectype,
price=price,
valid=self.p.valid)
else:
print('USING BRACKET')
price = self.data0.close[0] - 0.05
self.order, _, _ = self.buy_bracket(size=self.p.stake,
exectype=bt.Order.Market,
price=price,
stopprice=price - 0.10,
limitprice=price + 0.10,
valid=self.p.valid)
self.orderid.append(self.order)
elif self.position and not self.p.donotcounter:
if self.order is None:
if not self.p.sell:
self.order = self.sell(size=self.p.stake // 2,
exectype=bt.Order.Market,
price=self.data0.close[0])
else:
self.order = self.buy(size=self.p.stake // 2,
exectype=bt.Order.Market,
price=self.data0.close[0])
self.orderid.append(self.order)
elif self.order is not None and self.p.cancel:
if self.datastatus > self.p.cancel:
self.cancel(self.order)
if self.datastatus:
self.datastatus += 1
def start(self):
header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
'OpenInterest', 'SMA']
print(', '.join(header))
self.done = False
def runstrategy():
args = parse_args()
# Create a cerebro
cerebro = bt.Cerebro()
storekwargs = dict(
# ram key_id=args.keyid,
# ram secret_key=args.secretkey,
# ram paper=not args.live,
host="192.168.0.71",
)
store = StoreCls(**storekwargs)
broker = store.getbroker() # MTraderBroker
cerebro.setbroker(broker)
timeframe = bt.TimeFrame.TFrame(args.timeframe)
# Manage data1 parameters
tf1 = args.timeframe1
tf1 = bt.TimeFrame.TFrame(tf1) if tf1 is not None else timeframe
cp1 = args.compression1
cp1 = cp1 if cp1 is not None else args.compression
if args.resample or args.replay:
datatf = datatf1 = bt.TimeFrame.Ticks
datacomp = datacomp1 = 1
else:
datatf = timeframe
datacomp = args.compression
datatf1 = tf1
datacomp1 = cp1
fromdate = None
if args.fromdate:
dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
fromdate = datetime.datetime.strptime(args.fromdate, dtformat)
DataFactory = store.getdata # MTraderData
datakwargs = dict(
timeframe=datatf, compression=datacomp,
qcheck=args.qcheck,
historical=args.historical,
fromdate=fromdate,
bidask=args.bidask,
useask=args.useask,
backfill_start=not args.no_backfill_start,
backfill=not args.no_backfill,
tz=args.timezone
)
# if args.no_store and not args.broker: # neither store nor broker
# datakwargs.update(storekwargs) # pass the store args over the data
data0 = DataFactory(dataname=args.data0, **datakwargs)
data1 = None
if args.data1 is not None:
if args.data1 != args.data0:
datakwargs['timeframe'] = datatf1
datakwargs['compression'] = datacomp1
data1 = DataFactory(dataname=args.data1, **datakwargs)
#ram
print(datakwargs)
else:
data1 = data0
rekwargs = dict(
timeframe=timeframe,
compression=args.compression,
bar2edge=not args.no_bar2edge,
adjbartime=not args.no_adjbartime,
rightedge=not args.no_rightedge,
takelate=not args.no_takelate,
)
if args.replay:
cerebro.replaydata(data0, **rekwargs)
if data1 is not None:
rekwargs['timeframe'] = tf1
rekwargs['compression'] = cp1
cerebro.replaydata(data1, **rekwargs)
elif args.resample:
cerebro.resampledata(data0, **rekwargs)
if data1 is not None:
rekwargs['timeframe'] = tf1
rekwargs['compression'] = cp1
cerebro.resampledata(data1, **rekwargs)
else:
cerebro.adddata(data0)
if data1 is not None:
cerebro.adddata(data1)
if args.valid is None:
valid = None
else:
valid = datetime.timedelta(seconds=args.valid)
# Add the strategy
cerebro.addstrategy(TestStrategy,
smaperiod=args.smaperiod,
trade=args.trade,
exectype=bt.Order.ExecType(args.exectype),
stake=args.stake,
stopafter=args.stopafter,
valid=valid,
cancel=args.cancel,
donotcounter=args.donotcounter,
sell=args.sell,
usebracket=args.usebracket)
# Live data ... avoid long data accumulation by switching to "exactbars"
cerebro.run(exactbars=args.exactbars)
if args.exactbars < 1: # plotting is possible
if args.plot:
pkwargs = dict(style='line')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Test Metatrader integration')
parser.add_argument('--exactbars', default=1, type=int,
required=False, action='store',
help='exactbars level, use 0/-1/-2 to enable plotting')
parser.add_argument('--stopafter', default=0, type=int,
required=False, action='store',
help='Stop after x lines of LIVE data')
parser.add_argument('--debug',
required=False, action='store_true',
help='Display all info received from source')
parser.add_argument('--keyid', default=None,
required=False, action='store',
help='MT5 API key id')
parser.add_argument('--secretkey', default=None,
required=False, action='store',
help='MT5 API secret key')
parser.add_argument('--live', default=None,
required=False, action='store',
help='Go to live server rather than paper')
parser.add_argument('--qcheck', default=0.5, type=float,
required=False, action='store',
help=('Timeout for periodic '
'notification/resampling/replaying check'))
parser.add_argument('--data0', default=None,
required=True, action='store',
help='data 0 into the system')
parser.add_argument('--data1', default=None,
required=False, action='store',
help='data 1 into the system')
parser.add_argument('--timezone', default=None,
required=False, action='store',
help='timezone to get time output into (pytz names)')
parser.add_argument('--bidask', default=None,
required=False, action='store_true',
help='Use bidask ... if False use midpoint')
parser.add_argument('--useask', default=None,
required=False, action='store_true',
help='Use the "ask" of bidask prices/streaming')
parser.add_argument('--no-backfill_start',
required=False, action='store_true',
help='Disable backfilling at the start')
parser.add_argument('--no-backfill',
required=False, action='store_true',
help='Disable backfilling after a disconnection')
parser.add_argument('--historical',
required=False, action='store_true',
help='do only historical download')
parser.add_argument('--fromdate',
required=True, action='store',
help=('Starting date for historical download '
'with format: YYYY-MM-DD[THH:MM:SS]'))
parser.add_argument('--smaperiod', default=5, type=int,
required=False, action='store',
help='Period to apply to the Simple Moving Average')
pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--replay',
required=False, action='store_true',
help='replay to chosen timeframe')
pgroup.add_argument('--resample',
required=False, action='store_true',
help='resample to chosen timeframe')
parser.add_argument('--timeframe', default='Minutes',
choices=bt.TimeFrame.Names,
required=False, action='store',
help='TimeFrame for Resample/Replay')
parser.add_argument('--compression', default=1, type=int,
required=False, action='store',
help='Compression for Resample/Replay')
parser.add_argument('--timeframe1', default=None,
choices=bt.TimeFrame.Names[4],
required=False, action='store',
help='TimeFrame for Resample/Replay - Data1')
parser.add_argument('--compression1', default=None, type=int,
required=False, action='store',
help='Compression for Resample/Replay - Data1')
parser.add_argument('--no-takelate',
required=False, action='store_true',
help=('resample/replay, do not accept late samples'))
parser.add_argument('--no-bar2edge',
required=False, action='store_true',
help='no bar2edge for resample/replay')
parser.add_argument('--no-adjbartime',
required=False, action='store_true',
help='no adjbartime for resample/replay')
parser.add_argument('--no-rightedge',
required=False, action='store_true',
help='no rightedge for resample/replay')
parser.add_argument('--trade',
required=False, action='store_true',
help='Do Sample Buy/Sell operations')
parser.add_argument('--sell',
required=False, action='store_true',
help='Start by selling')
parser.add_argument('--usebracket',
required=False, action='store_true',
help='Test buy_bracket')
parser.add_argument('--donotcounter',
required=False, action='store_true',
help='Do not counter the 1st operation')
parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
choices=bt.Order.ExecTypes,
required=False, action='store',
help='Execution to Use when opening position')
parser.add_argument('--stake', default=0.1, type=float,
required=False, action='store',
help='Stake to use in buy operations')
parser.add_argument('--valid', default=None, type=float,
required=False, action='store',
help='Seconds to keep the order alive (0 means DAY)')
parser.add_argument('--cancel', default=0, type=int,
required=False, action='store',
help=('Cancel a buy order after n bars in operation,'
' to be combined with orders like Limit'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example (escape the quotes if needed):\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
start_date = datetime.datetime.now() - datetime.timedelta(minutes=500)
sys.argv = [
'MTtest2.py',
'--data0', 'BTCEUR',
'--timeframe', 'Minutes',
'--compression', '1',
'--data1', 'BTCUSD',
'--timeframe1', 'Minutes',
'--compression1', '1',
'--fromdate', start_date.strftime("%Y-%m-%dT%H:%M:%S"),
'--trade']
runstrategy()