user28679678
user28679678

Reputation: 11

'TypeError: other must be a MultiIndex or a list of tuples' when trying to backtest yfinance data using backtesting

Here is the part of the code that is specifying the backtest. I already have a 'Signal' column in another dataframe 'data', but that isnt the problem here.

temp = yf.download(
    symb,
    interval=interval,
    period=period
    )
temp.reset_index(inplace=True)
print(temp.columns)


class MyStrategy(Strategy):
    stop_factor = 0.02  # stop loss factor
    take_profit_factor = 0.04  # take profit factor for 1:2 risk-reward ratio

    def init(self):
        # Set up signals
        self.signal = self.data['signals']

    def next(self):
        # If the signal is a buy, we want to buy
        if self.signal == 1:
            self.buy(size=1, stop=self.data.close[-1] * (1 - self.stop_factor), takeprofit=self.data.close[-1] * (1 + self.take_profit_factor))
        # If the signal is a sell, we want to sell
        elif self.signal == -1:
            self.sell(size=1, stop=self.data.close[-1] * (1 + self.stop_factor), takeprofit=self.data.close[-1] * (1 - self.take_profit_factor))

# Backtest setup

bt = Backtest(temp, MyStrategy, cash=10000, commission=0.002)
stats = bt.run()

# Print the backtest results
print(stats) 

I have tried to change the index to a new 'Candle_No' column and tried to reset the index but nothing has worked.

This is the error that actually shows up:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/multi.py in _convert_can_do_setop(self, other)
   3853                 try:
-> 3854                     other = MultiIndex.from_tuples(other, names=self.names)
   3855                 except (ValueError, TypeError) as err:

8 frames
ValueError: Length of names must match number of levels in MultiIndex.

The above exception was the direct cause of the following exception:

TypeError                                 Traceback (most recent call last)
/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/multi.py in _convert_can_do_setop(self, other)
   3856                     # ValueError raised by tuples_to_object_array if we
   3857                     #  have non-object dtype
-> 3858                     raise TypeError(msg) from err
   3859         else:
   3860             result_names = get_unanimous_names(self, other)

TypeError: other must be a MultiIndex or a list of tuples

Upvotes: 1

Views: 149

Answers (1)

The data that you collect from yfinance has MultiIndex columns, especially when downloading data with specific intervals. You need to deal with that. To test a solution, I needed to add data (As was mentioned to you in the comments, your question needs to be elaborated and sample data needs to be shared, particularily the data containing the signal variable. So, adapt the following to your needs:

import pandas as pd
import yfinance as yf
from backtesting import Backtest, Strategy
import numpy as np

symb = "AAPL"
interval = "1d"
period = "1y"

temp = yf.download(
    symb,
    interval=interval,
    period=period
)
temp.reset_index(inplace=True)

temp['signals'] = np.random.choice([-1, 0, 1], size=len(temp))

if isinstance(temp.columns, pd.MultiIndex):
    temp.columns = ['_'.join(col).strip() if isinstance(col, tuple) else col for col in temp.columns]

temp.rename(columns={'Open': 'Open', 'High': 'High', 'Low': 'Low', 'Close': 'Close'}, inplace=True)

class MyStrategy(Strategy):
    stop_factor = 0.02
    take_profit_factor = 0.04

    def init(self):
        self.signal = self.data['signals']

    def next(self):
        if self.signal[-1] == 1:
            self.buy(size=1, sl=self.data.Close[-1] * (1 - self.stop_factor),
                     tp=self.data.Close[-1] * (1 + self.take_profit_factor))
        elif self.signal[-1] == -1:
            self.sell(size=1, sl=self.data.Close[-1] * (1 + self.stop_factor),
                      tp=self.data.Close[-1] * (1 - self.take_profit_factor))

bt = Backtest(temp, MyStrategy, cash=10000, commission=0.002)
stats = bt.run()

print(stats)

which returns

[*********************100%%**********************]  1 of 1 completed
Start                                     0.0
End                                     251.0
Duration                                251.0
Exposure Time [%]                         0.0
Equity Final [$]                      10000.0
Equity Peak [$]                       10000.0
Return [%]                                0.0
Buy & Hold Return [%]               25.001282
Return (Ann.) [%]                         0.0
Volatility (Ann.) [%]                     NaN
Sharpe Ratio                              NaN
Sortino Ratio                             NaN
Calmar Ratio                              NaN
Max. Drawdown [%]                        -0.0
Avg. Drawdown [%]                         NaN
Max. Drawdown Duration                    NaN
Avg. Drawdown Duration                    NaN
# Trades                                  0.0
Win Rate [%]                              NaN
Best Trade [%]                            NaN
Worst Trade [%]                           NaN
Avg. Trade [%]                            NaN
Max. Trade Duration                       NaN
Avg. Trade Duration                       NaN
Profit Factor                             NaN
...
_equity_curve                   Equity  Dr...
_trades                   Empty DataFrame
...
dtype: object

Upvotes: 1

Related Questions