Esempio n. 1
0
def generate_backtest_stats(btdata: Dict[str, DataFrame],
                            all_results: Dict[str, Dict[str, Union[DataFrame,
                                                                   Dict]]],
                            min_date: Arrow,
                            max_date: Arrow) -> Dict[str, Any]:
    """
    :param btdata: Backtest data
    :param all_results: backtest result - dictionary in the form:
                     { Strategy: {'results: results, 'config: config}}.
    :param min_date: Backtest start date
    :param max_date: Backtest end date
    :return:
    Dictionary containing results per strategy and a stratgy summary.
    """
    result: Dict[str, Any] = {'strategy': {}}
    market_change = calculate_market_change(btdata, 'close')

    for strategy, content in all_results.items():
        results: Dict[str, DataFrame] = content['results']
        if not isinstance(results, DataFrame):
            continue
        config = content['config']
        max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
        starting_balance = config['dry_run_wallet']
        stake_currency = config['stake_currency']

        pair_results = generate_pair_metrics(btdata,
                                             stake_currency=stake_currency,
                                             starting_balance=starting_balance,
                                             results=results,
                                             skip_nan=False)
        sell_reason_stats = generate_sell_reason_stats(
            max_open_trades=max_open_trades, results=results)
        left_open_results = generate_pair_metrics(
            btdata,
            stake_currency=stake_currency,
            starting_balance=starting_balance,
            results=results.loc[results['is_open']],
            skip_nan=True)
        daily_stats = generate_daily_stats(results)
        best_pair = max(
            [pair for pair in pair_results if pair['key'] != 'TOTAL'],
            key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
        worst_pair = min(
            [pair for pair in pair_results if pair['key'] != 'TOTAL'],
            key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
        results['open_timestamp'] = results['open_date'].astype(int64) // 1e6
        results['close_timestamp'] = results['close_date'].astype(int64) // 1e6

        backtest_days = (max_date - min_date).days
        strat_stats = {
            'trades':
            results.to_dict(orient='records'),
            'locks': [lock.to_json() for lock in content['locks']],
            'best_pair':
            best_pair,
            'worst_pair':
            worst_pair,
            'results_per_pair':
            pair_results,
            'sell_reason_summary':
            sell_reason_stats,
            'left_open_trades':
            left_open_results,
            'total_trades':
            len(results),
            'total_volume':
            float(results['stake_amount'].sum()),
            'avg_stake_amount':
            results['stake_amount'].mean() if len(results) > 0 else 0,
            'profit_mean':
            results['profit_ratio'].mean() if len(results) > 0 else 0,
            'profit_total':
            results['profit_abs'].sum() / starting_balance,
            'profit_total_abs':
            results['profit_abs'].sum(),
            'backtest_start':
            min_date.datetime,
            'backtest_start_ts':
            min_date.int_timestamp * 1000,
            'backtest_end':
            max_date.datetime,
            'backtest_end_ts':
            max_date.int_timestamp * 1000,
            'backtest_days':
            backtest_days,
            'backtest_run_start_ts':
            content['backtest_start_time'],
            'backtest_run_end_ts':
            content['backtest_end_time'],
            'trades_per_day':
            round(len(results) / backtest_days, 2) if backtest_days > 0 else 0,
            'market_change':
            market_change,
            'pairlist':
            list(btdata.keys()),
            'stake_amount':
            config['stake_amount'],
            'stake_currency':
            config['stake_currency'],
            'stake_currency_decimals':
            decimals_per_coin(config['stake_currency']),
            'starting_balance':
            starting_balance,
            'dry_run_wallet':
            starting_balance,
            'final_balance':
            content['final_balance'],
            'max_open_trades':
            max_open_trades,
            'max_open_trades_setting':
            (config['max_open_trades']
             if config['max_open_trades'] != float('inf') else -1),
            'timeframe':
            config['timeframe'],
            'timerange':
            config.get('timerange', ''),
            'enable_protections':
            config.get('enable_protections', False),
            'strategy_name':
            strategy,
            # Parameters relevant for backtesting
            'stoploss':
            config['stoploss'],
            'trailing_stop':
            config.get('trailing_stop', False),
            'trailing_stop_positive':
            config.get('trailing_stop_positive'),
            'trailing_stop_positive_offset':
            config.get('trailing_stop_positive_offset', 0.0),
            'trailing_only_offset_is_reached':
            config.get('trailing_only_offset_is_reached', False),
            'use_custom_stoploss':
            config.get('use_custom_stoploss', False),
            'minimal_roi':
            config['minimal_roi'],
            'use_sell_signal':
            config['ask_strategy']['use_sell_signal'],
            'sell_profit_only':
            config['ask_strategy']['sell_profit_only'],
            'sell_profit_offset':
            config['ask_strategy']['sell_profit_offset'],
            'ignore_roi_if_buy_signal':
            config['ask_strategy']['ignore_roi_if_buy_signal'],
            **daily_stats,
        }
        result['strategy'][strategy] = strat_stats

        try:
            max_drawdown, _, _, _, _ = calculate_max_drawdown(
                results, value_col='profit_ratio')
            drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
                results, value_col='profit_abs')
            strat_stats.update({
                'max_drawdown':
                max_drawdown,
                'max_drawdown_abs':
                drawdown_abs,
                'drawdown_start':
                drawdown_start,
                'drawdown_start_ts':
                drawdown_start.timestamp() * 1000,
                'drawdown_end':
                drawdown_end,
                'drawdown_end_ts':
                drawdown_end.timestamp() * 1000,
                'max_drawdown_low':
                low_val,
                'max_drawdown_high':
                high_val,
            })

            csum_min, csum_max = calculate_csum(results, starting_balance)
            strat_stats.update({'csum_min': csum_min, 'csum_max': csum_max})

        except ValueError:
            strat_stats.update({
                'max_drawdown':
                0.0,
                'max_drawdown_abs':
                0.0,
                'max_drawdown_low':
                0.0,
                'max_drawdown_high':
                0.0,
                'drawdown_start':
                datetime(1970, 1, 1, tzinfo=timezone.utc),
                'drawdown_start_ts':
                0,
                'drawdown_end':
                datetime(1970, 1, 1, tzinfo=timezone.utc),
                'drawdown_end_ts':
                0,
                'csum_min':
                0,
                'csum_max':
                0
            })

    strategy_results = generate_strategy_metrics(all_results=all_results)

    result['strategy_comparison'] = strategy_results

    return result
def generate_strategy_stats(pairlist: List[str], strategy: str,
                            content: Dict[str, Any], min_date: datetime,
                            max_date: datetime,
                            market_change: float) -> Dict[str, Any]:
    """
    :param pairlist: List of pairs to backtest
    :param strategy: Strategy name
    :param content: Backtest result data in the format:
                    {'results: results, 'config: config}}.
    :param min_date: Backtest start date
    :param max_date: Backtest end date
    :param market_change: float indicating the market change
    :return: Dictionary containing results per strategy and a strategy summary.
    """
    results: Dict[str, DataFrame] = content['results']
    if not isinstance(results, DataFrame):
        return {}
    config = content['config']
    max_open_trades = min(config['max_open_trades'], len(pairlist))
    starting_balance = config['dry_run_wallet']
    stake_currency = config['stake_currency']

    pair_results = generate_pair_metrics(pairlist,
                                         stake_currency=stake_currency,
                                         starting_balance=starting_balance,
                                         results=results,
                                         skip_nan=False)

    buy_tag_results = generate_tag_metrics("buy_tag",
                                           starting_balance=starting_balance,
                                           results=results,
                                           skip_nan=False)

    sell_reason_stats = generate_sell_reason_stats(
        max_open_trades=max_open_trades, results=results)
    left_open_results = generate_pair_metrics(
        pairlist,
        stake_currency=stake_currency,
        starting_balance=starting_balance,
        results=results.loc[results['is_open']],
        skip_nan=True)
    daily_stats = generate_daily_stats(results)
    trade_stats = generate_trading_stats(results)
    best_pair = max(
        [pair for pair in pair_results if pair['key'] != 'TOTAL'],
        key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
    worst_pair = min(
        [pair for pair in pair_results if pair['key'] != 'TOTAL'],
        key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
    if not results.empty:
        results['open_timestamp'] = results['open_date'].view(int64) // 1e6
        results['close_timestamp'] = results['close_date'].view(int64) // 1e6

    backtest_days = (max_date - min_date).days or 1
    strat_stats = {
        'trades':
        results.to_dict(orient='records'),
        'locks': [lock.to_json() for lock in content['locks']],
        'best_pair':
        best_pair,
        'worst_pair':
        worst_pair,
        'results_per_pair':
        pair_results,
        'results_per_buy_tag':
        buy_tag_results,
        'sell_reason_summary':
        sell_reason_stats,
        'left_open_trades':
        left_open_results,
        # 'days_breakdown_stats': days_breakdown_stats,
        'total_trades':
        len(results),
        'total_volume':
        float(results['stake_amount'].sum()),
        'avg_stake_amount':
        results['stake_amount'].mean() if len(results) > 0 else 0,
        'profit_mean':
        results['profit_ratio'].mean() if len(results) > 0 else 0,
        'profit_median':
        results['profit_ratio'].median() if len(results) > 0 else 0,
        'profit_total':
        results['profit_abs'].sum() / starting_balance,
        'profit_total_abs':
        results['profit_abs'].sum(),
        'backtest_start':
        min_date.strftime(DATETIME_PRINT_FORMAT),
        'backtest_start_ts':
        int(min_date.timestamp() * 1000),
        'backtest_end':
        max_date.strftime(DATETIME_PRINT_FORMAT),
        'backtest_end_ts':
        int(max_date.timestamp() * 1000),
        'backtest_days':
        backtest_days,
        'backtest_run_start_ts':
        content['backtest_start_time'],
        'backtest_run_end_ts':
        content['backtest_end_time'],
        'trades_per_day':
        round(len(results) / backtest_days, 2),
        'market_change':
        market_change,
        'pairlist':
        pairlist,
        'stake_amount':
        config['stake_amount'],
        'stake_currency':
        config['stake_currency'],
        'stake_currency_decimals':
        decimals_per_coin(config['stake_currency']),
        'starting_balance':
        starting_balance,
        'dry_run_wallet':
        starting_balance,
        'final_balance':
        content['final_balance'],
        'rejected_signals':
        content['rejected_signals'],
        'max_open_trades':
        max_open_trades,
        'max_open_trades_setting':
        (config['max_open_trades']
         if config['max_open_trades'] != float('inf') else -1),
        'timeframe':
        config['timeframe'],
        'timeframe_detail':
        config.get('timeframe_detail', ''),
        'timerange':
        config.get('timerange', ''),
        'enable_protections':
        config.get('enable_protections', False),
        'strategy_name':
        strategy,
        # Parameters relevant for backtesting
        'stoploss':
        config['stoploss'],
        'trailing_stop':
        config.get('trailing_stop', False),
        'trailing_stop_positive':
        config.get('trailing_stop_positive'),
        'trailing_stop_positive_offset':
        config.get('trailing_stop_positive_offset', 0.0),
        'trailing_only_offset_is_reached':
        config.get('trailing_only_offset_is_reached', False),
        'use_custom_stoploss':
        config.get('use_custom_stoploss', False),
        'minimal_roi':
        config['minimal_roi'],
        'use_sell_signal':
        config['use_sell_signal'],
        'sell_profit_only':
        config['sell_profit_only'],
        'sell_profit_offset':
        config['sell_profit_offset'],
        'ignore_roi_if_buy_signal':
        config['ignore_roi_if_buy_signal'],
        **daily_stats,
        **trade_stats
    }

    try:
        max_drawdown_legacy, _, _, _, _, _ = calculate_max_drawdown(
            results, value_col='profit_ratio')
        (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val,
         max_drawdown) = calculate_max_drawdown(
             results,
             value_col='profit_abs',
             starting_balance=starting_balance)
        strat_stats.update({
            'max_drawdown':
            max_drawdown_legacy,  # Deprecated - do not use
            'max_drawdown_account':
            max_drawdown,
            'max_drawdown_abs':
            drawdown_abs,
            'drawdown_start':
            drawdown_start.strftime(DATETIME_PRINT_FORMAT),
            'drawdown_start_ts':
            drawdown_start.timestamp() * 1000,
            'drawdown_end':
            drawdown_end.strftime(DATETIME_PRINT_FORMAT),
            'drawdown_end_ts':
            drawdown_end.timestamp() * 1000,
            'max_drawdown_low':
            low_val,
            'max_drawdown_high':
            high_val,
        })

        csum_min, csum_max = calculate_csum(results, starting_balance)
        strat_stats.update({'csum_min': csum_min, 'csum_max': csum_max})

    except ValueError:
        strat_stats.update({
            'max_drawdown':
            0.0,
            'max_drawdown_account':
            0.0,
            'max_drawdown_abs':
            0.0,
            'max_drawdown_low':
            0.0,
            'max_drawdown_high':
            0.0,
            'drawdown_start':
            datetime(1970, 1, 1, tzinfo=timezone.utc),
            'drawdown_start_ts':
            0,
            'drawdown_end':
            datetime(1970, 1, 1, tzinfo=timezone.utc),
            'drawdown_end_ts':
            0,
            'csum_min':
            0,
            'csum_max':
            0
        })

    return strat_stats