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

I'm having trouble buying on open/ selling on close using daysteps



  • Hi everyone,

    I've been gone for a few months but I'm back at it! Glad to see the community is still as active as when I went dark around April!

    Anyway, I'm trying to make a strategy that buys on open using the open price and sells on close using the close price. I have daily data and i'm trying to make use of the daysteps.py example and the DayStepsFilter.

    I sort of have it working. My strategy buys on open using the open price, and it sells on close, BUT it sells using the open price instead of the close price. I'm not sure what's going on.

    First, here is a sample log (reproduced here as is, which is a bit out of order):

    2014-12-08, BUY EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04
    2014-12-08, SELL EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04
    2014-12-08, OPERATION PROFIT, GROSS 0.00, NET -0.08
    0236,0471,0236,0236,2014-12-08T23:59:59.999989,41.91,41.91
    2014-12-08, BUY CREATE, 41.91
    0236,0472,0236,0236,2014-12-08T23:59:59.999989,41.91,41.37
    2014-12-08, SELL CREATE, 41.37
    

    First, I issue a buy for $41.91 which is the open price. That is executed with the open price of $41.91.

    Then on the closing bar of the day I issue a sell using the closing price of $41.37 and BUT it's executed using the opening price of $41.91.

    I've tried futzing with the order types of the buy and sell but could not get it working.

    Here is the next() function in the strategy and hopefully someone could point out my error:

        def next(self):
            self.callcounter += 1
    
            txtfields = list()
            txtfields.append('%04d' % len(self.data))
            txtfields.append('%04d' % self.callcounter)
            txtfields.append('%04d' % len(self))
            txtfields.append('%04d' % len(self.data0))
            txtfields.append(self.data.datetime.datetime(0).isoformat())
            txtfields.append('%.2f' % self.data0.open[0])
            txtfields.append('%.2f' % self.data0.close[0])
            print(','.join(txtfields))
    
            if len(self.data) > self.lcontrol:
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()
    
            else:
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                self.sell()
    
            self.lcontrol = len(self.data)
    

    Here's all the code (which is basically daysteps with the logging code and the code to buy/ sell on open and close.

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    import datetime  # For datetime objects
    import os.path  # To manage paths
    import sys  # To find out the script name (in argv[0])
    
    # Import the backtrader platform
    import backtrader as bt
    
    
    # Create a Stratey
    class TestStrategy(bt.Strategy):
    
        def log(self, txt, dt=None):
            ''' Logging function fot this strategy'''
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))
    
        def __init__(self):
            # Keep a reference to the "close" line in the data[0] dataseries
            self.dataclose = self.datas[0].close
            self.lcontrol = 0  # control if 1st or 2nd call
    
        def start(self):
            self.lcontrol = 0  # control if 1st or 2nd call
            self.callcounter = 0
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
    
            # Check if an order has been completed
            # Attention: broker could reject order if not enough cash
            if order.status in [order.Completed]:
                if order.isbuy():
                    self.log(
                        'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                        (order.executed.price,
                         order.executed.value,
                         order.executed.comm))
    
                else:  # Sell
                    self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                             (order.executed.price,
                              order.executed.value,
                              order.executed.comm))
    
                self.bar_executed = len(self)
    
            elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                self.log('Order Canceled/Margin/Rejected')
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    
        def next(self):
            self.callcounter += 1
    
            txtfields = list()
            txtfields.append('%04d' % len(self.data))
            txtfields.append('%04d' % self.callcounter)
            txtfields.append('%04d' % len(self))
            txtfields.append('%04d' % len(self.data0))
            txtfields.append(self.data.datetime.datetime(0).isoformat())
            txtfields.append('%.2f' % self.data0.open[0])
            txtfields.append('%.2f' % self.data0.close[0])
            print(','.join(txtfields))
    
            if len(self.data) > self.lcontrol:
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()
    
            else:
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                self.sell()
    
            self.lcontrol = len(self.data)
    
    
    if __name__ == '__main__':
        # Create a cerebro entity
        cerebro = bt.Cerebro()
    
        # Datas are in a subfolder of the samples. Need to find where the script is
        # because it could have been called from anywhere
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, 'orcl_2014.txt')
    
        # Create a Data Feed
        data = bt.feeds.YahooFinanceCSVData(
            dataname=datapath,
            # Do not pass values before this date
            fromdate=datetime.datetime(2014, 1, 1),
            # Do not pass values before this date
            todate=datetime.datetime(2014, 12, 31),
            # Do not pass values after this date
            reverse=False)
    
        # Add the Data Feed to Cerebro
        #data.addfilter(DayStepsCloseFilter)
    
        data.addfilter(bt.filters.DayStepsFilter)
        cerebro.adddata(data)
    
        # Add a strategy
        cerebro.addstrategy(TestStrategy)
    
        # Set our desired cash start
        cerebro.broker.setcash(100000.0)
    
        # consider a bar with the same time as the end of session to be the end of the session
        cerebro.broker.set_eosbar(True)
    
        # Set the commission - 0.1% ... divide by 100 to remove the %
        cerebro.broker.setcommission(commission=0.001)
    
        # Print out the starting conditions
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        # Run over everything
        cerebro.run()
    
        # Print out the final result
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    

    Thanks everyone!



  • I continued searching for answers and I came across cerebro.broker.set_coc(True).

    It sort of works but I'm still not sure I'm doing this right.

    This does let my sell order use the close price BUT it sells on the next day. The logs are in the right order here though and you can see it sells at the right price ($41.37) but on the 9th instead of the 8th.

    2014-12-05, SELL CREATE, 41.93
    2014-12-08, SELL EXECUTED, Price: 41.93, Cost: 42.02, Comm 0.04
    2014-12-08, OPERATION PROFIT, GROSS -0.09, NET -0.17
    0236,0471,0236,0236,2014-12-08T23:59:59.999989,41.91,41.91
    2014-12-08, BUY CREATE, 41.91
    2014-12-08, BUY EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04
    0236,0472,0236,0236,2014-12-08T23:59:59.999989,41.91,41.37
    2014-12-08, SELL CREATE, 41.37
    2014-12-09, SELL EXECUTED, Price: 41.37, Cost: 41.91, Comm 0.04
    2014-12-09, OPERATION PROFIT, GROSS -0.54, NET -0.62
    

  • administrators

    @brettelliot said in I'm having trouble buying on open/ selling on close using daysteps:

    It sort of works but I'm still not sure I'm doing this right.

    You were doing it wrong before. You cannot use the close price for selling unless you cheat (set_coc -> set_cheat_on_close), because when you issue the order you have already seen the price. And using a price which has been seen (and the price has already influenced the calculation of the indicators) is cheating

    @brettelliot said in I'm having trouble buying on open/ selling on close using daysteps:

    BUT it sells on the next day.

    The evaluation is done after the bar is gone.



  • OK that makes sense. What's the right way to use daily data and make two trades... one buys at the open price and the other sells at the closing price?


  • administrators

    Imho: there is no right way because to really do that and not cheat yourself you need a smaller timeframe.

    But if you want to do it:

    • Use cheat-on-open and issue an order in next_open: it will be executed with the opening price. Issue the order adding coc=False (to avoid it being hit by cheat-on-close) to either buy / sell

      See: Docs - Cheat on Open

    • Activate also cheat-on-close and send a 2nd order during next (use no coc parameter for the order) and it will be executed with the close price)



  • Thank you!!



  • Or use hourly data - then you can buy at 9am and sell at 5pm...