Backtrader Plot: Trading Strategy Visualization with Python
Backtrader plot is the built-in visualization engine that turns your backtest results into charts. You feed it data through Cerebro, run your strategy, and call cerebro.plot() — out comes a full set of matplotlib figures with price action, indicators, portfolio value, and trade markers. Charts help inspect calculation and execution timing, but they do not establish strategy profitability.
It runs on matplotlib under the hood. The enabled objects and plot settings determine which figures and panels appear. A chart is not a complete order audit log.
How the Plotting Feature Works
The plotter is automatic by design. It visualizes three things straight out of the box:
- The Data: Every data feed you load into Cerebro (like your stock's OHLC prices).
- The Indicators: Any indicators you add within your strategy (like moving averages or RSI).
- The Observers: Key performance trackers, like your running cash balance and every trade you made.
The plotter draws enabled feeds, indicators and observers with plot support. Hidden objects, ordinary Python variables and arbitrary strategy calculations do not automatically become plotted lines. See the plotting reference.
Using it is straightforward. After you run your backtest with cerebro.run(), you call:
cerebro.plot()
Important: Call cerebro.plot() after cerebro.run(). Your trading logic has to finish processing first — otherwise there's nothing to plot. Install a Matplotlib release compatible with your Python and Backtrader environment, and verify the active display backend.
How to Create Your First Backtrader Plot
Getting your first chart is satisfying and simple. Gather your ingredients (data and strategy), mix them in Cerebro, and let it cook.
The following structure requires a real file in Backtrader's expected CSV format at your-data.txt. Its strategy calculates an SMA but places no trades, so it should not produce filled-trade markers by itself:
import backtrader as bt
class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data)
data = bt.feeds.BacktraderCSVData(dataname='your-data.txt')
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(MyStrategy)
cerebro.run()
cerebro.plot()
When you run this, Backtrader generates a plot automatically. Your main chart shows:
- The price data from your file.
- The simple moving average line.
With the default stdstats=True, Backtrader adds standard observers. Their placement and marks differ:
| Observer Name | What It Shows You |
|---|---|
| Broker | Plots cash and portfolio value lines. |
| Trades | Plots completed-trade P/L markers according to observer settings. |
| BuySell | Marks executed buys and sells; markers are not every submitted or rejected order. |
Inspect plotted values against the underlying broker and trade records. For more on automated strategies, check out our guide to TradingView algorithms.
Getting to Know Backtrader's Built-in Trackers (And How to Tweak Them)
Backtrader automatically adds three trackers to your charts when you run a backtest. They work like a built-in dashboard.
Here's what each one does:
- The Account Tracker: Watches your money. It shows your available cash and total portfolio value (cash plus open positions).
- The Trade Tracker: Focuses on completed trades. A "completed trade" means you opened a position and closed it entirely. Once closed, this tracker shows the final profit or loss for that trade.
- The Buy/Sell Marker: Marks executed buys and sells. Marker offsets and per-bar aggregation mean their displayed location is not necessarily the exact execution price.
These are great starting points. But you can turn them off if they clutter your custom chart.
Two ways to disable them:
- When you create the engine:
cerebro = bt.Cerebro(stdstats=False)
- When you run the backtest:
cerebro.run(stdstats=False)
Setting stdstats=False tells Backtrader you'll handle the dashboard yourself. It gives you full control over how clean or detailed your charts look.
Making Your Indicators Look Just Right
Customizing Backtrader charts works like customizing a dashboard. You decide how each indicator looks and where it sits — through the plotinfo dictionary.
Main settings you'll use:
| Setting | What It Does |
|---|---|
plot | On/off switch. True shows the indicator, False hides it. |
subplot | Decides if the indicator shares the main chart or gets its own space below. |
plotname | Gives your indicator a readable name on the chart instead of a technical class name. |
plotabove | When using a subplot, controls whether it appears above or below the main price data. |
Two ways to apply these:
Method 1: Set it as you create the indicator.
sma = bt.indicators.SimpleMovingAverage(self.data, period=15, plotname='My 15-Day SMA')
Method 2: Adjust settings after creation.
sma.plotinfo.plotname = 'My 15-Day SMA'
Both methods configure the same display property. Keep indicator instances available if later code needs to change their settings.
Making Your Trading Charts Clearer: Customizing Lines in Backtrader
Ever looked at a chart and wished you could make one line stand out, or hide another to reduce clutter? In Backtrader, the plotlines dictionary gives you precise control over each line's look.
Think of it as giving individual instructions to each line on your chart. You control its color, style, and special Backtrader behaviors (those start with an underscore, like _name or _method).
Useful options:
- _plotskip: Set to
Trueif you want a line calculated but not shown. Great for background calculations. - _plotvalue & _plotvaluetag: Control how the latest value displays.
_plotvalueshows in the legend,_plotvaluetagputs a tag on the right side of the chart. - _name: Rename a line for a clearer chart label.
- _skipnan: If your data has gaps (NaN), setting this to
Trueskips over them. - _samecolor: Forces a line to match the color of the line plotted just before it.
Changing How a Line is Drawn: The _method Trick
One of the coolest options is _method. It tells Backtrader how to draw the data. Default is a line, but you can change it.
For MACD, the histogram works better as bars:
plotlines = dict(
histo=dict(_method='bar', alpha=0.50, width=1.0)
)
Setting _method='bar' gives you a clean bar chart for the histogram. alpha controls transparency, width controls bar size. Use a callable drawing method supported by Matplotlib's axes and pass arguments accepted by that method. _method='tick' is not a documented substitute for drawing price ticks.
Making It Pretty: Colors, Markers, and More
Beyond the special _ options, valid keywords depend on the selected Matplotlib drawing method. A line-plot keyword is not necessarily valid for a bar plot.
- Use
colororcto set a specific color ('green','#FF5500'). - Add
marker(like'o'for circles,'^'for triangles) andmarkersizeto highlight data points. - Control
linestylewith'--'for dashed or':'for dotted lines.
Reference for special Backtrader options:
| Option | What It Does | Example |
|---|---|---|
_plotskip | Skips drawing the line entirely (good for hidden calculations). | _plotskip=True |
_method | Changes the drawing style (e.g., to bars). | _method='bar' |
_name | Changes the line's name in the legend. | _name='Fast MA' |
_skipnan | Skips over NaN values when drawing the line. | _skipnan=True |
_samecolor | Forces the line to use the same color as the previous one. | _samecolor=True |
Mix these plotlines options to turn a busy default chart into a clean, focused one that highlights exactly what you need.
Building Charts with Multiple Trading Indicators
When you're building a real strategy, you typically need several indicators on one chart. Backtrader uses each indicator's plotting metadata to determine placement.
Moving averages commonly default to a price overlay, while RSI and Stochastic commonly use subplots. This comes from class settings such as subplot; Backtrader does not infer placement by measuring the values' numerical range.
Here's how to add a set of indicators:
def __init__(self):
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25, subplot=True)
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0], plot=False)
You don't need to save every indicator to a variable for it to show up. Create it, and Backtrader handles the rest. The last line uses plot=False — useful when you need an indicator's values for strategy logic but don't want it cluttering the chart.
So you've been building your strategy, and it works. But the charts look off. Colors are hard to distinguish. Too much data crammed into one image.
That's where system-wide plot customization helps. Think of it as a consistent makeover using a few simple settings, instead of tweaking every line individually.
The key is cerebro.plot() with a few special parameters:
| Parameter | What It's For | If You Leave It Blank... |
|---|---|---|
| plotter | Pass a plotter instance implementing the plotting interface; a PlotScheme alone is not a plotter. | Backtrader creates a default one, usually fine to start. |
| numfigs | Many indicators over many years? One chart will be a mess. Split output across multiple figures. | Defaults to 1 — everything on one (potentially very long) chart. |
| iplot | Working in Jupyter Notebook? Set True (default) for inline charts. | For command-line scripts, set False. |
| **kwargs | Pass individual style attributes directly (like barup='green') without a full PlotScheme. | Nothing happens — you'll use defaults. |
These parameters help you produce consistent, professional-looking charts whether you're reviewing them yourself or sharing with others.
Making Your Charts Look Exactly How You Want
The PlotScheme class is your control panel for chart appearance.
Main settings:
style: The big one. Line, bar, or candlestick?grid: Toggle background grid lines on or off.volume: Show or hide trading volume.voloverlay: If showing volume, overlay on the main chart or show in its own section?barup/bardown: Colors for up and down periods. Green and red are standard, but pick what works for you.
Apply them like this:
cerebro.plot(style='candle', grid=False, volume=False)
A Quick Guide to Chart Styles
Three chart styles, each showing price action differently:
| Style | What It Shows | Best For... |
|---|---|---|
'line' (Default) | A single line connecting closing prices. | Quick trend overview without extra detail. |
'bar' | Traditional OHLC bars showing Open, High, Low, Close. | More detail than a line, showing the trading range each period. |
'candle' | Candlestick chart with a body between open and close. | Reading market sentiment fast — thick bodies make bullish vs. bearish periods obvious. |
The default scheme uses 'line'. Candles show the open/close relationship and range; they do not identify which traders control the market. See the upstream PlotScheme source.
How to Fix the Matplotlib "cannot import name 'warnings'" Error in Backtrader
The error cannot import name 'warnings' from matplotlib.dates can come from older Backtrader plotting code importing an internal Matplotlib name. First capture the full traceback, Python version, installed package versions and the actual imported file path. An old local installation can differ from the current upstream source.
The upstream locator implementation imports Python's standard-library warnings directly and later calls warnings.warn(). Simply deleting the old import can leave warnings undefined. If maintaining a local patch, replace the obsolete import with import warnings and preserve the other date-locator imports. Keep that patch reproducible in an isolated environment rather than silently editing a shared installation.
An import fix does not prove all plotting APIs are compatible. Render a small known dataset and exercise its date axis and export. Pin the package combination only after that check. Do not assume matplotlib==3.3.4 is a universal fix; an old release may not support your current Python environment.
Making Your Charts Clearer for Any Time Period
Run a backtest over a long period and you'll see it: one crowded chart where details get lost. It's like a map squeezed onto one page.
Fix it with numfigs. Split your results into separate figures. Running cerebro.plot(numfigs=4) divides your backtest into four distinct charts. Patterns and trade signals become much easier to spot.
You can also control how much space your main data and indicators get with rowsmajor and rowsminor.
Default values:
| Parameter | Default Value | What It Means |
|---|---|---|
rowsmajor | 5 | Main price/volume charts get 5 parts of vertical space. |
rowsminor | 1 | Indicator sub-charts get 1 part of vertical space. |
Main chart is five times taller than each indicator pane. Good starting point.
But you can change this. If your strategy relies heavily on indicator readings, increase rowsminor. If you're focused on price action, keep the default or increase rowsmajor. Choose the ratio based on readable labels and the number of panels, then inspect the saved image at its intended size.
Working with Custom Indicators and Observers
Custom indicators inherit charting tools from the base Indicator class. To make your legend clear, you work with two methods.
First, _plotlabel() controls the legend label — the text next to your indicator line. By default it shows the indicator's name. To add useful details like the period you used, implement this method.
The method returns a list of values that appear in parentheses after the indicator name. The custom example below assumes a period parameter and chooses to include it only when it differs from 14. This is an example labeling policy, not a statement of every built-in RSI label default.
def _plotlabel(self):
labels = []
if self.p.period != 14: # Only show if non-default
labels.append(f'Period: {self.p.period}')
return labels
Second, _plotinit() runs just before the chart draws. It's your chance to set up the visual environment based on user parameters.
RSI uses this to set overbought/oversold bands. The _plotinit() method takes the user's band levels and tells the plotting system: "draw horizontal reference lines at these values."
def _plotinit(self):
self.plotinfo.plotyhlines = [self.p.upperband, self.p.lowerband]
Setting plotyhlines this way means horizontal lines always match the configured upperband and lowerband. Whether someone uses 70/30 or 80/20, the reference lines adjust automatically.
_plotlabel() identifies your indicator in the legend. _plotinit() sets up the chart's visual environment. Together they make your custom tool clearer and more user-friendly.
Troubleshooting Your Backtrader Visualizations: Common Questions Answered
Working with Backtrader's plotting is useful, but you'll hit snags. Here are straightforward answers to common questions.
My chart isn't showing up after cerebro.plot(). What's wrong?
First, check that matplotlib is installed: pip install matplotlib. Backtrader uses it for all charts.
The most common culprit is order of operations. Call cerebro.run() before cerebro.plot(). The plot function needs data from the run to display anything.
If you're in a script or non-interactive environment (some IDEs, servers), the chart won't pop up automatically. Two easy fixes:
- Add
import matplotlib.pyplot as pltandplt.show()aftercerebro.plot(). - Capture the nested figure lists returned by
cerebro.plot(iplot=False)and call each Matplotlib Figure'ssavefig()method with a distinct output filename. Passingsavefig=to Cerebro is not a documented file-export API. See Figure.savefig.
How do I change the colors on my Backtrader chart?
The upstream scheme defines its own palette and index order. Check that scheme rather than assuming Matplotlib's current global defaults. You can customize colors two ways:
- Quick change: Pass a list of colors (a
lcolorsvariable) toplot(). - Full control: Create a custom
PlotSchemeclass and override itscolor()method to define colors for each element.
Can I compare two different strategies on one chart?
Cerebro can register multiple strategies, and its plot output is organized by strategy and figure. Do not assume simultaneous strategies have independent accounts: they can share the configured broker. For independent performance comparisons, run separate experiments with identical starting conditions and compare normalized equity series.
But you can add multiple indicators (two different MAs) to a single strategy. This is the best way to compare different signal systems within the same backtest on one chart.
I added an indicator for calculations but don't want it cluttering the chart. How do I hide it?
Add plot=False when creating the indicator. Backtrader calculates the values but skips drawing.
hidden_atr = bt.indicators.ATR(self.data, plot=False)
What's the difference between plotinfo and plotlines?
They handle different levels of the chart:
plotinfocontrols big-picture settings for the entire indicator: subplot or main chart, legend name, whether it's plottable at all.plotlinescontrols individual line styling: color, style, width, marker types, the visual name for that specific line.
plotinfo decides where and if something is plotted. plotlines decides how each line looks.
What to Try Next
Now that you've got the basics, the best way to learn is to play. Start by tweaking PlotScheme settings — change colors, gridlines, row proportions. Find a look that makes the data easy on your eyes.
Build a strategy mixing different indicator types: a trend-following moving average, a momentum oscillator (RSI), and a volatility measure (Bollinger Bands). Run a backtest and watch Backtrader automatically arrange everything into a multi-panel chart. Use an explicit rule and compare charted signals against logged timestamps; a visually appealing result does not validate its trading assumptions.
Large datasets and many panels can increase rendering time and memory use. Benchmark your actual configuration; no tick-level or daily-data performance result is established here.
Once you're comfortable, customize your own indicators. Adding _plotlabel() and _plotinit() methods lets you control exactly how your indicator appears — its name, color, and line style. Useful if you're developing unique indicators that need to stand out.
If you plot frequently, get organized. Create a couple of standard PlotScheme setups — one for quick daily checks, another for detailed weekly reviews. Save them as reusable Python files. Keeps your charts consistent whether you're comparing strategies or sharing results.
Finally, automate the boring part. Instead of saving charts manually, write a script that saves plots to PNG files after each backtest. You'll build a visual history of your work, making it easy to document your process or review how a strategy changed over time.
Frequently Asked Questions
What does Backtrader plot do?
Backtrader plot converts your backtest results into matplotlib charts. After you call cerebro.run(), one call to cerebro.plot() generates figures showing price data, indicators, portfolio value changes, and trade markers — all automatic.
How do I get started with Backtrader plot?
Create a strategy class, load your data into Cerebro, add the strategy, run cerebro.run(), then call cerebro.plot(). Make sure matplotlib is installed (pip install matplotlib). The default output shows price data with your indicators plus enabled standard observers such as Broker, Trades and BuySell. The example strategy itself submits no orders.
What's the difference between plotinfo and plotlines in Backtrader?
plotinfo controls where and if an indicator is plotted — subplot vs. main chart, legend name. plotlines controls how individual lines look — color, style, width, markers. Use plotinfo for placement, plotlines for appearance.
How do I fix matplotlib errors in Backtrader?
Inspect the full traceback and installed versions. For the obsolete warnings import, the current upstream locator uses the standard-library import warnings; merely deleting the old import can cause another error. Validate a reproducible package combination with a small chart and export.
Can I plot multiple indicators on one Backtrader chart?
Yes. Placement follows indicator plotting metadata such as subplot, not an automatic range comparison. An instance can be plotted without a saved Python variable, but keep a reference if you need to access or configure it later.
Speaking of building and visualizing strategies, if you're looking for a more powerful way to create, test, and deploy trading indicators without manual coding, you might want to explore a dedicated platform.
Pineify offers a Visual Editor and AI Coding Agent for TradingView Pine Script. It is a separate development workflow, not a Backtrader chart renderer or automatic Python-to-Pine converter. See the Pineify review for its intended uses.
Its backtest-report workflow analyzes imported TradingView trade data and includes resampling-based Monte Carlo analysis. Those reports depend on the source backtest and their statistical assumptions. They do not certify a strategy for production, and generated scripts still require syntax and behavior checks.

