Source code for tradeexecutor.analysis.multi_asset_benchmark

"""Calculate portfolio benchmark tables for multiple assets side-by-side.

"""

import pandas as pd

from tradeexecutor.analysis.advanced_metrics import AdvancedMetricsMode, calculate_advanced_metrics
from tradeexecutor.state.state import State
from tradeexecutor.state.types import USDollarAmount
from tradeexecutor.analysis.curve import DEFAULT_BENCHMARK_COLOURS, CurveType
from tradeexecutor.visual.equity_curve import calculate_equity_curve, calculate_returns, resample_returns

from tradeexecutor.state.identifier import TradingPairIdentifier
from tradeexecutor.strategy.trading_strategy_universe import TradingStrategyUniverse, translate_trading_pair

from tradingstrategy.types import TokenSymbol


def _find_benchmark_pair(strategy_universe: TradingStrategyUniverse, token_symbol: TokenSymbol) -> TradingPairIdentifier | None:
    """Try to find the price series for our comparison asset in the trading universe."""
    for dex_pair in strategy_universe.data_universe.pairs.iterate_pairs():
        pair = translate_trading_pair(dex_pair)
        if pair.base.token_symbol == token_symbol:
            return pair

    return None


[docs]def get_benchmark_data( strategy_universe: TradingStrategyUniverse, max_count=2, interesting_assets=("BTC", "WBTC", "ETH", "WETH", "WMATIC", "MATIC"), cumulative_with_initial_cash: USDollarAmount =0.0, asset_colours=DEFAULT_BENCHMARK_COLOURS, ) -> pd.DataFrame: """Get returns series of different benchmark index assets from the universe. - Assets are: BTC, ETH, MATIC To be used with :py:func:`compare_multiple_portfolios` and :py:func:`tradeexecutor.visual.benchmark.visualise_equity_curve_benchmark`. Example: .. code-block:: python from tradeexecutor.analysis.multi_asset_benchmark import get_benchmark_data from tradeexecutor.visual.benchmark import visualise_equity_curve_benchmark benchmark_indexes = get_benchmark_data( strategy_universe, cumulative_with_initial_cash=state.portfolio.get_initial_cash() ) fig = visualise_equity_curve_benchmark( name=state.name, portfolio_statistics=state.stats.portfolio, all_cash=state.portfolio.get_initial_cash(), benchmark_indexes=benchmark_indexes, height=800, log_y=False, ) fig.show() :param max_count: Return this many benchmark series :param interesting_assets: Choose to benchmark from these. We also check for wrapped token symbol varients. :param cumulative_with_initial_cash: Get cumulative returns instead of daily returns. Set to the cumulative initial cash value. :return: DataFrame with returns series for each asset. Each series has colour and name metadata added to the `series.attr`. """ benchmark_assets = {} # Get the trading pair ids for the assets we want to compare for asset in interesting_assets: pair = _find_benchmark_pair(strategy_universe, asset) if not pair: continue unwrapped_name = asset[1:] if asset.startswith("W") else asset benchmark_assets[unwrapped_name] = pair if len(benchmark_assets) >= max_count: break df = pd.DataFrame() for name, pair in benchmark_assets.items(): price_series = strategy_universe.data_universe.candles.get_candles_by_pair(pair.internal_id)["close"] assert len(price_series.dropna()) != 0, f"Failed to read benchmark price series for {name}: {pair}" if isinstance(price_series.index, pd.MultiIndex): index_fixed_series = pd.Series(data=price_series.values, index=price_series.index.get_level_values(1)) else: index_fixed_series = price_series daily_returns = resample_returns(index_fixed_series.pct_change(), freq="D") if cumulative_with_initial_cash: cumulative_returns = (1 + daily_returns).cumprod() df[name] = cumulative_returns * cumulative_with_initial_cash df[name].attrs["returns_series_type"] = "cumulative_returns" df[name].attrs["curve"] = CurveType.equity else: df[name] = daily_returns df[name].attrs["returns_series_type"] = "daily_returns" df[name].attrs["period"] = "D" df[name].attrs["curve"] = CurveType.returns df[name].attrs["colour"] = asset_colours.get(name) df[name].attrs["name"] = name return df
[docs]def compare_multiple_portfolios( portfolios: pd.DataFrame, indexes: pd.DataFrame | None = None, mode: AdvancedMetricsMode=AdvancedMetricsMode.basic, periods_per_year=365, display=False, ) -> pd.DataFrame: """Compare multiple portfolios. - Assets against each other: BTC vs. ETH - Strategy against index: Strategy vs. BTC - Multiple assets in the same table: Strategt vs. BTC vs. ETH :param portfolios: A DataFrame of different daily series of actively trading portfolios. See :py:func:`tradeexecutor.visual.equity_curve.calculate_returns`. Each portfolio must have the returns series for the same time period, time periods are not matched. :param indexes: A DataFrame of different daily returns passive buy and hold indexes. :return: QuantStats comparison of all different returns. """ result_table = pd.DataFrame() assert len(portfolios.columns) + len(indexes.columns) >= 1, f"Need at least one portfolios to benchmark: {portfolios.columns}, {indexes.columns}" for name, portfolio_series in portfolios.items(): metrics = calculate_advanced_metrics( portfolio_series, mode=mode, periods_per_year=periods_per_year, convert_to_daily=False, display=display, ) assert "Strategy" in metrics.columns, f"We got {metrics.columns}" result_table[name] = metrics["Strategy"] last_series = portfolio_series for name, index_series in indexes.items(): metrics = calculate_advanced_metrics( last_series, benchmark=index_series, mode=mode, periods_per_year=periods_per_year, convert_to_daily=False, display=display, ) result_table[name] = metrics["Benchmark"] return result_table
[docs]def compare_strategy_backtest_to_multiple_assets( state: State, strategy_universe: TradingStrategyUniverse, display=False, ) -> pd.DataFrame: """Backtest comparison of strategy against buy and hold assets. :return: DataFrame with QuantStats results. One column for strategy and for each benchmark asset we have loaded in the strategy universe. """ # Get daily returns equity = calculate_equity_curve(state) returns = calculate_returns(equity) daily_returns = resample_returns(returns, "D") benchmarks = get_benchmark_data( strategy_universe, ) portfolios = pd.DataFrame( {"Strategy": daily_returns} ) return compare_multiple_portfolios( portfolios=portfolios, indexes=benchmarks, display=display, )