For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See:

Close position and selling short on the same bar not working correctly

  • I'm testing a strategy that buys a correct number of shares, and closes the position fine with the same number of shares but when it sells short it does it with very few shares as if there was not enough money left. I understand it's because as the two operations are done at the same point the broker hasn't yet updated the amount of cash from closing the previous operation. How can I fix this?

    2020-10-14T23:59:59.999989, Initial portfolio value of 1700.00
    1991-04-12T23:59:59.999989, BUY 56 shares at 28.87
    1991-04-12T23:59:59.999989, BUY COMPLETED. Size: 56, Price: 28.87, Commission: 0.72
    1991-06-19T23:59:59.999989, CLOSE Long Position of 56 shares at 25.54
    1991-06-19T23:59:59.999989, SELL 3 shares at 25.54
    1991-06-19T23:59:59.999989, SELL COMPLETED. Size: 56, Price: 25.54, Commission: 0.28
    1991-06-19T23:59:59.999989, SELL COMPLETED. Size: 3, Price: 25.54, Commission: 0.49
    1991-06-20T23:59:59.999989, TRADE COMPLETED, Portfolio: 1512.03, Gross: -186.48, Net: -187.48

    from __future__ import (absolute_import, division, print_function,
    import sys
    import os.path
    import datetime
    import argparse
    import backtrader as bt
    from strategies.strategies import *
    from sizers import MaxRiskSizer
    from commissions import DegiroCommission
    def runstrategy():
        cerebro = bt.Cerebro()
        comminfo = DegiroCommission()
        cerebro.addstrategy(EmaCrossLongShort, fast=13, slow=49)
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, 'data/CPE.csv')
        data = bt.feeds.YahooFinanceCSVData(
            # Do not pass values before this date
            #fromdate=datetime.datetime(1990, 10, 1),
            # Do not pass values after this date
            #todate=datetime.datetime(2020, 10, 15),
        start_portfolio_value =
        end_portfolio_value =
        pnl = end_portfolio_value - start_portfolio_value
        print(f'Starting Portfolio Value: {start_portfolio_value:.2f}')
        print(f'Final Portfolio Value: {end_portfolio_value:.2f}')
        print(f'PnL: {pnl:.2f}')
    if __name__ == '__main__':

    import backtrader as bt
    import backtrader.indicators as btind
    class EmaCrossLongShort(bt.Strategy):
        '''This strategy buys/sells upong the close price crossing
        upwards/downwards an Exponential Moving Average.
        It can be a long-only strategy by setting the param "longonly" to True
        params = dict(
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or[0]
                dt = bt.num2date(dt)
                print(f'{dt.isoformat()}, {txt}')
        def __init__(self):
            self.orderid = None  # to control operation entries
            fast_ema, slow_ema = btind.MovAv.EMA(, btind.MovAv.EMA(period=self.p.slow)
            self.signal = btind.CrossOver(fast_ema, slow_ema)
            self.log(f'Initial portfolio value of {}')
        def start(self):
        def next(self):
            if self.orderid:
                return  # if an order is active, no new orders are allowed
            if self.signal > 0.0:  # cross upwards
                if self.position:
                    self.log(f'COVER Short Position of {abs(self.position.size)} shares '
                             f'at {[0]:.2f}')
                self.log(f'BUY {self.getsizing()} shares at {[0]}')
            elif self.signal < 0.0:
                if self.position:
                    self.log(f'CLOSE Long Position of {self.position.size} shares '
                             f'at {[0]:.2f}')
                if not self.p.longonly:
                    self.log(f'SELL {abs(self.getsizing())} shares at '
        def notify_order(self, order):
            if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
                return  # Await further notifications
            if order.status == order.Completed:
                if order.isbuy():
                    buytxt = f'BUY COMPLETED. ' \
                             f'Size: {order.executed.size}, ' \
                             f'Price: {order.executed.price:.2f}, ' \
                             f'Commission: {order.executed.comm:.2f}'
                    self.log(buytxt, order.executed.dt)
                    selltxt = 'SELL COMPLETED. ' \
                             f'Size: {abs(order.executed.size)}, ' \
                             f'Price: {order.executed.price:.2f}, ' \
                             f'Commission: {order.executed.comm:.2f}'
                    self.log(selltxt, order.executed.dt)
            elif order.status in [order.Expired, order.Canceled, order.Margin]:
                pass  # Simply log
            # Allow new orders
            self.orderid = None
        def notify_trade(self, trade):
            if trade.isclosed:
                self.log(f'TRADE COMPLETED, '
                         f'Portfolio: {}, '
                         f'Gross: {trade.pnl:.2f}, '
                         f'Net: {trade.pnlcomm:.2f}')
            elif trade.justopened:
                #self.log('TRADE OPENED, SIZE %2d' % trade.size)

    import backtrader as bt
    class MaxRiskSizer(bt.Sizer):
        params = (('risk', 0.95),)
        def __init__(self):
            if self.p.risk > 1 or self.p.risk < 0:
                raise ValueError('The risk parameter is a percentage which must be'
                    'entered as a float. e.g. 0.5')
        def _getsizing(self, comminfo, cash, data, isbuy):
            #return comminfo.getsize(data.close[0], cash * self.p.risk)
            return round(cash * self.p.risk / data.close[0])

    import backtrader as bt
    class DegiroCommission(bt.CommInfoBase):
        params = (('per_share', 0.004), ('flat', 0.5),)
        def _getcommission(self, size, price, pseudoexec):
            return self.p.flat + size * self.p.per_share

  • @quake004

    def _getsizing(self, comminfo, cash, data, isbuy):
            return round( * self.p.risk / data.close[0])

  • That's right thanks. I've also seen the commission for long and short positions where different. I had to change

    return self.p.flat + abs(size) * self.p.per_share

Log in to reply