Here is an example of what I did:
Generating the lines and params tuples by passing in list(df.columns)
def get_extra_df_columns(self, df_columns: list[str] = []) -> list[tuple]:
lines: tuple = ()
params: tuple(tuple) = ()
for column in df_columns:
if column not in ['symbol', 'open', 'high', 'low', 'close', 'volume']:
lines = lines + (column,)
params = params + ( (column, -1), )
return [lines, params]
Creating the subclass by parsing the df to be parsed then returning it with the subclass attributes set dynamically:
def _create_data_feeder(self, database: Database, df: pd.DataFrame ):
lines, params = database.get_extra_df_columns(df_columns=list(df.columns) )
return type('PandasDataFeed', (bt.feeds.PandasData, ), {'lines':lines, 'params':params} )
Adding the dataframe to cerebro via the created data_feeder subclass
self.data_feeder = self._create_data_feeder(database=database, df=df)
self.cerebro.adddata(self.data_feeder(dataname=df, name=symbol ))
The extra columns in the df are vwap and supertrend and are accessible without hardcoding the lines and params
class TestStrategy(bt.Strategy):
def __init__(self):
for i, d in enumerate(self.datas):
bt.ind.SMA(d.supertrend, period=1, subplot=False, plotname='supertrend')
bt.ind.SMA(d.vwap, period=1, subplot=False, plotname='vwap')