Repository: microsoft/qlib
File: qlib/rl/contrib/backtest.py (Python)

Code before:
    if generate_report:
        report = _generate_report(decisions, [report_dict])
        if split == "stock":
            stock_id = orders.iloc[0].instrument
            report = {stock_id: report}
        else:
            day = orders.iloc[0].datetime
            report = {day: report}
        return records, report
    else:
        return records


def backtest(backtest_config: dict, parallel_mode: bool = False, with_simulator: bool = False) -> pd.DataFrame:
    order_df = read_order_file(backtest_config["order_file"])

    cash_limit = backtest_config["exchange"].pop("cash_limit")
    generate_report = backtest_config["exchange"].pop("generate_report")

    stock_pool = order_df["instrument"].unique().tolist()
    stock_pool.sort()

    stock_pool = stock_pool

    single = single_with_simulator if with_simulator else single_with_collect_data_loop
    if parallel_mode:
        mp_config = {"n_jobs": backtest_config["concurrency"], "verbose": 10, "backend": "multiprocessing"}
        torch.set_num_threads(1)  # https://github.com/pytorch/pytorch/issues/17199
        res = Parallel(**mp_config)(
            delayed(single)(
                backtest_config=backtest_config,
                orders=order_df[order_df["instrument"] == stock].copy(),
                split="stock",
                cash_limit=cash_limit,
                generate_report=generate_report,
            )
            for stock in stock_pool
        )
    else:
        res = [
            single(
                backtest_config=backtest_config,
                orders=order_df[order_df["instrument"] == stock].copy(),
                split="stock",
                cash_limit=cash_limit,
                generate_report=generate_report,
            )
            for stock in stock_pool
        ]

    output_path = Path(backtest_config["output_dir"])

Review comment (suggestion):
I don't think parallel_mode is a required parameter. 
Joblib will fall to single process when `n_jobs == 1`

Code after:
        Whether to generate reports.

    Returns
    -------
        If generate_report is True, return execution records and the generated report. Otherwise, return only records.
    """

    if split == "stock":
        stock_id = orders.iloc[0].instrument
        init_qlib(backtest_config["qlib"], part=stock_id)
    else:
        day = orders.iloc[0].datetime
        init_qlib(backtest_config["qlib"], part=day)

    trade_start_time = orders["datetime"].min()
    trade_end_time = orders["datetime"].max()
    stocks = orders.instrument.unique().tolist()

    strategy_config = {
        "class": "FileOrderStrategy",
        "module_path": "qlib.contrib.strategy.rule_strategy",
        "kwargs": {
            "file": orders,
            "trade_range": TradeRangeByTime(
                pd.Timestamp(backtest_config["start_time"]).time(),
                pd.Timestamp(backtest_config["end_time"]).time(),
            ),
        },
    }

    executor_config = _get_multi_level_executor_config(
        strategy_config=backtest_config["strategies"],
        cash_limit=cash_limit,
        generate_report=generate_report,
    )

    exchange_config = copy.deepcopy(backtest_config["exchange"])
    exchange_config.update(
        {
            "codes": stocks,
            "freq": "1min",
        }
    )

    strategy, executor = get_strategy_executor(
        start_time=pd.Timestamp(trade_start_time),
        end_time=pd.Timestamp(trade_end_time) + pd.DateOffset(1),
        strategy=strategy_config,
        executor=executor_config,
        benchmark=None,
        account=cash_limit if cash_limit is not None else int(1e12),