TradingView correlation indicators and rolling windows
A correlation indicator measures the linear relationship between two series over a selected sample. Values range from -1 to +1 when defined. Choose price levels or returns deliberately: the same assets can have very different coefficients under those two inputs. Correlation does not establish causation or predict a return to an earlier relationship.
The formula is undefined if either sample has zero variance. +1 and -1 describe perfect linear relationships within the sample, not matching price levels or equal percentage moves.
Set up TradingView's Correlation Coefficient
- Open the chart's Indicators menu and search for Correlation Coefficient. Select the built-in indicator.
- Choose the comparison Symbol in Settings. Confirm the exchange and data feed, especially when similar ticker names exist.
- Set Length and Source. Close compares price levels; changing the chart timeframe changes the observations in that window.
- Read the coefficient against its zero line and inspect how it changes over the chart history. Record the symbols, timeframe, source and date range with any value you report.
TradingView documents these inputs in its Correlation Coefficient guide. Use the custom example below when you need a return transformation and two windows.
Prices, returns, and what the coefficient means
| Input or result | Interpretation | Limit |
|---|---|---|
| Closing prices | Relationship between observed price levels | Common trends can dominate. High correlation does not demonstrate cointegration or a mean-reverting spread. |
| Log returns: ln(Pt / Pt-1) | Relationship between changes over matching intervals | Requires positive prices. Does not guarantee a stationary series or stable dependence. |
| Positive / negative r | Same / opposite direction of linear association within the sample | Does not identify beta, position sizing, cause, or future direction. |
| r near zero | Weak measured linear association | Does not prove independence or rule out nonlinear dependence. |
The example's ±0.70 bands are editable reference thresholds. They are not universal significance cutoffs or buy and sell signals. A negative coefficient is insufficient to establish a hedge; exposures and volatility also matter. See the NIST discussion of association and causation and the time-series discussion of spurious regression and cointegration.
Rolling windows and sample stability
A short window gives each observation more influence. A long window includes more history, which can mask a recent change. The example compares 20 and 100 chart bars; neither setting is an optimized parameter or a guarantee of statistical reliability.
If the short coefficient turns negative while the longer one remains positive, the two samples disagree. This can reflect new observations, old observations leaving the window, outliers, or a changing relationship. It does not show that the change is temporary or that a pairs trade will converge.
For a reproducible comparison, hold symbols, feeds, session settings and return definition fixed. Compare nearby window lengths and separate date ranges. Inspect unusually large observations and missing data. Overlapping windows share observations, so consecutive readings are not independent confirmations. This page reports no historical pair estimates or strategy performance tests.
Match trading sessions before interpreting r
A crypto chart includes bars when an equity market is closed. Carrying the last equity close forward creates artificial flat intervals if returns are then computed on the merged series. Matching timeframe names alone also does not ensure that daily bars close at the same time.
In Pine, gaps_off fills missing observations; gaps_on preserves gaps. Neither setting repairs different trading calendars. The example uses gaps, checks current bar start and end timestamps, and checks the previous bar timestamps for returns. It only displays full contiguous windows of valid pairs. A missing bar resets eligibility until the window is complete again.
This strict policy may produce few values for markets with different hours. Use instruments with compatible sessions or explicitly design a shared sampling schedule. Do not replace missing returns with zero. Timestamp agreement also cannot fix stale quotes, different price adjustments or data revisions.
lookahead_off avoids requesting future historical values but is not a general non-repainting guarantee. The example requests the chart timeframe and waits for the chart bar to close. Data-feed timing and revisions still need review. For higher-timeframe confirmed data, TradingView documents an offset expression with lookahead_on; simply changing the timeframe in this example is not equivalent.
References: other timeframes and data and repainting. For visual comparison, see comparing symbol lines.
Pine Script v6: two rolling correlation windows
Paste the complete script into Pine Editor and add it to a standard time-based chart. Select a comparison symbol with compatible bar boundaries. The default mode is log returns; price mode compares closing levels. An incomplete window, zero variance, or an open chart bar can leave the plot blank. This is an indicator, not a backtested trading strategy.
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//@version=6
indicator(title="TradingView Rolling Correlation Indicator [Pineify]", shorttitle="Rolling Corr", overlay=false)
// ---------------- Inputs ----------------
string sym_comp = input.symbol("SP:SPX", "Comparison / Benchmark Symbol", group="Asset Selection")
string calc_mode = input.string("Log Returns", "Calculation Mode", options=["Close Price", "Log Returns"], group="Calculation", tooltip="Close Price compares levels. Log Returns compare changes between matching close timestamps; neither proves a stable relationship.")
int len_fast = input.int(20, "Fast Lookback Window", minval=3, group="Rolling Windows", tooltip="Short-term tactical sensitivity (e.g. 20 bars).")
int len_slow = input.int(100, "Slow Lookback Window", minval=5, group="Rolling Windows", tooltip="Longer sample baseline (e.g. 100 bars).")
float thresh_pos = input.float(0.70, "Strong Positive Threshold", minval=0.0, maxval=1.0, step=0.05, group="Thresholds")
float thresh_neg = input.float(-0.70, "Negative Threshold", minval=-1.0, maxval=0.0, step=0.05, group="Thresholds")
bool show_table = input.bool(true, "Display Metric HUD Table", group="Visuals")
string table_pos = input.string("top_right", "HUD Position", options=["top_right", "bottom_right", "top_left", "bottom_left"], group="Visuals")
// ---------------- Matching observations ----------------
// Missing benchmark bars remain na; they are never converted to flat returns.
[comp_close, comp_prev, comp_time, comp_end, comp_prev_time, comp_prev_end] = request.security(sym_comp, timeframe.period, [close, close[1], time, time_close, time[1], time_close[1]], gaps=barmerge.gaps_on, lookahead=barmerge.lookahead_off)
bool matching_bar = not na(comp_close) and comp_time == time and comp_end == time_close
bool matching_return = matching_bar and comp_prev_time == time[1] and comp_prev_end == time_close[1] and close > 0 and close[1] > 0 and comp_close > 0 and comp_prev > 0
bool valid_pair = calc_mode == "Log Returns" ? matching_return : matching_bar
float src_main = valid_pair ? (calc_mode == "Log Returns" ? math.log(close / close[1]) : close) : na
float src_comp = valid_pair ? (calc_mode == "Log Returns" ? math.log(comp_close / comp_prev) : comp_close) : na
// Require a full contiguous chart-bar window of valid pairs.
// ta.correlation can skip na values, so mask incomplete windows explicitly.
float raw_fast = ta.correlation(src_main, src_comp, len_fast)
float raw_slow = ta.correlation(src_main, src_comp, len_slow)
bool fast_ready = ta.sma(valid_pair ? 1.0 : 0.0, len_fast) == 1.0
bool slow_ready = ta.sma(valid_pair ? 1.0 : 0.0, len_slow) == 1.0
float corr_fast = fast_ready and barstate.isconfirmed ? raw_fast : na
float corr_slow = slow_ready and barstate.isconfirmed ? raw_slow : na
// ---------------- Visual Reference Bands ----------------
h_top = hline(1.0, "Perfect Positive (+1.0)", color=color.new(color.gray, 60), linestyle=hline.style_dotted)
h_pos = hline(thresh_pos, "Positive Threshold", color=color.new(#10b981, 40), linestyle=hline.style_dashed)
h_zero = hline(0.0, "Zero Correlation (0.0)", color=color.new(color.gray, 50), linestyle=hline.style_solid)
h_neg = hline(thresh_neg, "Negative Threshold", color=color.new(#ef4444, 40), linestyle=hline.style_dashed)
h_bottom = hline(-1.0, "Perfect Negative (-1.0)", color=color.new(color.gray, 60), linestyle=hline.style_dotted)
fill(h_pos, h_top, color=color.new(#10b981, 90), title="Direct Co-Movement Zone")
fill(h_neg, h_bottom, color=color.new(#ef4444, 90), title="Negative Correlation Zone")
// ---------------- Plotting Rolling Correlation Series ----------------
plot(corr_slow, title="Slow Correlation", color=color.new(#f59e0b, 10), linewidth=2)
plot(corr_fast, title="Fast Correlation", color=#3b82f6, linewidth=2)
// Highlight the difference between the two sample estimates
p_fast = plot(corr_fast, title="Fast Line Invis", display=display.none)
p_slow = plot(corr_slow, title="Slow Line Invis", display=display.none)
fill(p_fast, p_slow, color=corr_fast >= corr_slow ? color.new(#3b82f6, 88) : color.new(#f59e0b, 88), title="Fast vs Slow Spread Fill")
// ---------------- HUD Dashboard Table ----------------
var table_pos_val = position.top_right
if table_pos == "bottom_right"
table_pos_val := position.bottom_right
else if table_pos == "top_left"
table_pos_val := position.top_left
else if table_pos == "bottom_left"
table_pos_val := position.bottom_left
var table hud = table.new(table_pos_val, 2, 5, bgcolor=color.new(#0f172a, 15), border_color=#334155, border_width=1)
f_cell_color(float val) =>
val >= thresh_pos ? color.new(#10b981, 70) : val <= thresh_neg ? color.new(#ef4444, 70) : color.new(#334155, 70)
if barstate.islast and show_table
// Header
table.cell(hud, 0, 0, "Benchmark", bgcolor=#1e293b, text_color=color.white, text_size=size.small)
table.cell(hud, 1, 0, sym_comp, bgcolor=#1e293b, text_color=#38bdf8, text_size=size.small)
// Fast window row
table.cell(hud, 0, 1, "Fast (" + str.tostring(len_fast) + "b)", text_color=color.white, text_size=size.small)
table.cell(hud, 1, 1, str.tostring(corr_fast, "#.##"), bgcolor=f_cell_color(corr_fast), text_color=color.white, text_size=size.small)
// Slow window row
table.cell(hud, 0, 2, "Slow (" + str.tostring(len_slow) + "b)", text_color=color.white, text_size=size.small)
table.cell(hud, 1, 2, str.tostring(corr_slow, "#.##"), bgcolor=f_cell_color(corr_slow), text_color=color.white, text_size=size.small)
// State row
string regime_text = na(corr_fast) or na(corr_slow) ? "Incomplete / undefined" : corr_fast >= thresh_pos and corr_slow >= thresh_pos ? "Strong Positive" : corr_fast <= thresh_neg and corr_slow <= thresh_neg ? "Negative Correlation" : math.abs(corr_fast - corr_slow) >= 0.5 ? "Window Difference" : "Between Thresholds"
table.cell(hud, 0, 3, "Sample State", text_color=color.white, text_size=size.small)
table.cell(hud, 1, 3, regime_text, text_color=#f1f5f9, text_size=size.small)
// Mode
table.cell(hud, 0, 4, "Mode", text_color=color.white, text_size=size.small)
table.cell(hud, 1, 4, calc_mode, text_color=#cbd5e1, text_size=size.small)
// ---------------- Alerts ----------------
bool positive_cross = ta.crossover(corr_fast, thresh_pos)
bool negative_cross = ta.crossunder(corr_fast, thresh_neg)
bool zero_cross = ta.cross(corr_fast, 0.0)
alertcondition(barstate.isconfirmed and positive_cross, "Positive threshold cross", "Fast correlation crossed above the selected positive threshold.")
alertcondition(barstate.isconfirmed and negative_cross, "Negative threshold cross", "Fast correlation crossed below the selected negative threshold.")
alertcondition(barstate.isconfirmed and zero_cross, "Zero cross", "Fast correlation crossed zero.")
To check the calculation, use the chart symbol as the comparison: a complete, nonconstant sample should be approximately +1. Then change only the comparison symbol. In price mode, compare against the built-in CC with identical length, source and session settings on a gap-free interval. These are suggested checks, not reported live-chart test results.
To receive notifications, create an alert in TradingView and select one of the script's conditions. The conditions wait for chart-bar confirmation; they do not send alerts automatically when the script is added. Recreate an alert after changing inputs so it uses the intended settings. See TradingView's alert documentation.
Correlation indicator questions
Customize the sample rules in Pineify
Ask the Pine Script AI Coding Agent to adapt the windows, symbols, missing-data policy or alert thresholds. State those choices explicitly and review the output in TradingView. For the wider research question behind an asset relationship, explore AI Finance Agent.
Educational analysis only, not investment advice. Historical correlation can change and does not guarantee diversification, hedge protection or profitable trading.