Correlation Plots

q.plot.correlation creates an interactive Plotly scatterplot matrix for exploring relationships among numeric features. Box or lasso selection is linked across every panel, and the observation timestamp is included in the hover context.

This example uses qrt’s bundled AAPL history, so it executes without a network connection. It deliberately combines related trend descriptors to make the feature structure easy to see. Strong relationships here indicate potential redundancy or multicollinearity; they do not by themselves establish predictive value.

Build market features

The feature set compares five- and twenty-session momentum with price relative to its 20-day average and the spread between 20-day EMA and 100-day SMA. These related trend measurements produce cleaner relationships than mixing trend, volatility, and volume in every panel.

The five-session forward return remains evaluation context only: it is deliberately excluded from the plotted feature dimensions to avoid presenting future information as a model input.

import numpy as np
import pandas as pd

import qrt as q

aapl = q.data.datasets.load("aapl")
sma_20d = q.indicator.sma(aapl["close"], 20)
trend_20d_100d = q.indicator.ema(aapl["close"], 20).div(
    q.indicator.sma(aapl["close"], 100)
).sub(1)
regime = pd.Series(
    np.select(
        [trend_20d_100d < -0.02, trend_20d_100d > 0.02],
        ["risk-off", "risk-on"],
        default="neutral",
    ),
    index=aapl.index,
    name="regime",
)

feature_columns = [
    "momentum_5d",
    "momentum_20d",
    "close_vs_sma_20d",
    "trend_20d_100d",
]
features = pd.DataFrame(
    {
        "momentum_5d": q.indicator.momentum(aapl["close"], window=5),
        "momentum_20d": q.indicator.momentum(aapl["close"], window=20),
        "close_vs_sma_20d": aapl["close"].div(sma_20d).sub(1),
        "trend_20d_100d": trend_20d_100d,
        "regime": regime,
        "forward_return_5d": q.indicator.returns(aapl["close"], periods=5).shift(-5),
        "symbol": "AAPL",
    }
).dropna().iloc[-750:]

features[feature_columns].corr().round(2)
momentum_5d momentum_20d close_vs_sma_20d trend_20d_100d
momentum_5d 1.00 0.46 0.75 0.06
momentum_20d 0.46 1.00 0.82 0.43
close_vs_sma_20d 0.75 0.82 1.00 0.27
trend_20d_100d 0.06 0.43 0.27 1.00
features
momentum_5d momentum_20d close_vs_sma_20d trend_20d_100d regime forward_return_5d symbol
datetime
2023-07-20 0.013593 0.049848 0.013875 0.108415 risk-on 0.000466 AAPL
2023-07-21 0.006555 0.026417 0.006323 0.106640 risk-on 0.020267 AAPL
2023-07-24 -0.006392 0.032515 0.008964 0.105135 risk-on 0.019196 AAPL
2023-07-25 -0.000568 0.045069 0.011308 0.103794 risk-on 0.010278 AAPL
2023-07-26 -0.003075 0.034244 0.014199 0.102758 risk-on -0.009871 AAPL
... ... ... ... ... ... ... ...
2026-07-10 0.021676 0.081419 0.057755 0.082605 risk-on 0.058417 AAPL
2026-07-13 0.014872 0.073335 0.060574 0.085717 risk-on 0.029246 AAPL
2026-07-14 0.013520 0.081510 0.048228 0.087604 risk-on 0.040907 AAPL
2026-07-15 0.045024 0.104851 0.084697 0.092770 risk-on -0.004916 AAPL
2026-07-16 0.053887 0.113688 0.097591 0.099053 risk-on -0.034808 AAPL

750 rows × 7 columns

Color by market regime

The regime is defined from AAPL’s own 20-day EMA relative to its 100-day SMA: below -2% is risk-off, above 2% is risk-on, and the middle band is neutral. Because trend_20d_100d drives that definition, its color separation is intentionally clear rather than accidental.

color_by="regime" is an explicit column lookup: the input DataFrame must contain a column named regime. QRT does not infer that column, interpret its name, or calculate the regime boundaries. For each row, it reads the value already stored in features["regime"]; color_discrete_map then maps values such as "risk-off" to their specified colors. A text, categorical, or boolean color_by column uses discrete colors, while a numeric column uses color_continuous_scale.

The helper defaults to a full matrix with visible diagonal panels, high-contrast markers, dark boxed subplot axes, and outward ticks. Legend entries can be hidden or isolated, and selecting observations in one panel highlights them throughout the matrix.

regime_colors = {
    "risk-off": "#D1495B",
    "neutral": "#8D99AE",
    "risk-on": "#2A9D8F",
}

fig = q.plot.correlation(
    features,
    columns=feature_columns,
    color_by="regime",
    color_discrete_map=regime_colors,
    hover_data=["symbol", "forward_return_5d"],
    title="AAPL trend-feature relationships by market regime",
)
fig.show()

Color by forward outcome

Use color_continuous_scale for a numeric color variable. The default is "RdYlGn"; it also accepts Plotly scale names such as "RdBu", "Viridis", or "Cividis". Append _r to reverse a named scale, for example "RdYlGn_r".

The feature-feature geometry remains clean, but the forward-return colors are more mixed. That contrast is useful: correlated inputs can be redundant without having a simple relationship to future returns. QRT centers the color scale at zero, with red for losses and green for gains by default.

fig = q.plot.correlation(
    features,
    columns=feature_columns,
    color_by="forward_return_5d",
    hover_data=["symbol", "regime"],
    title="AAPL trend features colored by 5-day forward return",
)
fig.show()

The default full matrix keeps both mirrored comparisons and visible diagonal panels. Use triangle="lower" or triangle="upper" for a more compact presentation. In research workflows, useful categorical colors include asset, volatility regime, predicted action, and train/validation/test split; useful continuous colors include forward return, realized volatility, model confidence, and sample weight.

Back to top