Meta Analysis

Backtest Analysis

Introduction

Load your backtest results into the Research Environment to analyze trades and easily compare them against the raw backtesting data. Compare backtests from different projects to find uncorrelated strategies to combine for better performance.

Loading your backtest trades allows you to plot fills against detailed data, or locate the source of profits. Similarly you can search for periods of high churn to reduce turnover and trading fees.

Read Backtest Results

To get the results of a backtest, call the ReadBacktestread_backtest method with the project Id and backtest ID.

#load "../Initialize.csx"
#load "../QuantConnect.csx"

using QuantConnect;
using QuantConnect.Api;

var backtest = api.ReadBacktest(projectId, backtestId);
backtest = api.read_backtest(project_id, backtest_id)

The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:

PlatformProject IdBacktest Id
Cloud PlatformGet Project IdGet Backtest Id
Local PlatformGet Project IdGet Backtest Id
CLIGet Project IdGet Backtest Id

Note that this method returns a snapshot of the backtest at the current moment. If the backtest is still executing, the result won't include all of the backtest data.

The ReadBacktestread_backtest method returns a Backtest object, which have the following attributes:

Plot Order Fills

Follow these steps to plot the daily order fills of a backtest:

  1. Get the backtest orders.
  2. orders = api.read_backtest_orders(project_id, backtest_id)

    The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:

    PlatformProject IdBacktest Id
    Cloud PlatformGet Project IdGet Backtest Id
    Local PlatformGet Project IdGet Backtest Id
    CLIGet Project IdGet Backtest Id

    The ReadBacktestOrdersread_backtest_orders method returns a list of Order objects, which have the following properties:

  3. Organize the trade times and prices for each security into a dictionary.
    class OrderData:
        def __init__(self):
            self.buy_fill_times = []
            self.buy_fill_prices = []
            self.sell_fill_times = []
            self.sell_fill_prices = []
    
    order_data_by_symbol = {}
    for order in [x.order for x in orders]:
        if order.symbol not in order_data_by_symbol:
            order_data_by_symbol[order.symbol] = OrderData()
        order_data = order_data_by_symbol[order.symbol]
        is_buy = order.quantity > 0
        (order_data.buy_fill_times if is_buy else order_data.sell_fill_times).append(order.last_fill_time.date())
        (order_data.buy_fill_prices if is_buy else order_data.sell_fill_prices).append(order.price)
  4. Get the price history of each security you traded.
    qb = QuantBook()
    start_date = datetime.max.date()
    end_date = datetime.min.date()
    for symbol, order_data in order_data_by_symbol.items():
        if order_data.buy_fill_times:
            start_date = min(start_date, min(order_data.buy_fill_times))
            end_date = max(end_date, max(order_data.buy_fill_times))
        if order_data.sell_fill_times:
            start_date = min(start_date, min(order_data.sell_fill_times))
            end_date = max(end_date, max(order_data.sell_fill_times))
    start_date -= timedelta(days=3)
    all_history = qb.history(list(order_data_by_symbol.keys()), start_date, end_date, Resolution.DAILY)
  5. Create a candlestick plot for each security and annotate each plot with buy and sell markers.
    import plotly.express as px
    import plotly.graph_objects as go
    
    for symbol, order_data in order_data_by_symbol.items():
        history = all_history.loc[symbol]
    
        # Plot security price candlesticks
        candlestick = go.Candlestick(x=history.index,
                                    open=history['open'],
                                    high=history['high'],
                                    low=history['low'],
                                    close=history['close'],
                                    name='Price')
        layout = go.Layout(title=go.layout.Title(text=f'{symbol.value} Trades'),
                        xaxis_title='Date',
                        yaxis_title='Price',
                        xaxis_rangeslider_visible=False,
                        height=600)
        fig = go.Figure(data=[candlestick], layout=layout)
    
        # Plot buys
        fig.add_trace(go.Scatter(
            x=order_data.buy_fill_times,
            y=order_data.buy_fill_prices,
            marker=go.scatter.Marker(color='aqua', symbol='triangle-up', size=10),
            mode='markers',
            name='Buys',
        ))
    
        # Plot sells
        fig.add_trace(go.Scatter(
            x=order_data.sell_fill_times,
            y=order_data.sell_fill_prices,
            marker=go.scatter.Marker(color='indigo', symbol='triangle-down', size=10),
            mode='markers',
            name='Sells',
        ))
    
    fig.show()
  6. Plot of AAPL price with buy/sell markers Plot of SPY price with buy/sell markers

    Note: The preceding plots only show the last fill of each trade. If your trade has partial fills, the plots only display the last fill.

Plot Metadata

Follow these steps to plot the equity curve, benchmark, and drawdown of a backtest:

  1. Get the backtest instance.
  2. backtest = api.read_backtest(project_id, backtest_id)

    The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:

    PlatformProject IdBacktest Id
    Cloud PlatformGet Project IdGet Backtest Id
    Local PlatformGet Project IdGet Backtest Id
    CLIGet Project IdGet Backtest Id
  3. Get the "Strategy Equity", "Drawdown", and "Benchmark" Chart objects.
  4. equity_chart = backtest.charts["Strategy Equity"]
    drawdown_chart = backtest.charts["Drawdown"]
    benchmark_chart = backtest.charts["Benchmark"]
  5. Get the "Equity", "Equity Drawdown", and "Benchmark" Series from the preceding charts.
  6. equity = equity_chart.series["Equity"].values
    drawdown = drawdown_chart.series["Equity Drawdown"].values
    benchmark = benchmark_chart.series["Benchmark"].values
  7. Create a pandas.DataFrame from the series values.
  8. df = pd.DataFrame({
        "Equity": pd.Series({value.TIME: value.CLOSE for value in equity}),
        "Drawdown": pd.Series({value.TIME: value.Y for value in drawdown}),
        "Benchmark": pd.Series({value.TIME: value.Y for value in benchmark})
    }).ffill()
  9. Plot the performance chart.
  10. # Create subplots to plot series on same/different plots
    fig, ax = plt.subplots(2, 1, figsize=(12, 12), sharex=True, gridspec_kw={'height_ratios': [2, 1]})
    
    # Plot the equity curve
    ax[0].plot(df.index, df["Equity"])
    ax[0].set_title("Strategy Equity Curve")
    ax[0].set_ylabel("Portfolio Value ($)")
    
    # Plot the benchmark on the same plot, scale by using another y-axis
    ax2 = ax[0].twinx()
    ax2.plot(df.index, df["Benchmark"], color="grey")
    ax2.set_ylabel("Benchmark Price ($)", color="grey")
    
    # Plot the drawdown on another plot
    ax[1].plot(df.index, df["Drawdown"], color="red")
    ax[1].set_title("Drawdown")
    ax[1].set_xlabel("Time")
    ax[1].set_ylabel("%")
    api-equity-curve

The following table shows all the chart series you can plot:

ChartSeriesDescription
Strategy EquityEquityTime series of the equity curve
Daily PerformanceTime series of daily percentage change
CapacityStrategy CapacityTime series of strategy capacity snapshots
DrawdownEquity DrawdownTime series of equity peak-to-trough value
BenchmarkBenchmarkTime series of the benchmark closing price (SPY, by default)
ExposureSecurityType - Long RatioTime series of the overall ratio of SecurityType long positions of the whole portfolio if any SecurityType is ever in the universe
SecurityType - Short RatioTime series of the overall ratio of SecurityType short position of the whole portfolio if any SecurityType is ever in the universe
Custom ChartCustom SeriesTime series of a Series in a custom chart

Plot Insights

Follow these steps to display the insights of each asset in a backtest:

  1. Get the insights.
  2. insight_response = api.read_backtest_insights(project_id, backtest_id)

    The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:

    PlatformProject IdBacktest Id
    Cloud PlatformGet Project IdGet Backtest Id
    Local PlatformGet Project IdGet Backtest Id
    CLIGet Project IdGet Backtest Id

    The read_backtest_insights method returns an InsightResponse object, which have the following properties:

  3. Organize the insights into a DataFrame.
  4. import pytz
    
    def _eastern_time(unix_timestamp):
        return unix_timestamp.replace(tzinfo=pytz.utc)\
            .astimezone(pytz.timezone('US/Eastern')).replace(tzinfo=None)
    
    insight_df = pd.DataFrame(
        [
            {
                'Symbol': i.symbol,
                'Direction': i.direction,
                'Generated Time': _eastern_time(i.generated_time_utc),
                'Close Time': _eastern_time(i.close_time_utc),
                'Weight': i.weight
            }
            for i in insight_response.insights
        ]
    )
  5. Get the price history of each security that has an insight.
  6. symbols = list(insight_df['Symbol'].unique())
    qb = QuantBook()
    history = qb.history(
        symbols, insight_df['Generated Time'].min()-timedelta(1), 
        insight_df['Close Time'].max(), Resolution.DAILY
    )['close'].unstack(0)
  7. Plot the price and insights of each asset.
  8. colors = ['yellow', 'green', 'red']
    fig, axs = plt.subplots(len(symbols), 1, sharex=True)
    for i, symbol in enumerate(symbols):
        ax = axs[i]
        history[symbol].plot(ax=ax)
        for _, insight in insight_df[insight_df['Symbol'] == symbol].iterrows():
            ax.axvspan(
                insight['Generated Time'], insight['Close Time'], 
                color=colors[insight['Direction']], alpha=0.3
            )
        ax.set_title(f'Insights for {symbol.value}')
        ax.set_xlabel('Date')
        ax.set_ylabel('Price')
    plt.tight_layout()
    plt.show()

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: