Most algo traders write signals. Almost none visualize them correctly.
You can have a backtest with 300 trades, a Sharpe ratio of 1.4, and still have no idea whether your system is working — or getting lucky. The numbers don't show you where the strategy breathes. Charts do.
If you're:
- building a quantitative trading system,
- running backtests and not knowing how to interpret the results,
- or trying to communicate your edge to investors or teammates,
This matters.
The Real Problem Isn't the Data. It's What You Can't See.
A developer I know spent three months building a mean-reversion strategy. Solid execution, clean Python, well-documented. He ran the backtest. The results looked good.
What he couldn't see:
- Three consecutive months of drawdown that would have ended any fund
- 80% of returns coming from a single volatile week in March
- His volume filters triggering on micro-cap stocks with no real liquidity
The strategy wasn't broken. His visualization layer was.
Visualization is not decoration. It is part of the analysis.
Once I understood that, I stopped treating charts as outputs and started treating them as inputs to the research process.
Why EODHD Is the Right Data Layer for This
Before the code, a word on data quality.
Every chart in this article is powered by EODHD APIs. Not because it's the only option — but because it's the one I consistently use for production-grade algo trading research.
What you get:
- End-of-day prices for 70,000+ tickers across 60+ exchanges
- Intraday data down to 1-minute resolution
- Fundamental data, economic indicators, and options chains — all through a clean REST API
- Python-friendly JSON responses with predictable schema
The free tier covers most of what you need to start. Paid plans scale with your data volume.
Data Architecture: From API to Chart
Before we dive into charts, it's worth understanding how data flows from the EODHD API into your visualization layer.
The pipeline has four stages: fetch raw OHLCV data from the REST API, normalize it into a pandas DataFrame, compute derived metrics (signals, indicators, returns), and finally render the visualization.
The 5 Visualizations
1. Candlestick Chart with Moving Averages — Trend Structure at a Glance
What it shows: Price action as OHLC candles, layered with a fast (20-period) and slow (50-period) exponential moving average.
Why it matters for algo trading:
The candlestick + EMA combination is the foundation of every trend-following system. Before deploying any directional strategy, you need to see whether the asset is trending, consolidating, or reversing. Moving average crossovers are a common signal generation mechanism — and seeing them plotted against real price action reveals how late, noisy, or clean those signals actually are.
A crossover that looks clean in a table of dates looks completely different when you see it happening during a sideways chop period. The chart reveals what the numbers hide.
import requests
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
API_KEY = "your_api_key"
TICKER = "AAPL.US"
def fetch_ohlcv(ticker, api_key, period="d", from_date="2024-01-01"):
url = f"https://eodhd.com/api/eod/{ticker}"
params = {
"api_token": api_key,
"fmt": "json",
"period": period,
"from": from_date
}
r = requests.get(url, params=params)
df = pd.DataFrame(r.json())
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
return df
df = fetch_ohlcv(TICKER, API_KEY)
# Compute EMAs
df["ema_20"] = df["close"].ewm(span=20).mean()
df["ema_50"] = df["close"].ewm(span=50).mean()
fig = go.Figure()
fig.add_trace(go.Candlestick(
x=df.index,
open=df["open"], high=df["high"],
low=df["low"], close=df["close"],
name="OHLC"
))
fig.add_trace(go.Scatter(x=df.index, y=df["ema_20"],
line=dict(color="orange", width=1.5), name="EMA 20"))
fig.add_trace(go.Scatter(x=df.index, y=df["ema_50"],
line=dict(color="blue", width=1.5), name="EMA 50"))
fig.update_layout(
title=f"{TICKER} — Candlestick with EMA 20/50",
xaxis_rangeslider_visible=False,
template="plotly_dark",
height=500
)
fig.show()
From here you can build:
- EMA crossover signal detection with entry/exit markers on the chart
- Multi-timeframe confluence overlays (daily trend + intraday entry)
- Automated alerts when price closes above/below a moving average
2. Volume Profile — Where the Market Actually Traded
What it shows: A horizontal histogram of traded volume at each price level over a defined period.
Why it matters for algo trading:
Price tells you when moves happened. Volume profile tells you where the market spent its time. High-volume nodes are price magnets — levels where large participants built positions. Low-volume nodes are inefficiencies — levels the market crossed through quickly and is likely to revisit.
If your strategy is entering near a high-volume node, you have natural support or resistance. If it's entering in a low-volume area, you're in air. No other visualization shows this as clearly.
import numpy as np
import matplotlib.pyplot as plt
# Fetch data (reuse fetch_ohlcv from above)
df = fetch_ohlcv(TICKER, API_KEY)
# Build volume profile
price_range = np.linspace(df["low"].min(), df["high"].max(), 50)
vol_profile = np.zeros(len(price_range) - 1)
for _, row in df.iterrows():
for i in range(len(price_range) - 1):
if price_range[i] <= row["close"] < price_range[i+1]:
vol_profile[i] += row["volume"]
break
bin_centers = (price_range[:-1] + price_range[1:]) / 2
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7),
gridspec_kw={"width_ratios": [3, 1]}, facecolor="#0d1117")
# Left: price chart
ax1.plot(df.index, df["close"], color="#58a6ff", linewidth=1.2)
ax1.set_facecolor("#0d1117")
ax1.set_title(f"{TICKER} — Close Price", color="white")
ax1.tick_params(colors="gray")
# Right: volume profile (horizontal bars)
ax2.barh(bin_centers, vol_profile,
height=(price_range[1] - price_range[0]) * 0.85,
color="#388bfd", alpha=0.75)
ax2.set_facecolor("#0d1117")
ax2.set_title("Volume Profile", color="white")
ax2.tick_params(colors="gray")
ax2.yaxis.tick_right()
plt.tight_layout()
plt.show()
From here you can build:
- Point of Control (POC) detection — the single price level with the highest volume
- Value Area calculation (70% of volume) for mean-reversion entries
- Multi-session profiles to compare volume distribution across market regimes
3. Equity Curve with Drawdown Panel — Backtest Health in One View
What it shows: The cumulative return of a strategy over time (top panel) + the drawdown at every point (bottom panel).
Why it matters for algo trading:
A positive total return tells you almost nothing. What matters is the path to that return. A strategy that returns +40% over 3 years with a -35% drawdown in the middle is not the same as one that returns +40% with a max drawdown of -8%. One you can trade. The other will cause you to abandon the system at exactly the wrong moment.
The drawdown panel is the psychological test. If you can't stomach what you see there, you can't run that strategy live.
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# --- Simulate a simple MA crossover strategy ---
df = fetch_ohlcv(TICKER, API_KEY)
df["ema_10"] = df["close"].ewm(span=10).mean()
df["ema_30"] = df["close"].ewm(span=30).mean()
df["signal"] = np.where(df["ema_10"] > df["ema_30"], 1, -1)
df["daily_return"] = df["close"].pct_change()
df["strategy_return"] = df["signal"].shift(1) * df["daily_return"]
df["equity_curve"] = (1 + df["strategy_return"]).cumprod()
df["running_max"] = df["equity_curve"].cummax()
df["drawdown"] = (df["equity_curve"] / df["running_max"]) - 1
# --- Plot ---
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
row_heights=[0.65, 0.35],
vertical_spacing=0.05)
fig.add_trace(go.Scatter(
x=df.index, y=df["equity_curve"],
fill="tozeroy", line=dict(color="#58a6ff"),
name="Equity Curve"), row=1, col=1)
fig.add_trace(go.Scatter(
x=df.index, y=df["drawdown"] * 100,
fill="tozeroy", line=dict(color="#f85149"),
fillcolor="rgba(248,81,73,0.2)",
name="Drawdown (%)"), row=2, col=1)
fig.update_layout(
template="plotly_dark",
title="MA Crossover Strategy — Equity Curve & Drawdown",
height=550,
showlegend=True
)
fig.show()
Key metrics to annotate on this chart:
- Max Drawdown — the deepest valley from peak
- Recovery Time — how long it took to reach a new equity high
- Calmar Ratio — annualized return / max drawdown (one number that summarizes risk-adjusted performance)
4. Correlation Heatmap — Portfolio Risk Hidden in Plain Sight
What it shows: A matrix of pairwise return correlations between multiple assets or strategies.
Why it matters for algo trading:
Diversification only works if your assets are actually uncorrelated. Most traders think they're diversified because they hold different tickers. They're not — they're holding different tickers that all fall together in a risk-off event.
A correlation heatmap shows you the real structure of your portfolio. Dark red cells = concentrated risk. Near-zero cells = genuine diversification. This is the chart you show before adding a new strategy or asset to your book.
import seaborn as sns
import matplotlib.pyplot as plt
TICKERS = ["AAPL.US", "MSFT.US", "GOOGL.US", "AMZN.US", "JPM.US", "GLD.US"]
price_data = {}
for ticker in TICKERS:
df_t = fetch_ohlcv(ticker, API_KEY)
price_data[ticker.split(".")[0]] = df_t["close"]
prices = pd.DataFrame(price_data).dropna()
returns = prices.pct_change().dropna()
corr_matrix = returns.corr()
fig, ax = plt.subplots(figsize=(9, 7), facecolor="#0d1117")
sns.heatmap(
corr_matrix,
annot=True, fmt=".2f",
cmap="RdYlGn",
center=0, vmin=-1, vmax=1,
square=True, linewidths=0.5,
cbar_kws={"shrink": 0.8},
ax=ax
)
ax.set_title("Asset Return Correlation Matrix", color="white", pad=15)
ax.tick_params(colors="white")
plt.tight_layout()
plt.show()
From here you can build:
- Correlation monitoring over rolling windows to detect regime shifts
- Portfolio optimizer that constrains position sizing based on pairwise correlation
- Alert when two previously uncorrelated assets start moving together
5. RSI + Bollinger Bands Overlay — Momentum Meets Volatility
What it shows: RSI (Relative Strength Index) as a momentum oscillator below the price chart, with Bollinger Bands (20-period SMA ± 2σ) overlaid on price.
Why it matters for algo trading:
Neither RSI nor Bollinger Bands alone is particularly reliable. Together, they become a high-conviction signal framework:
- Price touching the lower Bollinger Band while RSI is below 30 = potential oversold reversal entry
- Price breaking above the upper band while RSI is above 70 = potential momentum continuation or overbought exit
This is the kind of multi-indicator confluence that most systematic strategies are built around. Seeing it visually — before you code the logic — prevents you from building signal combinations that never actually coincide in real market data.
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
df = fetch_ohlcv(TICKER, API_KEY)
# Bollinger Bands
df["bb_mid"] = df["close"].rolling(20).mean()
df["bb_std"] = df["close"].rolling(20).std()
df["bb_upper"] = df["bb_mid"] + 2 * df["bb_std"]
df["bb_lower"] = df["bb_mid"] - 2 * df["bb_std"]
# RSI
delta = df["close"].diff()
gain = delta.clip(lower=0).rolling(14).mean()
loss = (-delta.clip(upper=0)).rolling(14).mean()
rs = gain / loss
df["rsi"] = 100 - (100 / (1 + rs))
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
row_heights=[0.65, 0.35],
vertical_spacing=0.04)
# --- Price + Bollinger Bands ---
fig.add_trace(go.Scatter(x=df.index, y=df["close"],
line=dict(color="#58a6ff", width=1.5), name="Close"), row=1, col=1)
fig.add_trace(go.Scatter(x=df.index, y=df["bb_upper"],
line=dict(color="rgba(255,165,0,0.5)", dash="dot"), name="BB Upper"), row=1, col=1)
fig.add_trace(go.Scatter(x=df.index, y=df["bb_lower"],
fill="tonexty", fillcolor="rgba(255,165,0,0.05)",
line=dict(color="rgba(255,165,0,0.5)", dash="dot"), name="BB Lower"), row=1, col=1)
fig.add_trace(go.Scatter(x=df.index, y=df["bb_mid"],
line=dict(color="gray", width=0.8, dash="dash"), name="BB Mid"), row=1, col=1)
# --- RSI ---
fig.add_trace(go.Scatter(x=df.index, y=df["rsi"],
line=dict(color="#c9d1d9"), name="RSI"), row=2, col=1)
fig.add_hline(y=70, line_dash="dot", line_color="red", opacity=0.5, row=2, col=1)
fig.add_hline(y=30, line_dash="dot", line_color="green", opacity=0.5, row=2, col=1)
fig.update_layout(
template="plotly_dark",
title=f"{TICKER} — Bollinger Bands + RSI",
height=580
)
fig.show()
From here you can build:
- Signal detection when both conditions are met simultaneously (dual-condition scanner)
- Backtesting the mean-reversion rule: buy when price < BB lower AND RSI < 30
- Squeeze detection using Bollinger Band width as a volatility compression indicator
FAQs
❓ Do I need a paid EODHD plan to run these examples?
✅ The free tier of EODHD includes end-of-day data for most major exchanges, which is enough to run all five visualizations in this article. You only need a paid plan if you require intraday data (1-minute or 5-minute candles), real-time feeds, or access to premium data categories like options chains.
❓ Which Python charting library is best for algo trading — Plotly or Matplotlib?
✅ Use Plotly for any visualization that benefits from interactivity: equity curves you want to zoom, heatmaps you want to hover over, multi-panel dashboards. Use Matplotlib for static outputs, publication-quality figures, or when you're exporting to PDF reports. Both are used in this article because each has a domain where it genuinely wins.
❓ Can I use these visualizations in a live trading dashboard, not just backtesting?
✅ Yes. The EODHD API supports intraday data with minute-level resolution on paid plans. Swap the period="d" parameter in fetch_ohlcv to period="m" for minute data. The chart code stays identical — only the data granularity changes. For a fully automated live dashboard, combine these functions with a scheduler like APScheduler or a workflow tool like n8n.
❓ What does the Volume Profile actually measure vs. standard volume bars?
✅ Standard volume bars (shown at the bottom of most charts) show volume over time — how much was traded in each candle. Volume Profile shows volume at price — how much was traded at each price level across a defined period. The second view is far more useful for identifying support/resistance zones and understanding where institutional positions were built.
❓ Is algorithmic trading data visualization useful if I'm not running fully automated strategies?
✅ Absolutely. Even manual or semi-systematic traders benefit from these charts. The equity curve and drawdown panel are essential for anyone journaling their performance. The correlation heatmap is useful for any multi-asset portfolio. Visualization is not about automation — it's about reducing blind spots.
Final Thoughts
Markets don't reward complexity. They reward clarity.
The five charts in this article won't give you an edge by themselves. But they will show you where your edge actually exists — and, more importantly, where it doesn't.
Start with the candlestick overlay to understand trend structure. Add the drawdown panel to understand psychological survivability. Use the correlation matrix before you add any new position to your book.
The rest follows.
Start Building With Better Data
Every chart in this article runs on EODHD APIs. If you're serious about algorithmic trading research, the data layer is not the place to cut corners.
What you get access to:
- End-of-day and intraday data for 70,000+ tickers across 60+ exchanges
- Fundamentals, economic indicators, and options chains — all through one clean REST API
- A free tier that covers everything in this article, no credit card required
Work With Me
I produce technical content for fintech and developer tools companies — tutorials, API walkthroughs, data-driven articles that actually get read.
If you're building something in the trading, data, or developer tools space and need content that explains your product without sounding like a press release:
👉 Connect on LinkedIn or reach out directly at kevinmenesesgonzalez@gmail.com
Looking for technical content for your company? I can help — LinkedIn · kevinmenesesgonzalez@gmail.com

Top comments (0)