Plotting for Analysis: Price with Indicators, Equity and Drawdown Charts, Return Distributions and Exporting Figures
Use matplotlib's figure-and-axes interface to build the four charts every backtest report needs, read them correctly, and export them at print quality.
What you will learn
- The
fig, ax = plt.subplots()pattern and why it beats the quickseries.plot()shortcut for anything you will reuse. - How to stack price, indicator and equity panels that share one time axis, and when to switch the equity axis to a log scale.
- How to draw a drawdown chart and a return histogram and what to look for in each.
- How figure size, DPI and file format determine what the exported image looks like.
Explanation
Charts in research are not decoration; they are the fastest check that your numbers are right. An equity curve that jumps 30 % in one day usually means a data error, not a great trade. This lesson uses matplotlib, which pandas calls under the hood, through its object-oriented interface.
Figure, axes and the shared time axis
A figure is the whole canvas; an axes object is one plot area inside it. fig, axes = plt.subplots(3, 1, figsize=(10, 8), sharex=True, height_ratios=[3, 1, 1]) creates three stacked panels of different heights that zoom together. The pattern for every panel is the same: call ax.plot(x, y, label=...), then ax.set_ylabel, ax.legend() and ax.grid(True, alpha=0.3). Give each panel a short title and label units (€, %, points).
Price with indicators
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True, height_ratios=[3, 1])ax1.plot(df.index, df.close, lw=1, label='Close')ax1.plot(df.index, df.sma50, lw=1, label='SMA 50')ax1.fill_between(df.index, df.bb_lo, df.bb_hi, alpha=0.15, label='Bollinger')ax1.legend(loc='upper left'); ax1.set_ylabel('Price (€)')ax2.plot(df.index, df.rsi, lw=1); ax2.axhline(70, ls='--'); ax2.axhline(30, ls='--')ax2.set_ylabel('RSI'); fig.autofmt_xdate(); fig.tight_layout()
fill_between shades a band; axhline draws reference levels; autofmt_xdate rotates dates so they do not overlap. To mark trades from the lesson 8 trade list, use ax1.scatter(entries.index, entries.price, marker='^') for entries and a downward triangle for exits.
Equity and drawdown
Plot equity with ax.set_yscale('log') whenever the account more than doubles over the period. On a linear axis a move from €10,000 to €11,000 looks tiny next to a later move from €40,000 to €44,000, although both are +10 %. The drawdown panel plots dd = equity / equity.cummax() - 1 and fills it: ax.fill_between(dd.index, dd, 0, alpha=0.4). Read it for three things: the deepest point (maximum drawdown), the longest stretch below zero (time under water, often more painful than depth), and whether drawdowns cluster in one regime.
Return distributions
ax.hist(returns, bins=50, density=True) shows the shape of daily or per-trade returns. Overlay a normal curve with the same mean and standard deviation to see the fat tails: real returns have more extreme days than the bell curve predicts. For a trade list, plot the R-multiples instead: a healthy trend system shows many small losses near −1R and a long right tail; a short-volatility system shows the mirror image, which is a warning. Report skewness and kurtosis with returns.skew() and returns.kurt() (pandas returns excess kurtosis, so 0 means normal-like tails).
Exporting
fig.savefig('equity.png', dpi=150, bbox_inches='tight') writes a raster image; pixel size = inches × DPI, so a 10 × 6 inch figure at 150 DPI is 1,500 × 900 pixels. Use .svg or .pdf for vector output that stays sharp in a report, and bbox_inches='tight' to trim white margins. Always call plt.close(fig) after saving inside a loop, or a script that produces 40 watchlist charts (lesson 12) will hold 40 figures in memory. Set defaults once with plt.rcParams['figure.dpi'] = 110 rather than in every call.
Worked example
Hypothetical example: a backtest equity series (euros, invented) and its chart. Fees and taxes excluded from the equity values.
- Equity at year-ends: €10,000, €13,000, €11,700, €16,000, €24,000. Drawdown series: peaks 10,000; 13,000; 13,000; 16,000; 24,000 → drawdowns 0; 0; 11,700 / 13,000 − 1 = −10.0 %; 0; 0. Maximum drawdown −10 %, time under water about one year.
- On a linear axis the last segment (+€8,000) dominates the picture; on a log axis the steps +30 %, −10 %, +36.8 %, +50 % appear with their true relative sizes, and the −10 % year is visible instead of being flattened.
- Daily returns: mean 0.06 %, standard deviation 1.1 %, excess kurtosis 4.2. A normal distribution with that standard deviation would put a −3.3 % day (three standard deviations) at about one in 740 days; the histogram shows seven such days in 1,000. That gap is the fat tail and it is the reason stops can be gapped through (lesson 8).
- Export:
figsize=(8, 5)atdpi=200gives 1,600 × 1,000 pixels, enough for a slide; the same figure saved as SVG is resolution-independent and often smaller than 100 kB.
Common mistake
Practice activity
On paper: (a) An equity curve goes 20,000 → 26,000 → 19,500 → 28,000 (in euros, four points). Compute the drawdown at each point. (b) You want a figure that is exactly 1,200 × 800 pixels at 100 DPI; what figsize do you pass? (c) The histogram of a strategy's R-multiples has its tallest bar between +0.2R and +0.4R and a thin left tail reaching −6R. Write one sentence on what this shape suggests about the strategy and the risk a Sharpe ratio would not show.
Show solution
(a) Peaks: 20,000; 26,000; 26,000; 28,000. Drawdowns: 0; 0; 19,500 / 26,000 − 1 = −25.0 %; 0.
(b) figsize = (1,200 / 100, 800 / 100) = (12, 8) inches.
(c) Many small wins and rare very large losses is the profile of a strategy that sells risk (for example writing options or averaging down); the Sharpe ratio, built from an average and a standard deviation, is pulled up by the frequent small wins and understates the chance of a −6R day that erases months of gains.
Limitations: a histogram with fewer than roughly 100 observations is too noisy to judge tail shape; report the actual worst values instead.
Key takeaways
- Use
plt.subplotswith shared x-axes for price, indicator, equity and drawdown panels; label units. - Log-scale equity shows percentage moves honestly; drawdown charts show depth and time under water.
- Histograms of returns or R-multiples reveal fat tails and asymmetry that summary statistics hide.
- Pixels = inches × DPI; prefer SVG or PDF for reports and close figures inside loops.
Next step
Lesson 11 moves from one instrument to several: correlation and covariance matrices, portfolio volatility from weights, and a rebalancing simulation in pandas.
Self-check
Answer the questions, then check your answers. You can retry as often as you like.
Automatic self-check for your own revision. Your answers are checked in your browser and are not seen or assessed by us. It is not an exam and not a qualification.
Your progress
“Complete” only means you marked the lesson as done. It is not a test of your understanding. Progress and notes are saved only in this browser on this device — not in an account.