code: def _backtesting_forecaster_multiseries(
      forecaster,
      series: pd.DataFrame,
      steps: int,
      metric: Union[str, Callable, list],
      initial_train_size: Optional[int] = None,
      fixed_train_size: bool = True,
      gap: int = 0,
      allow_incomplete_fold: bool = True,
      levels: Optional[Union[str, list]] = None,
      exog: Optional[Union[pd.Series, pd.DataFrame]] = None,
      refit: Optional[Union[bool, int]] = False,
      interval: Optional[list] = None,
      n_boot: int = 500,
      random_state: int = 123,
      in_sample_residuals: bool = True,
      n_jobs: Optional[Union[int, str]] = "auto",
      verbose: bool = False,
      show_progress: bool = True,
  ) -> Tuple[pd.DataFrame, pd.DataFrame]:
      """
      Backtesting for multi-series and multivariate forecasters.
  
      - If `refit` is `False`, the model will be trained only once using the
      `initial_train_size` first observations.
      - If `refit` is `True`, the model is trained on each iteration, increasing
      the training set.
      - If `refit` is an `integer`, the model will be trained every that number
      of iterations.
      - If `forecaster` is already trained and `initial_train_size` is `None`,
      no initial train will be done and all data will be used to evaluate the model.
      However, the first `len(forecaster.last_window)` observations are needed
      to create the initial predictors, so no predictions are calculated for them.
  
      A copy of the original forecaster is created so that it is not modified during
      the process.
  
      Parameters
      ----------
      forecaster : ForecasterAutoregMultiSeries, ForecasterAutoregMultiSeriesCustom, ForecasterAutoregMultiVariate
          Forecaster model.
      series : pandas DataFrame
          Training time series.
      steps : int
          Number of steps to predict.
      metric : str, Callable, list
          Metric used to quantify the goodness of fit of the model.
  
          - If `string`: {'mean_squared_error', 'mean_absolute_error',
          'mean_absolute_percentage_error', 'mean_squared_log_error'}
          - If `Callable`: Function with arguments y_true, y_pred that returns a float.
          - If `list`: List containing multiple strings and/or Callables.
      initial_train_size : int, default `None`
          Number of samples in the initial train split. If `None` and `forecaster` is
          already trained, no initial train is done and all data is used to evaluate the
          model. However, the first `len(forecaster.last_window)` observations are needed
          to create the initial predictors, so no predictions are calculated for them.
          This useful to backtest the model on the same data used to train it.
          `None` is only allowed when `refit` is `False` and `forecaster` is already
          trained.
      fixed_train_size : bool, default `True`
          If True, train size doesn't increase but moves by `steps` in each iteration.
      gap : int, default `0`
          Number of samples to be excluded after the end of each training set and
          before the test set.
      allow_incomplete_fold : bool, default `True`
          Last fold is allowed to have a smaller number of samples than the
          `test_size`. If `False`, the last fold is excluded.
      levels : str, list, default `None`
          Time series to be predicted. If `None` all levels will be predicted.
      exog : pandas Series, pandas DataFrame, default `None`
          Exogenous variable/s included as predictor/s. Must have the same
          number of observations as `y` and should be aligned so that y[i] is
          regressed on exog[i].
      refit : bool, int, default `False`
          Whether to re-fit the forecaster in each iteration. If `refit` is an integer,
          the Forecaster will be trained every that number of iterations.
      interval : list, default `None`
          Confidence of the prediction interval estimated. Sequence of percentiles
          to compute, which must be between 0 and 100 inclusive. If `None`, no
          intervals are estimated.
      n_boot : int, default `500`
          Number of bootstrapping iterations used to estimate prediction
          intervals.
      random_state : int, default `123`
          Sets a seed to the random generator, so that boot intervals are always
          deterministic.
      in_sample_residuals : bool, default `True`
          If `True`, residuals from the training data are used as proxy of prediction
          error to create prediction intervals. If `False`, out_sample_residuals
          are used if they are already stored inside the forecaster.
      n_jobs : int, 'auto', default `'auto'`
          The number of jobs to run in parallel. If `-1`, then the number of jobs is
          set to the number of cores. If 'auto', `n_jobs` is set using the function
          skforecast.utils.select_n_jobs_backtesting.
          **New in version 0.9.0**
      verbose : bool, default `False`
          Print number of folds and index of training and validation sets used
          for backtesting.
      show_progress: bool, default `True`
          Whether to show a progress bar.
  
      Returns
      -------
      metrics_levels : pandas DataFrame
          Value(s) of the metric(s). Index are the levels and columns the metrics.
      backtest_predictions : pandas Dataframe
          Value of predictions and their estimated interval if `interval` is not `None`.
          If there is more than one level, this structure will be repeated for each of them.
  
          - column pred: predictions.
          - column lower_bound: lower bound of the interval.
          - column upper_bound: upper bound of the interval.
  
      """
  
      forecaster = deepcopy(forecaster)
  
      if n_jobs == "auto":
          n_jobs = select_n_jobs_backtesting(
              forecaster=forecaster, refit=refit
          )
      else:
          n_jobs = n_jobs if n_jobs > 0 else cpu_count()
  
      levels = _initialize_levels_model_selection_multiseries(
          forecaster=forecaster, series=series, levels=levels
      )
  
      if not isinstance(metric, list):
          metrics = [
              _get_metric(metric=metric)
              if isinstance(metric, str)
              else metric
          ]
      else:
          metrics = [
              _get_metric(metric=m) if isinstance(m, str) else m
              for m in metric
          ]
  
      store_in_sample_residuals = False if interval is None else True
  
      if initial_train_size is not None:
          # First model training, this is done to allow parallelization when `refit`
          # is `False`. The initial Forecaster fit is outside the auxiliary function.
          exog_train = (
              exog.iloc[:initial_train_size,]
              if exog is not None
              else None
          )
          forecaster.fit(
              series=series.iloc[:initial_train_size,],
              exog=exog_train,
              store_in_sample_residuals=store_in_sample_residuals,
          )
          window_size = forecaster.window_size
          externally_fitted = False
      else:
          # Although not used for training, first observations are needed to create
          # the initial predictors
          window_size = forecaster.window_size
          initial_train_size = window_size
          externally_fitted = True
  
      folds = _create_backtesting_folds(
          data=series,
          window_size=window_size,
          initial_train_size=initial_train_size,
          test_size=steps,
          externally_fitted=externally_fitted,
          refit=refit,
          fixed_train_size=fixed_train_size,
          gap=gap,
          allow_incomplete_fold=allow_incomplete_fold,
          return_all_indexes=False,
          verbose=verbose,
      )
  
      if refit:
          n_of_fits = int(len(folds) / refit)
          if (
              type(forecaster).__name__
              != "ForecasterAutoregMultiVariate"
              and n_of_fits > 50
          ):
              warnings.warn(
                  (
                      f"The forecaster will be fit {n_of_fits} times. This can take substantial "
                      f"amounts of time. If not feasible, try with `refit = False`.\n"
                  ),
                  LongTrainingWarning,
              )
          elif (
              type(forecaster).__name__
              == "ForecasterAutoregMultiVariate"
              and n_of_fits * forecaster.steps > 50
          ):
              warnings.warn(
                  (
                      f"The forecaster will be fit {n_of_fits*forecaster.steps} times "
                      f"({n_of_fits} folds * {forecaster.steps} regressors). This can take "
                      f"substantial amounts of time. If not feasible, try with `refit = False`.\n"
                  ),
                  LongTrainingWarning,
              )
  
      if show_progress:
          folds = tqdm(folds)
  
      def _fit_predict_forecaster(
          series, exog, forecaster, interval, fold
      ):
          """
          Fit the forecaster and predict `steps` ahead. This is an auxiliary
          function used to parallelize the backtesting_forecaster_multiseries
          function.
          """
  
          train_idx_start = fold[0][0]
          train_idx_end = fold[0][1]
          last_window_start = fold[1][0]
          last_window_end = fold[1][1]
          test_idx_start = fold[2][0]
          test_idx_end = fold[2][1]
  
          if fold[4] is False:
              # When the model is not fitted, last_window must be updated to include
              # the data needed to make predictions.
              last_window_series = series.iloc[
                  last_window_start:last_window_end,
              ]
          else:
              # The model is fitted before making predictions. If `fixed_train_size`
              # the train size doesn't increase but moves by `steps` in each iteration.
              # If `False` the train size increases by `steps` in each  iteration.
              series_train = series.iloc[train_idx_start:train_idx_end,]
              exog_train = (
                  exog.iloc[train_idx_start:train_idx_end,]
                  if exog is not None
                  else None
              )
              last_window_series = None
              forecaster.fit(
                  series=series_train,
                  exog=exog_train,
                  store_in_sample_residuals=store_in_sample_residuals,
              )
  
          next_window_exog = (
              exog.iloc[test_idx_start:test_idx_end,]
              if exog is not None
              else None
          )
  
          steps = len(range(test_idx_start, test_idx_end))
          if (
              type(forecaster).__name__
              == "ForecasterAutoregMultiVariate"
              and gap > 0
          ):
              # Select only the steps that need to be predicted if gap > 0
              test_idx_start = fold[3][0]
              test_idx_end = fold[3][1]
              steps = list(
                  np.arange(len(range(test_idx_start, test_idx_end)))
                  + gap
                  + 1
              )
  
          if interval is None:
              pred = forecaster.predict(
                  steps=steps,
                  levels=levels,
                  last_window=last_window_series,
                  exog=next_window_exog,
              )
          else:
              pred = forecaster.predict_interval(
                  steps=steps,
                  levels=levels,
                  last_window=last_window_series,
                  exog=next_window_exog,
                  interval=interval,
                  n_boot=n_boot,
                  random_state=random_state,
                  in_sample_residuals=in_sample_residuals,
              )
  
          if (
              type(forecaster).__name__
              != "ForecasterAutoregMultiVariate"
              and gap > 0
          ):
              pred = pred.iloc[gap:,]
  
          return pred
  
      backtest_predictions = Parallel(n_jobs=n_jobs)(
          delayed(_fit_predict_forecaster)(
              series=series,
              exog=exog,
              forecaster=forecaster,
              interval=interval,
              fold=fold,
          )
          for fold in folds
      )
  
      backtest_predictions = pd.concat(backtest_predictions)
  
      metrics_levels = [
          [
              m(
                  y_true=series[level].loc[backtest_predictions.index],
                  y_pred=backtest_predictions[level],
              )
              for m in metrics
          ]
          for level in levels
      ]
  
      metrics_levels = pd.concat(
          [
              pd.DataFrame({"levels": levels}),
              pd.DataFrame(
                  data=metrics_levels,
                  columns=[
                      m if isinstance(m, str) else m.__name__
                      for m in metrics
                  ],
              ),
          ],
          axis=1,
      )
  
      return metrics_levels, backtest_predictions
  
imports: from typing import Union, Tuple, Optional, Callable
  import numpy
  import pandas
  import warnings
  import logging
  from copy import deepcopy
  from joblib import Parallel, delayed, cpu_count
  from tqdm.auto import tqdm
  from sklearn.model_selection import ParameterGrid
  from sklearn.model_selection import ParameterSampler
  import optuna
  from optuna.samplers import TPESampler, RandomSampler
  from ..exceptions import LongTrainingWarning
  from ..exceptions import IgnoredArgumentWarning
  from ..model_selection.model_selection import _get_metric
  from ..model_selection.model_selection import _create_backtesting_folds
  from ..utils import check_backtesting_input
  from ..utils import initialize_lags_grid
  from ..utils import select_n_jobs_backtesting
label_desc: Use image node_id 2 for calling a global function with example usage: _backtesting_forecaster_multiseries(forecaster, series, steps, metric, initial_train_size, fixed_train_size, gap, allow_incomplete_fold, levels, exog, refit, interval, n_boot, random_state, in_sample_residuals, n_jobs, verbose, show_progress) and returns: metrics_levels,  backtest_predictions, pred