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

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
    

    runstrat.py

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    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)
        cerebro.broker.set_cash(1700)
        cerebro.broker.set_coc(True)
    
        cerebro.addsizer(MaxRiskSizer)
        cerebro.broker.addcommissioninfo(comminfo)
    
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, 'data/CPE.csv')
        data = bt.feeds.YahooFinanceCSVData(
            dataname=datapath,
            # 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),
            reverse=False
        )
        cerebro.adddata(data)
    
        start_portfolio_value = cerebro.broker.getvalue()
        cerebro.run()
        end_portfolio_value = cerebro.broker.getvalue()
        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}')
    
        cerebro.plot()
    
    if __name__ == '__main__':
        runstrategy()
    

    strategies.py

    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(
            fast=13,
            slow=48,
            printout=True,
            longonly=False,
        )
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[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(period=self.p.fast), btind.MovAv.EMA(period=self.p.slow)
            self.signal = btind.CrossOver(fast_ema, slow_ema)
            self.log(f'Initial portfolio value of {self.broker.get_value():.2f}')
    
        def start(self):
            pass
    
        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 {self.data.close[0]:.2f}')
                    self.close()
    
                self.log(f'BUY {self.getsizing()} shares at {self.data.close[0]}')
                self.buy()
    
            elif self.signal < 0.0:
                if self.position:
                    self.log(f'CLOSE Long Position of {self.position.size} shares '
                             f'at {self.data.close[0]:.2f}')
                    self.close()
    
                if not self.p.longonly:
                    self.log(f'SELL {abs(self.getsizing())} shares at '
                             f'{self.data.close[0]}')
                    self.sell()
    
        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)
                else:
                    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]:
                self.log(f'{order.Status[order.status]}')
                pass  # Simply log
    
            # Allow new orders
            self.orderid = None
    
        def notify_trade(self, trade):
            if trade.isclosed:
                self.log(f'TRADE COMPLETED, '
                         f'Portfolio: {self.broker.get_value():.2f}, '
                         f'Gross: {trade.pnl:.2f}, '
                         f'Net: {trade.pnlcomm:.2f}')
    
            elif trade.justopened:
                #self.log('TRADE OPENED, SIZE %2d' % trade.size)
                pass
    

    sizers.py

    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])
    

    commissions.py

    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.broker.get_value() * 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 commissions.py:

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

Log in to reply
 

});