TimeReturn doesn't seem to work
-
I am trying to get a basic buy and hold strategy working, but TimeReturn does not seem to be working... my code:
start_date = datetime(2019,9,30) end_date = datetime(2019,10,5) data_df = get_eod_data('SPY', start_date, end_date) data_df['close_change'] = data_df['close'].pct_change() print(data_df[['close', 'close_change']]) class BuyAndHold(bt.Strategy): def __init__(self): self.logger = logging.getLogger(__name__) self.logger.info(f'Initializing strategy: BuyAndHold') def nextstart(self): self.logger.info(f"BUY {self.data.datetime.date(0)}: {self.data.close[0]:.2f}") self.order_target_value(target=self.broker.get_cash()) def stop(self): self.logger.info(f"starting value: {self.broker.startingcash}") self.logger.info(f"ending value : {self.broker.getvalue()}") roi = (self.broker.get_value() / self.broker.startingcash) - 1.0 self.logger.info(f"roi: {roi}") data = bt.feeds.PandasData(dataname=data_df, openinterest=None) cerebro = bt.Cerebro(stdstats=False) cerebro.adddata(data) cerebro.addanalyzer(btanalyzers.TimeReturn, _name='returns') cerebro.addobserver(bt.observers.Broker) cerebro.addobserver(bt.observers.DrawDown) cerebro.addsizer(bt.sizers.PercentSizer, percents=100) cerebro.addstrategy(BuyAndHold) results = cerebro.run() # run it all #cerebro.plot(style='bar') # and plot it with a single command returns_in_ordered_dict = results[0].analyzers.getbyname('returns').get_analysis() returns_df = pd.DataFrame([returns_in_ordered_dict]).T.rename(columns={0: 'return'}) print(returns_df) print(286.601471 /288.957764-1)
the output:
close close_change trade_date 2019-09-30 288.957764 NaN 2019-10-01 285.520630 -0.011895 2019-10-02 280.477051 -0.017664 2019-10-03 282.774933 0.008193 2019-10-04 286.601471 0.013532 13:41:53 [ buy_hold]:INFO: Initializing strategy: BuyAndHold 13:41:53 [ buy_hold]:INFO: BUY 2019-09-30: 288.96 13:41:53 [ buy_hold]:INFO: starting value: 10000.0 13:41:53 [ buy_hold]:INFO: ending value : 9887.776226027483 13:41:53 [ buy_hold]:INFO: roi: -0.011222377397251737 return 2019-09-30 0.000000 2019-10-01 -0.014897 2019-10-02 -0.017407 2019-10-03 0.008071 2019-10-04 0.013333 -0.008154454711242876
since this is a buy and hold, and it buys on the first bar (288.96) (the close on 2019-9-30), over these five days, the buy and hold should return -81.544bps, however using hte TimeReturn, it says my return is -112.22bps. also the daily returns are wrong. is there something I am not understanding here ?
-
@adamb032 I believe what is happening, is in your dataframe, you are calculating the
pct_change
of the price. TimeReturn in backtrader is working off the value of the account.Try adding this into your analyzer.
cerebro.addanalyzer(bt.analyzers.TimeReturn, **dict(_name='returns', data=data))
By telling the analyzer to use data, you will get identical outputs to your dataframe.
Here are the outputs on my end:
Original data with percent change. Close close_change Date 2019-09-30 296.769989 NaN 2019-10-01 293.239990 -0.011895 2019-10-02 288.059998 -0.017665 2019-10-03 290.420013 0.008193 2019-10-04 294.350006 0.013532 TimeReturn using the data as parameter in the analyzer: return 2019-09-30 0.002703 2019-10-01 -0.011895 2019-10-02 -0.017665 2019-10-03 0.008193 2019-10-04 0.013532 Date/Close/Cash/Value of the account: 2019-10-01 close: 293.24, cash: 174.58, value: 9851.50 2019-10-02 close: 288.06, cash: 174.58, value: 9680.56 2019-10-03 close: 290.42, cash: 174.58, value: 9758.44 2019-10-04 close: 294.35, cash: 174.58, value: 9888.13
-
@run-out ah i see, i have another question, it looks like the first bar's returns are close/open-1 (ie, the open->close) returns, is there a way to force TimeReturn to only use close->close (so the first value is nan/0) or would that require basically writing my own wrapper around TimeReturn ?
-
@adamb032 When I was reading the docs today it says that the first bar will be open to close if open is available, otherwise not data exist. Not sure if this works but try setting
firstopen
to False.- ``firstopen`` (default: ``True``) When tracking the returns of a ``data`` the following is done when crossing a timeframe boundary, for example ``Years``: - Last ``close`` of previous year is used as the reference price to see the return in the current year The problem is the 1st calculation, because the data has** no previous** closing price. As such and when this parameter is ``True`` the *opening* price will be used for the 1st calculation. This requires the data feed to have an ``open`` price (for ``close`` the standard [0] notation will be used without reference to a field price) Else the initial close will be used.