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

Simple buy-and-hold strategy



  • I'm trying to simulate a very simple buy-and-hold strategy, but my code never seems to close its position. Data is SPY daily candles from 2000 to current.

    Strategy
    My logic is basically to say that if we're at the beginning of the data frame, place a buy. If we're at the end of the dataframe, close the position.

    # Define our backtester class
    class BuyAndHold(bt.Strategy):
    
        def __init__(self):
            self.startcash = self.broker.getvalue()
    
        def next(self):
    
            for index, data in enumerate(self.datas):
                datetime, dataname = self.datetime.date(), data._name
    
                pos = self.getposition(data).size
                if not pos:
    
                    # print(self.datetime.date().strftime("%Y-%m-%d %H:%M:%S"))
    
                    if self.datetime.date().strftime("%Y-%m-%d") == pd.read_pickle('data').head(2).date[0].strftime("%Y-%m-%d"):
                        trade_setup = 'LONG'
                    elif self.datetime.date().strftime("%Y-%m-%d") == pd.read_pickle('data').tail(2).date[0].strftime("%Y-%m-%d"):
                        trade_setup = 'CLOSE'
                    else:
                        trade_setup = None
    
                    if trade_setup == 'LONG':
                        print("buying")
                        self.buy()
    
                    elif trade_setup == 'CLOSE':
                        print("closing ")
                        self.close()
    
    

    Run the strategy and print relevant bits:

    def run_the_strategy():
    
        one_run_data = []
        # one_run_data.append(params)
    
        #Variable for our starting cash
        start_cash = 100000
    
        # Create an instance of cerebro
        cerebro = Cerebro()
    
        # Set the starting cash
        cerebro.broker.setcash(start_cash)
    
        # Add our strategy
        cerebro.addstrategy(BuyAndHold)
    
    
        data = PandasData(dataname=pd.read_pickle('data'), timeframe=bt.TimeFrame.Days)
        cerebro.adddata(data, name='data')
    
        # Add a sizer
        cerebro.addsizer(bt.sizers.AllInSizer)
    
        # Add the analyzers we are interested in
        cerebro.addanalyzer(bt.analyzers.SQN, _name="sqn")    
        cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")
        cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', timeframe=bt.TimeFrame.Days)
        cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")
    
        # Run the strategy!!
        strategy = cerebro.run()
    
        # Calculate success 
        sqn = strategy[0].analyzers.sqn.get_analysis()['sqn']
        n_trades_taken = strategy[0].analyzers.sqn.get_analysis()['trades']
        pnl = cerebro.broker.getvalue()
        print("SQN: ", sqn, "n trades", n_trades_taken, "ending PnL: $", pnl)
    

    However, this is what the strategy does when run:

    $ python3 buy-and-hold.py
    Current time is 2018-11-25 14:51:49.310095
    Press Enter to continue...
    buying
    closing 
    SQN:  0 n trades 0 ending PnL: $ 100000.0
    

    It apparently tries to open the position (buy) and then close (with self.close) but makes no trades and clearly does not calculate the PnL properly. Why? I've tried switching out sizers and some other things. My only guess is that either self.buy() or self.close() is not working as intended here.


  • administrators

    @tw00000 said in Simple buy-and-hold strategy:

    My only guess is that either self.buy() or self.close() is not working as intended here.

    Sorry, but with a statement like that I don't really feel like writing a proper answer. Please have a look at your own code.



  • Since the broker values is not changed, than it looks like the script didn't buy anything. Did you check order notification if the orders were issued and executed?

    Simpler way to open trade at the very beginning is

    if not pos:
        self.buy()
    

    Will buy only at the very first next()



  • @ab_trader said in Simple buy-and-hold strategy:

    Since the broker values is not changed, than it looks like the script didn't buy anything. Did you check order notification if the orders were issued and executed?

    Simpler way to open trade at the very beginning is

    if not pos:
        self.buy()
    

    Will buy only at the very first next()

    Thanks for this advice, this helped me realize what my problem was with putting the self.close() inside of the if not pos:, which clearly would never happen! Also, this led me to simplify the next code down:

    def next(self):
    
        for index, data in enumerate(self.datas):
            datetime, dataname = self.datetime.date(), data._name
    
            pos = self.getposition(data).size
            if not pos:
                print("buying")
                self.buy()
    
            if self.datetime.date().strftime("%Y-%m-%d") == pd.read_pickle('data').tail(1).date[0].strftime("%Y-%m-%d"):
                self.close()
                print("closing")
            else:
                trade_setup = None
    
    

    Strangely, now it (apparently) buys multiple times, but at least moving the self.close() statement out of the if not pos: conditional solved the problem with closing the trade!

    $ python3 buy-and-hold.py
    Current time is 2018-11-26 21:58:09.593324
    Press Enter to continue...
    buying
    buying
    buying
    buying
    buying
    closing
    SQN:  0 n trades 1 ending PnL: $ 188986.74126607418
    See REPORT-buy-and-hold.pdf for report with backtest results.
    Completed at 2018-11-26 21:58:17.480872
    

    I'm not sure why the buy section is triggered so many times, but this definitely solved my problem @ab_trader ! Thanks!



  • I think that position closing can be also simplified (didn't tested by myself):

    if (len(self.datas) - len(self)) == 1:
        self.close()
    

    It seems to me that such script should issue closing order before last bar. But, again, I didn't test it.

    As a wild guess about several buy signals - might be a case that open price was so different from close price that bt was not able to buy number of stocks based on AllInSizer calcs. Try with size=1.