---
title: "Backtest Analysis"
description: "Learn how to analyze backtest results in the Research Environment."
canonical: https://www.quantconnect.com/docs/v2/research-environment/meta-analysis/backtest-analysis
markdown: https://www.quantconnect.com/docs/v2/research-environment/meta-analysis/backtest-analysis.md
language: python
source: https://github.com/QuantConnect/Documentation/tree/master/04%20Research%20Environment/10%20Meta%20Analysis/02%20Backtest%20Analysis
modified: 2026-10-28
index: https://www.quantconnect.com/llms.txt
---

# Backtest Analysis

[Documentation](https://www.quantconnect.com/docs/v2.md) > [Research Environment](https://www.quantconnect.com/docs/v2/research-environment.md) > Meta Analysis > Backtest Analysis

> Code examples are shown in Python. Append `?lang=csharp` to this URL for the C# version of this page.

## 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 `read_backtest` method with the project Id and backtest ID.

```python
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:

| Platform | Project Id | Backtest Id |
| --- | --- | --- |
| Cloud Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/cloud-platform/projects/getting-started.md#13-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/cloud-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
| Local Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/local-platform/projects/getting-started.md#14-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/local-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
| CLI | [Get Project Id](https://www.quantconnect.com/docs/v2/lean-cli/projects/project-management.md#07-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/lean-cli/backtesting/deployment.md#05-Get-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 `read_backtest` method returns a `Backtest` object, which have the following attributes:

*Members of `QuantConnect.Api.Backtest` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Api.Backtest&language=python*

## Plot Order Fills

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

1. Get the backtest orders.
   ```python
   orders = api.read_backtest_orders(project_id, backtest_id).orders
   ```
   The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:
   | Platform | Project Id | Backtest Id |
   | --- | --- | --- |
   | Cloud Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/cloud-platform/projects/getting-started.md#13-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/cloud-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | Local Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/local-platform/projects/getting-started.md#14-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/local-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | CLI | [Get Project Id](https://www.quantconnect.com/docs/v2/lean-cli/projects/project-management.md#07-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/lean-cli/backtesting/deployment.md#05-Get-Backtest-Id) |
   The `read_backtest_orders` method returns an `OrdersResponseWrapper` object. Its `length` property is the total number of orders in the backtest. Each call returns at most 100 orders. By default, you get the first 100 orders. To get the next ones, pass `start` and `end` arguments. Note that `end` - `start` must be 100 or less.
   ```python
   orders = api.read_backtest_orders(project_id, backtest_id, 100, 200).orders
   ```
   The `orders` property of the `OrdersResponseWrapper` is a list of `ApiOrderResponse` objects, which have the following properties:
   *Members of `QuantConnect.Orders.ApiOrderResponse` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Orders.ApiOrderResponse&language=python*
2. Organize the trade times and prices for each security into a dictionary.

   ```python
   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)
   ```
3. Get the price history of each security you traded.

   ```python
   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)
   ```
4. Create a candlestick plot for each security and annotate each plot with buy and sell markers.

   ```python
   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()
   ```
   ![Plot of AAPL price with buy/sell markers](https://cdn.quantconnect.com/i/tu/plot-backtest-trades-in-research-env-aapl.png)
   ![Plot of SPY price with buy/sell markers](https://cdn.quantconnect.com/i/tu/plot-backtest-trades-in-research-env-spy.png)
   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. Define the project Id, backtest Id, and read the "Strategy Equity", "Drawdown", and "Benchmark" charts.
   ```python
   from time import time

   project_id = 23034953
   backtest_id = 'ff616bb2cbccf70f61ea431278e57728'

   def read_chart(project_id, backtest_id, chart_name, start=0, end=int(time()), count=500):
       return api.read_backtest_chart(
           project_id, chart_name, start, end, count, backtest_id
       ).chart

   strategy_equity = read_chart(project_id, backtest_id, 'Strategy Equity')
   drawdown_chart = read_chart(project_id, backtest_id, 'Drawdown')
   benchmark_chart = read_chart(project_id, backtest_id, 'Benchmark')
   ```
   The following table provides links to documentation that explains how to get the project Id and backtest Id, depending on the platform you use:
   | Platform | Project Id | Backtest Id |
   | --- | --- | --- |
   | Cloud Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/cloud-platform/projects/getting-started.md#13-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/cloud-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | Local Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/local-platform/projects/getting-started.md#14-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/local-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | CLI | [Get Project Id](https://www.quantconnect.com/docs/v2/lean-cli/projects/project-management.md#07-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/lean-cli/backtesting/deployment.md#05-Get-Backtest-Id) |
2. Extract the series and create a `pandas.DataFrame`.
   ```python
   def to_series(chart, series_name, selector=lambda x: x.y):
       return pd.Series({v.time: selector(v) for v in chart.series[series_name].values})

   df = pd.DataFrame({
       "Equity": to_series(strategy_equity, 'Equity', selector=lambda x: x.close),
       "Return": to_series(strategy_equity, 'Return'),
       "Drawdown": to_series(drawdown_chart, 'Equity Drawdown'),
       "Benchmark": to_series(benchmark_chart, 'Benchmark')
   }).ffill()
   df.index = df.index.tz_localize('UTC').tz_convert('US/Eastern').tz_localize(None)
   ```
3. Plot the performance chart.
   ```python
   # Create subplots to plot series on same/different plots
   fig, ax = plt.subplots(3, 1, figsize=(12, 16), sharex=True, gridspec_kw={'height_ratios': [2, 1, 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 daily returns
   ax[1].plot(df.index, df["Return"], color="blue")
   ax[1].set_title("Daily Return")
   ax[1].set_ylabel("%")

   # Plot the drawdown on another plot
   ax[2].plot(df.index, df["Drawdown"], color="red")
   ax[2].set_title("Drawdown")
   ax[2].set_xlabel("Time")
   ax[2].set_ylabel("%");
   ```
   ![api-equity-curve](https://cdn.quantconnect.com/i/tu/api-result-plot.png)

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

| Chart | Series | Description |
| --- | --- | --- |
| Strategy Equity | Equity | Time series of the equity curve. This series may not update daily. |
| Strategy Equity | Return | Time series of daily returns. Use this series for daily updates. |
| Strategy Equity | Daily Performance | Time series of daily percentage change. This series has as many data points as "Equity". |
| Capacity | Strategy Capacity | Time series of [strategy capacity](https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/glossary.md#08-capacity) snapshots |
| Drawdown | Equity Drawdown | Time series of equity peak-to-trough value |
| Benchmark | Benchmark | Time series of the [benchmark](https://www.quantconnect.com/docs/v2/writing-algorithms/initialization.md#11-Set-Benchmark) closing price (SPY, by default) |
| Exposure | SecurityType - Long Ratio | Time series of the overall ratio of SecurityType long positions of the whole portfolio if any SecurityType is ever in the universe |
| Exposure | SecurityType - Short Ratio | Time series of the overall ratio of SecurityType short position of the whole portfolio if any SecurityType is ever in the universe |
| Assets Sales Volume | Each ticker is one series | A chart showing the proportion of total volume for each traded security. |
| Portfolio Turnover | Portfolio Turnover | A time series of the portfolio turnover rate. |
| Portfolio Margin | Each ticker is one series | A stacked area chart of the portfolio margin usage. For more information about this chart, see [Portfolio Margin Plots](https://www.quantconnect.com/docs/v2/cloud-platform/backtesting/results/portfolio-margin-plots.md). |
| Asset Plot | Price and Annotations | A time series of an asset's price with order event annotations. For more information about these charts, see [Asset Plots](https://www.quantconnect.com/docs/v2/cloud-platform/live-trading/results.md#05-Asset-Plots). |
| Custom Chart | Custom Series | Time series of a `Series` in a [custom chart](https://www.quantconnect.com/docs/v2/writing-algorithms/charting.md) |

## Plot Insights

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

1. Get the insights.
   ```python
   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:
   | Platform | Project Id | Backtest Id |
   | --- | --- | --- |
   | Cloud Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/cloud-platform/projects/getting-started.md#13-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/cloud-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | Local Platform | [Get Project Id](https://www.quantconnect.com/docs/v2/local-platform/projects/getting-started.md#14-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/local-platform/backtesting/getting-started.md#07-Get-Backtest-Id) |
   | CLI | [Get Project Id](https://www.quantconnect.com/docs/v2/lean-cli/projects/project-management.md#07-Get-Project-Id) | [Get Backtest Id](https://www.quantconnect.com/docs/v2/lean-cli/backtesting/deployment.md#05-Get-Backtest-Id) |
   The `read_backtest_insights` method returns an `InsightResponse` object, which have the following properties:
   *Members of `QuantConnect.Api.InsightResponse` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Api.InsightResponse&language=python*
2. Organize the insights into a DataFrame.
   ```python
   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
       ]
   )
   ```
3. Get the [price history](https://www.quantconnect.com/docs/v2/research-environment/datasets/key-concepts.md) of each security that has an insight.
   ```python
   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)
   ```
4. Plot the price and insights of each asset.
   ```python
   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()
   ```
   ![](https://cdn.quantconnect.com/i/tu/meta-analysis-plot-insights.png)

## Examples

### Example 1: Read Backtest Statistics

The following example reads the last completed backtest statistics in a jupyter notebook.

```python
# Instantiate QuantBook instance for researching.
qb = QuantBook()

# Get backtest list in the current project.
backtests = api.list_backtests(qb.project_id)
# Get the last completed backtest to study.
backtest_id = sorted(
    [x for x in backtests.backtests if x.progress == 1],
    key=lambda x: x.created,
    reverse=True
)[0].backtest_id
backtest = api.read_backtest(project_id, backtest_id)

# Obtain the backtest statistics.
print(backtest.statistics)
```
