Ranked bar charts

q.plot.barchart compares one scalar per category, such as strategy metrics, asset returns, factor exposures, or model coefficients.

This tutorial generates synthetic strategy prices with q.stats.random_walk, calculates performance metrics, and presents them as interactive Plotly bar charts. The examples are deterministic and require no market data or network access.

1. Import dependencies

The tutorial uses pandas for tabular data, NumPy for reproducible parameter generation, and QRT for synthetic prices, statistics, and plotting.

import pandas as pd

import qrt as q

2. Configure reproducibility

A fixed seed makes the generated strategy parameters reproducible. The random-walk generator also receives its own seed, so the synthetic prices remain stable across documentation builds.

q.set_seed(7)

3. Rank random-walk strategies

q.stats.random_walk generates 40 geometric Brownian motion price paths from annualized drift and volatility inputs. After converting the prices to daily returns, q.stats.sharpe calculates one annualized Sharpe ratio per strategy.

The resulting Series maps strategy names to scalar values, which is the simplest input for q.plot.barchart. Setting sorted=True ranks the strategies from highest to lowest Sharpe, while vertical labels keep all 40 names readable.

import numpy as np

rng = np.random.default_rng(7)
strategy_names = [f"strategy_{number}" for number in range(1, 41)]
annual_drifts = rng.uniform(-0.10, 0.25, size=len(strategy_names))
annual_volatilities = rng.uniform(0.10, 0.40, size=len(strategy_names))

strategy_prices = q.stats.random_walk(
    periods=756,
    paths=len(strategy_names),
    drift=annual_drifts,
    volatility=annual_volatilities,
    start=100.0,
    start_date="2023-01-02",
    names=strategy_names,
    seed=42,
)
strategies = strategy_prices.pct_change().dropna()
strategy_sharpes = strategies.apply(q.stats.sharpe).rename("Sharpe ratio")

strategy_sharpes.head()
strategy_1    1.101559
strategy_2    0.627295
strategy_3    0.926552
strategy_4    0.811375
strategy_5    0.250864
Name: Sharpe ratio, dtype: float64
q.plot.cumulative_returns(returns=strategies["strategy_1"])
fig = q.plot.barchart(
    data=strategy_sharpes,
    sorted=True,
    title="40 random-walk strategies ranked by Sharpe ratio",
    yaxis_title="Annualized Sharpe ratio",
)
fig.show()

4. Plot DataFrame columns

A one-row DataFrame produces one bar per selected column. This example first compounds each strategy’s daily returns into a financially meaningful total return, then uses the resulting columns as bar categories.

The columns argument accepts exact names and shell-style patterns. Because every generated column ends in _return, the "*_return" pattern selects all 40 strategies.

total_returns = strategies.add(1).prod().sub(1).to_frame().T
total_returns.columns = [f"{column}_return" for column in total_returns.columns]

fig = q.plot.barchart(
    data=total_returns,
    columns="*_return",
    sorted=True,
    title="Compounded total return by strategy",
    yaxis_title="Total return",
)
fig.update_yaxes(tickformat=".0%")
fig.show()

For a multi-row DataFrame, specify how each selected column should collapse to one value. Here aggregate="mean" compares the average daily return of the first ten strategies. This explicit reduction is appropriate for the displayed statistic; compounded total return was calculated separately above.

fig = q.plot.barchart(
    strategies,
    columns=[f"strategy_{number}" for number in range(1, 11)],
    aggregate="mean",
    sorted=True,
    title="Average daily return for ten strategies",
    yaxis_title="Mean daily return",
)
fig.update_yaxes(tickformat=".3%")
fig.show()

5. Choose a color scheme

The default "sign" scheme uses green for positive values, red for negative values, and gray for zero. Customize positive_color, negative_color, and zero_color to retain those semantics with a different palette.

scenario_returns = pd.Series(
    {
        "trend": 0.24,
        "carry": 0.11,
        "defensive": 0.0,
        "mean reversion": -0.08,
        "breakout": -0.17,
    },
    name="Scenario return",
)

fig = q.plot.barchart(
    scenario_returns,
    sorted=True,
    positive_color="#16A34A",
    negative_color="#DC2626",
    zero_color="#64748B",
    title="Strategy outcomes",
    yaxis_title="Return",
)
fig.update_yaxes(tickformat=".0%")
fig.show()

Pass a mapping when categories need stable, individually assigned colors. Every displayed category must have a corresponding entry.

palette = ["#2563EB", "#475569", "#0D9488", "#D97706"]
strategy_colors = {
    name: palette[index % len(palette)]
    for index, name in enumerate(strategy_names)
}

fig = q.plot.barchart(
    strategy_sharpes,
    sorted=True,
    color_scheme=strategy_colors,
    title="Random-walk Sharpe ratios by strategy",
    yaxis_title="Annualized Sharpe ratio",
)
fig.show()

Alternatively, pass a color sequence to cycle through a reusable palette. If the sequence is shorter than the number of bars, QRT repeats it; a single color string applies one color to every bar.

fig = q.plot.barchart(
    strategy_sharpes,
    sorted=True,
    color_scheme=["#0891B2", "#F59E0B"],
    label_angle=-90,
    title="Alternating strategy palette",
    yaxis_title="Annualized Sharpe ratio",
)
fig.show()

6. Validate the outputs

These assertions make the documentation build fail if the synthetic-data shape, price constraints, calculated metrics, or final Plotly trace changes unexpectedly.

assert strategy_prices.shape == (756, 40)
assert (strategy_prices > 0).all().all()
assert strategies.shape == (755, 40)
assert list(strategies.columns) == strategy_names
assert strategy_sharpes.shape == (40,)
assert strategy_sharpes.notna().all()
assert total_returns.shape == (1, 40)
assert ((total_returns > -1) & total_returns.notna()).all().all()
assert len(fig.data) == 1
assert fig.data[0].type == "bar"
assert len(fig.data[0].x) == 40

7. Reproduce the tutorial

The source notebook is docs/plot/barchart.ipynb. Quarto executes it with the repository’s Python environment, and the fixed seeds reproduce the same synthetic prices, statistics, and charts on every documentation build.

Back to top