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. I've used it on daily AAPL data from 2020 to 2025, and it beats staring at a table of numbers.
It runs on matplotlib under the hood. So every price bar, every moving average line, every buy/sell signal lands on the same canvas. One call, one chart. That's it.
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.
Because it's wired directly into the backtesting engine, anything you add to your strategy ends up on the final chart. No need to grab another library or build visualizations from scratch.
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. Make sure matplotlib is installed (pip install matplotlib), and you're set.
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.
Here's the structure:
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.
Below the main chart, you get three standard tracks:
| Observer Name | What It Shows You |
|---|---|
| CashValue | Tracks the change in your total portfolio value (cash + holdings) over time. |
| Trade | Shows a bar for each trade — green for profitable, red for losses. |
| BuySell | Places markers on the price chart where your strategy entered and exited. |
This default setup gives you solid, informative visualization with zero extra effort. Once you see it, you'll have a clear base to start customizing. I ran this on SPY from 2018 to 2024 and spotted two bad trades I'd missed in the raw output — the BuySell markers made them obvious. 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: Puts markers on your price chart showing exactly where your strategy bought and sold.
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 work. Pick whichever feels natural. I prefer Method 1 because it keeps everything in one place — less chance of forgetting to apply a style later.
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. You can also use _method='tick' or other matplotlib styles.
Making It Pretty: Colors, Markers, and More
Beyond the special _ options, any standard matplotlib styling keyword works.
- 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 handles this automatically — it groups indicators logically.
Indicators that move in a similar range to your price data (like a simple moving average) plot right on top of the main chart. Indicators with their own distinct scale (RSI, Stochastic) land in sub-charts below. Keeps everything readable.
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 PlotScheme object with global style rules — colors for candles, grid styles, backgrounds. | 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 candleup='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. |
Default is 'line'. I prefer 'candle' for real work — that body makes it way easier to see who's in control. If I'm just scanning trends, 'line' is fine.
How to Fix the Matplotlib "cannot import name 'warnings'" Error in Backtrader
If you hit "cannot import name 'warnings' from matplotlib.dates", you've got a version clash. A newer matplotlib version doesn't play well with older Backtrader plotting code. It's frustrating, but the fix is quick.
The root problem is a single file in Backtrader's installation.
Before editing, back up the file or make sure you can reinstall the package.
Step-by-Step Fix
| Step | Action | Details & Command |
|---|---|---|
| 1 | Find your site-packages folder. | Run: python -c "import site; print(site.getsitepackages())" |
| 2 | Go to the Backtrader plot directory. | Inside site-packages, navigate to backtrader/plot/. The file is locator.py. |
| 3 | Patch locator.py. | Open it in a text editor. Find from matplotlib.dates import warnings and delete that line. Save. |
| 4 | Verify. | Run your Backtrader plot script again. The error should be gone. |
Why This Works (And Other Options)
The import line is outdated — recent matplotlib versions don't keep the warnings module under matplotlib.dates. Removing it clears the path.
If you'd rather not edit package files directly:
- Pin your matplotlib version. Check Backtrader's docs for a compatible version, then run
pip install matplotlib==3.3.4. This is often the cleanest solution. - Check for a Backtrader update. The fix may already be in a newer commit on the Backtrader GitHub page.
I've hit this error twice — once on a fresh install with matplotlib 3.6 and once after an upgrade. The locator.py edit fixed it both times in under a minute. If you frequently run into platform performance problems, our guide on fixing TradingView lag might help.
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. I prefer a 4:1 ratio for my trend-following systems since I'm mostly watching price versus a moving average.
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. RSI uses this to show its period and moving average type, but only when they're not the defaults. The chart tells you exactly how the indicator was configured.
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(). - Save directly:
cerebro.plot(style='candle', savefig='my_backtest_chart.png').
How do I change the colors on my Backtrader chart?
Backtrader uses the Tableau 10 palette by default. You can customize it 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?
Not directly — Backtrader plots everything for a single strategy run on one chart. To compare two separate strategies (like Moving Average Crossover vs. RSI), run them as separate backtests.
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. For inspiration, check out some TradingView strategies proven through backtesting.
One limitation to be upfront about: I haven't tested Backtrader with tick-level intraday data, so I can't vouch for how the plot feature handles high-frequency strategy output. For daily and hourly bars it's rock solid.
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 three built-in observers: CashValue, Trade, and BuySell markers.
▶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?
The common one is cannot import name 'warnings' from matplotlib.dates. Edit backtrader/plot/locator.py and delete from matplotlib.dates import warnings. Or pin matplotlib to a compatible version (pip install matplotlib==3.3.4), or check GitHub for a Backtrader update.
▶Can I plot multiple indicators on one Backtrader chart?
Yes. Backtrader auto-arranges them — indicators near price range (moving averages) go on the main chart, ones with different scales (RSI, Stochastic) go in sub-charts below. You don't need to save every indicator to a variable.
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.
Tools like Pineify take this further by offering a Visual Editor and AI Coding Agent for TradingView's Pine Script. You can drag-and-drop 235+ technical indicators, combine them into strategies, and generate error-free code instantly — no manual coding required. It's useful for translating the strategy ideas you prototype in backtrader into production-ready indicators on TradingView. Check out our Pineify Review to see if it fits your workflow.
You can even import custom code to modify existing scripts or use the DIY Strategy Builder to set entry/exit rules visually. Their Professional Backtest Report tool transforms TradingView backtest data into institutional-grade reports with Sharpe ratio and Monte Carlo simulations. It automates the workflow from idea to tested, visual strategy.

