Skip to content

Instantly share code, notes, and snippets.

@mementum
Created August 29, 2019 15:00
Show Gist options
  • Save mementum/2076adfaf4afc8a51400d59815f3e5d5 to your computer and use it in GitHub Desktop.
Save mementum/2076adfaf4afc8a51400d59815f3e5d5 to your computer and use it in GitHub Desktop.
Fractional Sizes in backtrader
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
# Copyright (C) 2019 Daniel Rodriguez - MIT License
# - https://opensource.org/licenses/MIT
# - https://en.wikipedia.org/wiki/MIT_License
###############################################################################
import argparse
import logging
import sys
import backtrader as bt
# This defines not only the commission info, but some other aspects
# of a given data asset like the "getsize" information from below
# params = dict(stocklike=True) # No margin, no multiplier
class CommInfoFractional(bt.CommissionInfo):
def getsize(self, price, cash):
'''Returns fractional size for cash operation @price'''
return self.p.leverage * (cash / price)
class St(bt.Strategy):
params = dict(
p1=10, p2=30, # periods for crossover
ma=bt.ind.SMA, # moving average to use
target=0.5, # percentage of value to use
)
def __init__(self):
ma1, ma2 = [self.p.ma(period=p) for p in (self.p.p1, self.p.p2)]
self.cross = bt.ind.CrossOver(ma1, ma2)
def next(self):
self.logdata()
if self.cross > 0:
self.loginfo('Enter Long')
self.order_target_percent(target=self.p.target)
elif self.cross < 0:
self.loginfo('Enter Short')
self.order_target_percent(target=-self.p.target)
def notify_trade(self, trade):
if trade.justopened:
self.loginfo('Trade Opened - Size {} @Price {}',
trade.size, trade.price)
elif trade.isclosed:
self.loginfo('Trade Closed - Profit {}', trade.pnlcomm)
else: # trade updated
self.loginfo('Trade Updated - Size {} @Price {}',
trade.size, trade.price)
def notify_order(self, order):
if order.alive():
return
otypetxt = 'Buy ' if order.isbuy() else 'Sell'
if order.status == order.Completed:
self.loginfo(
('{} Order Completed - '
'Size: {} @Price: {} '
'Value: {:.2f} Comm: {:.2f}'),
otypetxt, order.executed.size, order.executed.price,
order.executed.value, order.executed.comm
)
else:
self.loginfo('{} Order rejected', otypetxt)
def loginfo(self, txt, *args):
out = [self.datetime.date().isoformat(), txt.format(*args)]
logging.info(','.join(out))
def logerror(self, txt, *args):
out = [self.datetime.date().isoformat(), txt.format(*args)]
logging.error(','.join(out))
def logdebug(self, txt, *args):
out = [self.datetime.date().isoformat(), txt.format(*args)]
logging.debug(','.join(out))
def logdata(self):
txt = []
txt += ['{:.2f}'.format(self.data.open[0])]
txt += ['{:.2f}'.format(self.data.high[0])]
txt += ['{:.2f}'.format(self.data.low[0])]
txt += ['{:.2f}'.format(self.data.close[0])]
txt += ['{:.2f}'.format(self.data.volume[0])]
self.loginfo(','.join(txt))
def run(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
data = bt.feeds.BacktraderCSVData(dataname=args.data)
cerebro.adddata(data) # create and add data feed
cerebro.addstrategy(St) # add the strategy
cerebro.broker.set_cash(args.cash) # set broker cash
if args.fractional: # use the fractional scheme if requested
cerebro.broker.addcommissioninfo(CommInfoFractional())
cerebro.run() # execute
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def logconfig(pargs):
if pargs.quiet:
verbose_level = logging.ERROR
else:
verbose_level = logging.INFO - pargs.verbose * 10 # -> DEBUG
logger = logging.getLogger()
for h in logger.handlers: # Remove all loggers from root
logger.removeHandler(h)
stream = sys.stdout if not pargs.stderr else sys.stderr # choose stream
logging.basicConfig(
stream=stream,
format="%(message)s", # format="%(levelname)s: %(message)s",
level=verbose_level,
)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Fractional Sizes with CommInfo',
)
pgroup = parser.add_argument_group('Data Options')
parser.add_argument('--data', default='../../datas/2005-2006-day-001.txt',
help='Data to read in')
pgroup = parser.add_argument_group(title='Broker Arguments')
pgroup.add_argument('--cash', default=100000.0, type=float,
help='Starting cash to use')
pgroup.add_argument('--fractional', action='store_true',
help='Use fractional commission info')
pgroup = parser.add_argument_group(title='Plotting Arguments')
pgroup.add_argument('--plot', default='', nargs='?', const='{}',
metavar='kwargs', help='kwargs: "k1=v1,k2=v2,..."')
pgroup = parser.add_argument_group('Verbosity Options')
pgroup.add_argument('--stderr', action='store_true',
help='Log to stderr, else to stdout')
pgroup = pgroup.add_mutually_exclusive_group()
pgroup.add_argument('--quiet', '-q', action='store_true',
help='Silent (errors will be reported)')
pgroup.add_argument('--verbose', '-v', action='store_true',
help='Increase verbosity level')
# Parse and process some args
pargs = parser.parse_args(pargs)
logconfig(pargs) # config logging
return pargs
if __name__ == '__main__':
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment