TradingView regression calculations

R-squared indicator: measure fit, then read the slope

An R-squared indicator measures how closely prices follow a fitted line within a chosen window. A perfectly straight rise and a perfectly straight fall both have R² = 1. Read the slope separately for direction. Here, each rolling window uses ordinary least squares with an intercept, equal bar spacing and equal weights. R² describes that historical fit; it does not measure the probability that the next price move will continue it.

See the decision guide

Define the regression before interpreting its score

Let x = 0, 1, …, N − 1 from the oldest to newest chart bar, and y be the selected source. Fit yhat = a + bx using all N observations. The intercept a is estimated along with slope b. Refit when a bar closes and the window advances. This page uses raw price levels, not returns or log prices.

  • For a dollar-denominated close, b is dollars per bar and SSE and SST have squared-dollar units. R² = 0.85 can be shown as 85%, but that is neither an 85% return nor an 85% win probability.
  • Each bar gets one x step. Calendar gaps, session breaks and range bars are not equal elapsed-time intervals. Regressing on timestamps instead changes the model when spacing is irregular.
  • Multiplying every price by a nonzero constant leaves R² unchanged; slope changes with that scale. Changing from prices to logarithms or returns changes the observations and generally changes R².
QuantityCalculationInterpretation
SxxΣ(x − mean(x))² = N(N² − 1)/12Variation in the bar index
SxyΣ(x − mean(x))(y − mean(y))Signed joint variation
Slope bSxy / SxxSource units per chart bar
Intercept amean(y) − b × mean(x)Source units; fitted value at the oldest bar
SSTΣ(y − mean(y))²Total variation around the window mean
SSEΣ(y − a − bx)²Residual variation around this same fitted line
R²1 − SSE / SST, when SST > 0Dimensionless fraction of variation accounted for in this window

Recalculate one window and the next

Use the hypothetical sequence 100, 102, 101, 105, 107, 106. For the first five values, mean(x) = 2, mean(y) = 103, Sxx = 10 and Sxy = 17. Thus b = 1.7 and a = 99.6. The following table uses one fitted line for all five residuals.

  • SSE = 5.1 and SST = 9 + 1 + 4 + 4 + 16 = 34. R² = 1 − 5.1/34 = 0.85. The slope is positive even though the middle price fell.
  • Roll to 102, 101, 105, 107, 106 and reset x to 0…4. Now a = 101.4, b = 1.4, SST = 26.8, SSE = 7.2 and R² = 49/67 = 0.731343. Direction remains upward while fit quality falls.
  • Do not sum historical prices minus each bar's own rolling regression endpoint. Those residuals come from different fitted lines and will not reproduce this SSE.
xPriceFitted valueResidualResidual squared
010099.60.40.16
1102101.30.70.49
2101103.0-2.04.00
3105104.70.30.09
4107106.40.60.36

When correlation squared is the same calculation

For ordinary unweighted least squares with one predictor and an estimated intercept, evaluated on the same observations used to fit it, R² equals Pearson corr(x, y)². Require nonzero variation in both x and y. With complete consecutive bars, using bar_index instead of local x only adds a constant, so the correlation is unchanged.

  • The algebra is direct: SSE = SST − Sxy²/Sxx, hence 1 − SSE/SST = Sxy²/(Sxx × SST) = corr(x, y)². This is an identity for these data, not an assumption that errors are normal or independent.
  • For the first worked window, correlation = 17/sqrt(10 × 34) = 0.921954 and its square is 0.85. The perfectly falling fixture has correlation -1 and R² = 1; squaring removes the sign.
  • The identity does not justify substituting correlation squared into a no-intercept, weighted, nonlinear, multiple-predictor or out-of-sample score. For x = [0,1,2], y = [1,2,3], a no-intercept fit has b = 1.6 and SSE = 1.2. Using the centered SST = 2 gives 1 − SSE/SST = 0.4, while corr(x,y)² = 1.
  • Missing-value rules must also match. The script masks the correlation comparison unless every bar in the exact N-bar window is valid. It never combines a skipped-observation correlation window with a fixed-bar residual window.

Make undefined windows visible

A flat price window has SST = 0 and SSE = 0. The expression 1 − 0/0 is undefined. This implementation returns na for R², retains the valid slope of zero, and emits no high-fit alert. Assigning zero or one would be a display convention with a different meaning, so neither is used here.

  • After a missing value, output resumes when the latest N consecutive source values are all valid. A chart session gap with no bar is not itself a source na; x still advances once per existing bar.
  • The calculation centers prices after subtracting the newest source value to reduce cancellation from large price levels. It tests SST > 0 without a price-dependent epsilon. Extremely small differences may still be lost in floating-point representation.
  • Only values within 1e-9 of the mathematical range [0,1] are accepted, then clipped to that range for roundoff. Values outside that tolerance remain na. Clipping is not a repair for mismatched data or a different model.
CaseExplicit policyReason
N = 1RejectedSxx is zero; slope cannot be estimated.
N = 2Rejected by the indicator inputTwo distinct prices always fit exactly. The algebra works, but fit quality has no residual discrimination.
N from 3 to 500Allowed in Live modeAt least one residual degree of freedom; this is an implementation range, not an optimal trading window.
Fewer than N loaded barsAll fit outputs are naNo partial-window estimate during warmup.
Any source value is naReject that exact windowNo zero fill, forward fill, skipped observations or extended lookback.
Constant complete windowSlope = 0; intercept = constant; R² = naNo variation against which to measure explained variation.
Open chart barPlots are na and alerts are falseOnly the final close-time calculation is published.

A complete Pine Script v6 indicator with test modes

Paste this original indicator into TradingView Pine Editor and add it to a chart. Live mode defaults to close over 20 bars. Test modes override the source and use five bars. The pane plots R² in teal for a positive slope, orange for a negative slope and gray for zero slope. Inspect the slope, intercept, sums of squares and correlation comparison in the Data Window.

  • Plots and alert conditions require barstate.isconfirmed. No future references, higher-timeframe requests, backdated markers or historical line redraws are used. With a stable chart source and unchanged data/settings, published closed-bar values use information available at that close.
  • An external indicator selected as Price source can have its own repainting behavior; this script cannot remove it. Data-feed revisions and changes to the dataset or chart settings can also change a recalculated history.
  • Create an alert from either named condition and select Once Per Bar Close. Each condition describes a qualifying window and may fire on consecutive closes. It is not an entry instruction or a crossing-only signal.
  • The default threshold 0.8 is an editable example, not a significance cutoff or a tested trading rule. Syntax validation checks compilation, not financial usefulness or the external source's behavior.
Chart-timeframe OLS; final source passed the Pineify syntax checker on October 18, 2026.pine
//@version=6
indicator("Rolling R-squared: fit and direction", overlay=false, precision=6, max_bars_back=500)
price = input.source(close, "Price source")
length = input.int(20, "Live window", minval=3, maxval=500)
threshold = input.float(0.8, "Descriptive high-fit threshold", minval=0, maxval=1, step=0.05)
mode = input.string("Live", "Data mode", options=["Live", "Rising", "Falling", "Noisy", "Alternating", "Constant", "Missing", "Outlier"])
// Fixtures repeat every eight bars. Fixture windows always use N = 5.
phase = bar_index % 8
var noisy = array.from(100.0, 102.0, 101.0, 105.0, 107.0, 106.0, 108.0, 109.0)
var outlier = array.from(100.0, 102.0, 104.0, 106.0, 130.0, 110.0, 112.0, 114.0)
source = switch mode
    "Rising" => 100.0 + 2.0 * phase
    "Falling" => 108.0 - 2.0 * phase
    "Noisy" => array.get(noisy, phase)
    "Alternating" => 100.0 + 2.0 * (phase % 2)
    "Constant" => 100.0
    "Missing" => phase == 2 ? na : array.get(noisy, phase)
    "Outlier" => array.get(outlier, phase)
    => price
n = mode == "Live" ? length : 5
// x increases from oldest = 0 to newest = n - 1.
complete = bar_index >= n - 1
base = source
sumShift = 0.0
for i = 0 to n - 1
    if na(source[i])
        complete := false
    else
        sumShift += source[i] - base
float slope = na
float intercept = na
float sst = na
float sse = na
float rSquared = na
if complete
    meanShift = sumShift / n
    meanX = (n - 1) / 2.0
    sxx = n * (n * n - 1) / 12.0
    sxy = 0.0
    sst := 0.0
    for i = 0 to n - 1
        dx = (n - 1 - i) - meanX
        dy = (source[i] - base) - meanShift
        sxy += dx * dy
        sst += dy * dy
    slope := sxy / sxx
    intercept := base + meanShift - slope * meanX
    sse := 0.0
    for i = 0 to n - 1
        dx = (n - 1 - i) - meanX
        dy = (source[i] - base) - meanShift
        residual = dy - slope * dx
        sse += residual * residual
    // A constant window has a valid zero slope, but undefined R-squared.
    if sst > 0
        raw = 1.0 - sse / sst
        if raw >= -1e-9 and raw <= 1.0 + 1e-9
            rSquared := math.max(0.0, math.min(1.0, raw))
// Evaluate on every bar, but compare only identical complete windows.
r = ta.correlation(float(bar_index), source, n)
corrSquared = complete and sst > 0 ? r * r : na
confirmed = barstate.isconfirmed
fitColor = slope > 0 ? color.teal : slope < 0 ? color.orange : color.gray
plot(confirmed ? rSquared : na, "R-squared", color=fitColor, linewidth=2, style=plot.style_linebr)
hline(threshold, "High-fit threshold (not significance)", color=color.gray)
hline(0, "Zero", color=color.gray)
hline(1, "One", color=color.gray)
plot(confirmed ? slope : na, "Slope: source units per bar", display=display.data_window)
plot(confirmed ? intercept : na, "Intercept: source units", display=display.data_window)
plot(confirmed ? sst : na, "SST", display=display.data_window)
plot(confirmed ? sse : na, "SSE", display=display.data_window)
plot(confirmed ? corrSquared : na, "Correlation squared", display=display.data_window)
plot(confirmed ? rSquared - corrSquared : na, "R-squared minus correlation squared", display=display.data_window)
plot(confirmed ? phase : na, "Fixture phase", display=display.data_window)
highFit = confirmed and not na(rSquared) and rSquared >= threshold
alertcondition(highFit and slope > 0, "High-fit rising window", "Confirmed R-squared meets threshold and slope is positive.")
alertcondition(highFit and slope < 0, "High-fit falling window", "Confirmed R-squared meets threshold and slope is negative.")

Reproduce the fixtures without market data

Select a test mode, then hover a closed bar whose Fixture phase is 4 in the Data Window. The eight-bar pattern repeats, and phase 4 always ends the five observations shown below. At phase 5, Noisy advances by one observation. The first four bars of the loaded dataset remain unavailable; do not use the open bar for comparisons.

  • With threshold 0.8, Rising and Noisy at phase 4 satisfy the rising condition, Falling satisfies the falling condition, and Alternating and Constant satisfy neither. Missing becomes eligible again at phase 7 with positive slope and R² = 0.81.
  • The Noisy eight-value cycle is [100,102,101,105,107,106,108,109]. Missing uses that cycle with phase 2 replaced by na. Outlier uses [100,102,104,106,130,110,112,114]. Rising is 100 + 2 × phase; Falling is 108 − 2 × phase; Alternating is 100 + 2 × (phase modulo 2). Constant is 100.
  • Compare finite R² with Correlation squared to about 1e-6 in the displayed values. At constant or missing windows, both must be unavailable. Dataset loading can shift bar_index and thus fixture phase; identify the displayed phase rather than a calendar date.
Mode / phaseOldest → newest pricesa / bSST / SSER²
Rising / 4100,102,104,106,108100 / 240 / 01
Falling / 4108,106,104,102,100108 / -240 / 01
Noisy / 4100,102,101,105,10799.6 / 1.734 / 5.10.85
Noisy / 5102,101,105,107,106101.4 / 1.426.8 / 7.20.731343
Alternating / 4100,102,100,102,100100.8 / 04.8 / 4.80
Constant / 4100,100,100,100,100100 / 00 / 0na
Missing / 4100,102,na,105,107na / nana / nana
Missing / 7105,107,106,108,109105.2 / 0.910 / 1.90.81
Outlier / 4100,102,104,106,13095.6 / 6.4603.2 / 193.60.679045

A high score cannot establish a future edge

The fitted prices and residuals come from observations already known. Price levels can be nonstationary and serially dependent. A random walk can show a convincing local trend without predictable future increments. The descriptive R² calculation remains defined, but ordinary independent-error significance claims do not follow from it.

  • One outlier can alter both the fitted line and the denominator. Replacing the last value of the perfect Rising fixture with 130 increases slope from 2 to 6.4 while reducing R² from 1 to 0.679045. A steeper line can fit less well.
  • A low R² only indicates a weak straight-line fit over this window. Alternating or curved paths may contain structure that this model does not describe. A score near zero does not prove randomness.
  • Window choice changes both observations and the fitted line. Choosing the best-looking length after inspecting the same history adds selection bias. Freeze candidate rules before evaluating later, untouched periods; include trading costs if turning the filter into a strategy.
  • Source, timeframe, session and length belong in every comparison. R² is scale-free, but equal scores from different windows do not imply equal slope, volatility, risk or forecast quality. These fixtures verify arithmetic only.
Where Pineify fits

Pine Script AI Coding Agent

Use Pineify to draft or revise a Pine indicator from this explicit OLS specification. Supply the window, missing-value policy, confirmation rule and fixture outputs, then inspect the generated source and compare its results before adding strategy rules.

Draft an R-squared indicator

Frequently asked questions

Educational information, not investment advice. All fixture prices are hypothetical. A fitted trend and a successful syntax check do not demonstrate forecast accuracy or profitable trading.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.