def _historical_forecasts_general_checks(
    model, series, kwargs, is_conformal: bool = False
):
    """
    Performs checks common to ForecastingModel and SKLearnModel backtest() methods

    Parameters
    ----------
    model
        The forecasting model.
    series
        Either series when called from ForecastingModel, or target_series if called from SKLearnModel
    kwargs
        Params specified by the caller of backtest(), they take precedence over the arguments' default values
    """
    # parse kwargs
    n = SimpleNamespace(**kwargs)

    # check forecast horizon
    if not n.forecast_horizon > 0:
        raise_log(
            ValueError("The provided forecasting horizon must be a positive integer."),
            logger,
        )

    # check stride
    if not n.stride > 0:
        raise_log(
            ValueError("The provided stride parameter must be a positive integer."),
            logger,
        )

    # check stride for ConformalModel
    if is_conformal and (
        n.stride < model.cal_stride or n.stride % model.cal_stride > 0
    ):
        raise_log(
            ValueError(
                f"The provided `stride` parameter must be a round-multiple of `cal_stride={model.cal_stride}` "
                f"and `>=cal_stride`. Received `stride={n.stride}`"
            ),
            logger,
        )

    series = series2seq(series)

    if n.start is not None:
        # check start parameter in general (non series dependent)
        if not isinstance(n.start, (float, int, np.int64, pd.Timestamp)):
            raise_log(
                TypeError(
                    "`start` must be either `float`, `int`, `pd.Timestamp` or `None`."
                ),
                logger,
            )

        if n.start_format not in ["position", "value"]:
            raise_log(
                ValueError(
                    f"`start_format` must be on of ['position', 'value']. Received '{n.start_format}'."
                )
            )
        if n.start_format == "position" and not isinstance(n.start, (int, np.int64)):
            raise_log(
                ValueError(
                    f"Since `start_format='position'`, `start` must be an integer, received {type(n.start)}."
                ),
                logger,
            )
        if isinstance(n.start, float):
            if is_conformal:
                raise_log(
                    ValueError(
                        "`start` of type float is not supported for `ConformalModel`."
                    ),
                    logger,
                )
            if not 0.0 <= n.start <= 1.0:
                raise_log(
                    ValueError("if `start` is a float, must be between 0.0 and 1.0."),
                    logger,
                )

        series_freq = None
        for idx, series_ in enumerate(series):
            start_is_value = False
            # check specifically for int and Timestamp as error by `get_timestamp_at_point` is too generic
            if isinstance(n.start, pd.Timestamp):
                if not series_._has_datetime_index:
                    raise_log(
                        ValueError(
                            "if `start` is a `pd.Timestamp`, all series must be indexed with a `pd.DatetimeIndex`"
                        ),
                        logger,
                    )
                if n.start > series_.end_time():
                    raise_log(
                        ValueError(
                            f"`start` time `{n.start}` is after the last timestamp `{series_.end_time()}` of the "
                            f"series at index: {idx}."
                        ),
                        logger,
                    )
                start_is_value = True
            elif isinstance(n.start, (int, np.int64)):
                if n.start_format == "position" or series_.has_datetime_index:
                    if n.start >= len(series_):
                        raise_log(
                            ValueError(
                                f"`start` position `{n.start}` is out of bounds for series of length {len(series_)} "
                                f"at index: {idx}."
                            ),
                            logger,
                        )
                else:
                    if (
                        n.start > series_.time_index[-1]
                    ):  # format "value" and range index
                        raise_log(
                            ValueError(
                                f"`start` time `{n.start}` is larger than the last index `{series_.time_index[-1]}` "
                                f"for series at index: {idx}."
                            ),
                            logger,
                        )
                    start_is_value = True

            # `ConformalModel` with `start_format='value'` requires all series to have the same frequency
            if is_conformal and start_is_value:
                if series_freq is None:
                    series_freq = series_.freq

                if series_freq != series_.freq:
                    raise_log(
                        ValueError(
                            f"Found mismatching `series` time index frequencies `{series_freq}` and `{series_.freq}`. "
                            f"`start_format='value'` with `ConformalModel` is only supported if all series in "
                            f"`series` have the same frequency."
                        ),
                        logger=logger,
                    )

            # find valid start position relative to the series start time, otherwise raise an error
            start_idx, _ = _get_start_index(
                series_, idx, n.start, n.start_format, n.stride
            )

            # check that `overlap_end` and `start` are a valid combination
            overlap_end = n.overlap_end
            if (
                not overlap_end
                and start_idx + n.forecast_horizon + model.output_chunk_shift
                > len(series_)
            ):
                # verbose error messages
                if n.start_format == "position" or (
                    not isinstance(n.start, pd.Timestamp)
                    and series_._has_datetime_index
                ):
                    start_value_msg = (
                        f"`start` position `{n.start}` corresponding to time"
                    )
                else:
                    start_value_msg = "`start` time"
                start = series_._time_index[start_idx]
                raise_log(
                    ValueError(
                        f"{start_value_msg} `{start}` is too late in the series {idx} to make any predictions with "
                        f"`overlap_end` set to `False`."
                    ),
                    logger,
                )

    # duplication of ForecastingModel.predict() check for the optimized historical forecasts implementations
    if not model.supports_probabilistic_prediction and n.num_samples > 1:
        raise_log(
            ValueError("`num_samples > 1` is only supported for probabilistic models."),
            logger,
        )

    # check direct likelihood parameter prediction before fitting a model
    if n.predict_likelihood_parameters:
        if not model.supports_likelihood_parameter_prediction:
            raise_log(
                ValueError(
                    f"Model `{model.__class__.__name__}` does not support `predict_likelihood_parameters=True`. "
                    f"Either the model does not support likelihoods, or no `likelihood` was used at model "
                    f"creation."
                )
            )
        if n.num_samples != 1:
            raise_log(
                ValueError(
                    f"`predict_likelihood_parameters=True` is only supported for `num_samples=1`, "
                    f"received {n.num_samples}."
                ),
                logger,
            )

        if (
            model.output_chunk_length is not None
            and n.forecast_horizon > model.output_chunk_length
        ):
            raise_log(
                ValueError(
                    "`predict_likelihood_parameters=True` is only supported for `forecast_horizon` smaller than or "
                    "equal to model's `output_chunk_length`."
                ),
                logger,
            )

    if n.data_transformers is not None:
        # check the type
        if not isinstance(n.data_transformers, dict):
            raise_log(
                ValueError(
                    "`data_transformers` should either `None` or a dictionary.", logger
                )
            )
        # check the keys
        supported_keys = {"series", "past_covariates", "future_covariates"}
        incorrect_keys = set(n.data_transformers.keys()) - supported_keys
        if len(incorrect_keys) > 0:
            raise_log(
                ValueError(
                    f"The keys supported by `data_transformers` are {supported_keys}, received the following "
                    f"incorrect keys: {incorrect_keys}."
                ),
                logger,
            )

        # convert to Pipelines
        data_pipelines = _convert_data_transformers(
            data_transformers=n.data_transformers, copy=False
        )
        # extract pipelines containing at least one fittable element
        fittable_pipelines = [
            transf_ for transf_ in data_pipelines.values() if transf_.fittable
        ]
        # extract pipelines where all the fittable transformer are fitted globally
        global_fit_pipelines = [
            transf_ for transf_ in fittable_pipelines if transf_._global_fit
        ]

        if n.retrain:
            # if more than one series is passed and the pipelines are retrained, they cannot be global
            if n.show_warnings and len(series) > 1 and len(global_fit_pipelines) > 0:
                logger.warning(
                    "When `retrain=True` and multiple series are provided, the fittable `data_transformers` "
                    "are trained on each series independently (`global_fit=True` will be ignored)."
                )
        else:
            # must already be fitted without retraining
            not_fitted_pipelines = [
                name_
                for name_, transf_ in data_pipelines.items()
                if transf_.fittable and not transf_._fit_called
            ]
            if len(not_fitted_pipelines) > 0:
                raise_log(
                    ValueError(
                        "All the fittable entries in `data_transformers` must already be fitted when "
                        f"`retrain=False`, the following entries were not fitted: {', '.join(not_fitted_pipelines)}."
                    ),
                    logger,
                )
            # extract the number of fitted params in each pipeline (already fitted)
            fitted_params_pipelines = [
                max(
                    len(t._fitted_params)
                    for t in pipeline
                    if isinstance(t, FittableDataTransformer)
                )
                for pipeline in data_pipelines.values()
            ]

            if len(series) > 1:
                # if multiple series are passed and the pipelines are not all globally fitted, the number of series must
                # match the number of fitted params in the pipelines
                if len(global_fit_pipelines) != len(fittable_pipelines) and len(
                    series
                ) != max(fitted_params_pipelines):
                    raise_log(
                        ValueError(
                            f"When multiple series are provided, their number should match the number of "
                            f"`TimeSeries` used to fit the data transformers `n={max(fitted_params_pipelines)}` "
                            f"(only relevant for fittable transformers that use `global_fit=False`)."
                        ),
                        logger,
                    )
            else:
                # at least one pipeline was fitted on several series with `global_fit=False` but only
                # one series was passed
                if n.show_warnings and max(fitted_params_pipelines) > 1:
                    logger.warning(
                        "Provided only a single series, but at least one of the `data_transformers` "
                        "that use `global_fit=False` was fitted on multiple `TimeSeries`."
                    )

    if (
        n.sample_weight is not None
        and not isinstance(n.sample_weight, str)
        and model.supports_sample_weight
    ):
        sample_weight = series2seq(n.sample_weight)
        for idx, (series_, sample_weight_) in enumerate(zip(series, sample_weight)):
            is_valid = (
                sample_weight_.freq == series_.freq
                and sample_weight_.start_time() <= series_.start_time()
                and len(sample_weight_) >= len(series_)
            )
            if not is_valid:
                raise_log(
                    ValueError(
                        f"`sample_weight` at series index {idx} must contain at least all times "
                        f"of the corresponding target `series`."
                    ),
                    logger=logger,
                )

    Performs checks common to ForecastingModel and SKLearnModel backtest() methods

    Parameters
    ----------
    model
        The forecasting model.
    series
        Either series when called from ForecastingModel, or target_series if called from SKLearnModel
    kwargs
        Params specified by the caller of backtest(), they take precedence over the arguments' default values
    
darts/utils/historical_forecasts/utils.py