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 eitherself.buy()
orself.close()
is not working as intended here. -
@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 theif not pos:
, which clearly would never happen! Also, this led me to simplify thenext
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 theif 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 thatopen
price was so different fromclose
price thatbt
was not able to buy number of stocks based onAllInSizer
calcs. Try withsize=1
. -
@tw00000 said in Simple buy-and-hold strategy:
if self.datetime.date().strftime("%Y-%m-%d") == pd.read_pickle('data').tail(1).date[0].strftime("%Y-%m-%d"):
Hey! I am trying to implement something similar. By executing this same line i am getting the below error -
File "C:\Users\40100147\AppData\Roaming\Python\Python37\site-packages\pandas\io\common.py", line 651, in get_handle handle = open(handle, ioargs.mode) PermissionError: [Errno 13] Permission denied: 'data'
Can you explain how you are using pd.pickle here ? I am using a single source of data. Below is the complete next() code
def next(self): pos = self.getposition(self.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
-
@ab_trader Hey! Did this line work for you ? I am getting the same value for len(self.data) and len(self) at every next step so the difference is never 1.
-
@abhishek-anand-1 i never checked this by myself.