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.
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.")