Extended Data Feed: Using Additional Feed Variables as Primary Indicators
-
Regards,
I successfully managed to load an extended dataset with an indicator that was calculated outside the Backtrader coding pipeline. In my strategy, it is named: "predictions".
The problem comes when I try to use this variable as a trading indicator: No trades are effected. Can anyone help? My code is below:
# Define Dynamic Class for Loading Data lines = ('datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest', 'predictions') params = (('datetime', -1), ('open', -1), ('high', -1), ('low', -1), ('close', -1), ('volume', -1), ('openinterest', -1), ('predictions', -1)) datafields = btfeeds.PandasData.datafields + (['datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest', 'predictions']) mydict = dict(lines=tuple(lines), params=params, datafields=bt.feeds.PandasData.datafields + list(lines),) PandasSPY = type('SPY', (btfeeds.PandasData,), mydict) # Trading Strategy class SPYStrategy(bt.Strategy): def __init__(self): self.predictions = self.datas[0].predictions def next(self): if self.data.close[0] < self.data.close[-1] and self.predictions > 0: print("Signal Detected") self.buy(size=1000) def notify_trade(self, trade): if trade.isclosed: dt = self.data.datetime.date() print('---------------------------- TRADE ---------------------------------') print("1: Data Name: {}".format(trade.data._name)) print("2: Bar Num: {}".format(len(trade.data))) print("3: Current date: {}".format(dt)) print('4: Status: Trade Complete') print('5: Ref: {}'.format(trade.ref)) print('6: PnL: {}'.format(round(trade.pnl,2))) print('--------------------------------------------------------------------') # Variable for our starting cash startcash = 10000 # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) # Set Commission - The Proportional Transaction Cost is Set as the Commission cerebro.broker.setcommission(commission=0.004199108990760269) # Add a strategy cerebro.addstrategy(SPYStrategy) # Get a pandas dataframe datapath = ('event_based_backtesting.csv') # Compile dataframe object dataframe = pandas.read_csv(datapath, parse_dates=True, index_col=0) # Pass it to the backtrader datafeed and add it to the cerebro data = PandasSPY(dataname=dataframe) cerebro.adddata(data) # Run over everything cerebro.run() # Get final portfolio Value portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash # Print out the final result print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) # Finally plot the end results cerebro.plot(style='candlestick')
-
1. That's a very complex and wrong way of extending the data feed. Using
datafields
was deprecated long ago, but that's not the wrong part.class MyPandasdata(bt.feeds.PandasData): lines = ('only_new_lines_go_here',) # NOT ALL PARAMS need be declared params = dict( redefine_param=x, param_for_new_line=y, )
Note:
datetime=-1
is not probably what you want (and it is not the default value inPandasData
), but with missing data (see below) it is impossible to know.2. In order for anyone to know why your trades are not executing, let me suggest that you
- Show some sample data
- Show logging of what's happening with the orders (are the orders getting rejected?)
It may be something as simple as not having enough cash.
-
Resolved! The issue stemmed from my inclusion of BT pipeline OHLCV parameters in the "lines" object for added variable.
Works well now.
-
For the benefit of others, I have decided to share the full code of the datafeed extension solution that worked for me:
# Define Dynamic Class for Loading Data class GenericCSV(bt.feeds.GenericCSVData): lines = ('stock_p_change', 'predictions') params = (('nullvalue', float('NaN')), ('dtformat', '%m/%d/%Y'), ('stock_p_change', 7), ('predictions', 8)) # Trading Strategy class SPYStrategy(bt.Strategy): def __init__(self): # The indicator autoregisters and will plot even if no obvious # reference is kept to it in the class pass def next(self): if self.data.predictions > 0 and self.data.stock_p_change < -0.005: self.buy(stake=1000) else: if self.data.predictions < 1 and self.data.stock_p_change > 0: self.close() def notify_trade(self, trade): dt = self.data.datetime.date() if trade.isclosed: print('{} {} Closed: PnL Gross {}, Net {}'.format( dt, trade.data._name, round(trade.pnl,2), round(trade.pnlcomm,2))) # Variable for our starting cash startcash = 10000 # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) # Set Commission - The Proportional Transaction Cost is Set as the Commission cerebro.broker.setcommission(commission=0.005199108990760269) # Add a strategy cerebro.addstrategy(SPYStrategy) # Get Data data = GenericCSV(dataname="event_based_backtesting.csv") cerebro.adddata(data) # Run over everything cerebro.run() # Get final portfolio Value portvalue = cerebro.broker.getvalue() pnl = portvalue - startcash # Print out the final result print('Final Portfolio Value: ${}'.format(portvalue)) print('P/L: ${}'.format(pnl)) # Finally plot the end results cerebro.plot(style='line')
To check if the added variables are being read in correctly--sometimes using the parameter "7" for your added variables may accidentally parse "openinterest" as an added variable--you can use this code:
# Define Dynamic Class for Loading Data class GenericCSV(bt.feeds.GenericCSVData): lines = ('stock_p_change', 'predictions') params = (('nullvalue', float('NaN')), ('dtformat', '%m/%d/%Y'), ('stock_p_change', 7), ('predictions', 8)) # Trading Strategy class SPYStrategy(bt.Strategy): def __init__(self): # The indicator autoregisters and will plot even if no obvious # reference is kept to it in the class pass def next(self): print('%03d %f %f, %f' % ( len(self), self.data.close[0], self.data.stock_p_change[0], self.data.predictions[0],)) # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) # Add a strategy cerebro.addstrategy(SPYStrategy) # Get Data data = GenericCSV(dataname="event_based_backtesting.csv") cerebro.adddata(data) # Run over everything cerebro.run()