Ты можешь сделать скринер стационарности по индикатору написанному на PINESCRIPt 5, Нужно точно повторить функционал индикатора (кроме таблицы) и искать стационарности именно так как их ищет индикатор.
Учти, что у индикатора есть параметр длинны (240) как у мувингов, а стационарность - это значение индикатора <-2.574
Код индикатора:
//@version=5
indicator(title = 'Augmented Dickey–Fuller (ADF) mean reversion test', shorttitle = 'ADF', overlay = false, format = format.price, precision = 3, max_bars_back = 5000, max_lines_count = 500)

//Functions import
matrixGet(float [] A, int i, int j, int nRows) =>
// @function: Get the value of the element of an implied 2d matrix
// @parameters:
// A :: float[] array: pseudo 2d matrix A = [[column_0],[column_1],...,[column(n-1)]]
// i :: integer: row number
// j :: integer: column number
// nRows :: integer: number of rows in the implied 2d matrix
array.get(id = A, index = i + nRows * j)

matrixSet(float [] A, float value, int i, int j, int nRows) =>
// @function: Set a value to the element of an implied 2d matrix
// @parameters:
// A :: float[] array, changed on output: pseudo 2d matrix A = [[column_0],[column_1],...,[column(n-1)]]
// value :: float: the new value to be set
// i :: integer: row number
// j :: integer: column number
// nRows :: integer: number of rows in the implied 2d matrix
array.set(id = A, index = i + nRows * j, value = value)
A

transpose(float[] A, int nRows, int nColumns) =>
// @function: Transpose an implied 2d matrix
// @parameters:
// A :: float[] array: pseudo 2d matrix A = [[column_0],[column_1],...,[column_(n-1)]]
// nRows :: integer: number of rows in A
// nColumns :: integer: number of columns in A
// @returns:
// AT :: float[] array: pseudo 2d matrix with implied dimensions: ncolums x nRows
float[] at = array.new_float(size = nRows * nColumns, initial_value = 0)
for i = 0 to nRows - 1
for j = 0 to nColumns - 1
matrixSet(A = at, value = matrixGet(A, i, j, nRows), i = j, j = i, nRows = nColumns)
at

multiply(float[] A, float[] B, int nRowsA, int nColumnsA, int nColumnsB) =>
// @function: Calculate scalar product of two matrices
// @parameters:
// A :: float[] array: pseudo 2d matrix
// B :: float[] array: pseudo 2d matrix
// nRowsA :: integer: number of rows in A
// nColumnsA :: integer: number of columns in A
// nColumnsB :: integer: number of columns in B
// @returns:
// C :: float[] array: pseudo 2d matrix with implied dimensions _nRowsA x _nColumnsB
float[] C = array.new_float(size = nRowsA * nColumnsB, initial_value = 0)
int nRowsB = nColumnsA
float elementC = 0.0
for i = 0 to nRowsA - 1
for j = 0 to nColumnsB - 1
elementC := 0
for k = 0 to nColumnsA - 1
elementC += matrixGet(A = A, i = i, j = k, nRows = nRowsA) * matrixGet(A = B, i = k, j = j, nRows = nRowsB)
matrixSet(A = C, value = elementC, i = i, j = j, nRows = nRowsA)
C

vNorm(float[] x) =>
// @function: Square norm of vector X with size n
// @parameters:
// X :: float[] array, vector
// @returns :
// norm :: float, square norm of X
int n = array.size(id = x)
float norm = 0.0
for i = 0 to n - 1
norm += math.pow(base = array.get(id = x, index = i), exponent = 2)
math.sqrt(number = norm)

qrDiag(float[] A, int nRows, int nColumns) =>
// @function: QR Decomposition with Modified Gram-Schmidt Algorithm (Column-Oriented)
// @parameters:
// A :: float[] array: pseudo 2d matrix A = [[column_0],[column_1],...,[column_(n-1)]]
// nRows :: integer: number of rows in A
// nColumns :: integer: number of columns in A
// @returns:
// Q :: float[] array, unitary matrix, implied dimenstions nRows x nColumns
// R :: float[] array, upper triangular matrix, implied dimansions nColumns x nColumns
float[] Q = array.new_float(size = nRows * nColumns, initial_value = 0)
float[] R = array.new_float(size = nColumns * nColumns, initial_value = 0)
float[] a = array.new_float(size = nRows, initial_value = 0)
float[] q = array.new_float(size = nRows, initial_value = 0)
float r = 0.0
float aux = 0.0
for i = 0 to nRows - 1
array.set(id = a, index = i, value = matrixGet(A = A, i = i, j = 0, nRows = nRows))
r := vNorm(x = a)
matrixSet(A = R, value = r, i = 0, j = 0, nRows = nColumns)
for i = 0 to nRows - 1
matrixSet(A = Q, value = array.get(id = a, index = i) / r, i = i, j = 0, nRows = nRows)
if nColumns != 1
for k = 1 to nColumns - 1
for i = 0 to nRows - 1
array.set(id = a, index = i, value = matrixGet(A = A, i = i, j = k, nRows = nRows))
for j = 0 to k - 1 by 1
r := 0
for i = 0 to nRows - 1
r += matrixGet(A = Q, i = i, j = j, nRows = nRows) * array.get(id = a, index = i)
matrixSet(A = R, value = r, i = j, j = k, nRows = nColumns)
for i = 0 to nRows - 1
aux := array.get(id = a, index = i) - r * matrixGet(A = Q, i = i, j = j, nRows = nRows)
array.set(id = a, index = i, value = aux)
r := vNorm(x = a)
matrixSet(A = R, value = r, i = k, j = k, nRows = nColumns)
for i = 0 to nRows - 1
matrixSet(A = Q,value = array.get(id = a, index = i) / r, i = i, j = k, nRows = nRows)
[Q, R]

pInv(float[] A, int nRows, int nColumns) =>
// @function: Pseudoinverse of matrix A calculated using QR decomposition
// @parameters:
// A :: float[] array: implied as a (nRows x nColumns) matrix A = [[column_0],[column_1],...,[column_(nColumns-1)]]
// nRows :: integer: number of rows in A
// nColumns :: integer: number of columns in A
// @returns:
// aInv :: float[] array implied as a (nColumns x nRows) matrix A = [[row_0],[row_1],...,[row(_nRows-1)]]
[Q, R] = qrDiag(A = A, nRows = nRows, nColumns = nColumns)
float[] QT = transpose(A = Q, nRows = nRows, nColumns = nColumns)
var rinv = array.new_float(size = nColumns * nColumns, initial_value = 0)
float r = 0.0
matrixSet(A = rinv, value = 1 / matrixGet(R, 0, 0, nColumns), i = 0, j = 0, nRows = nColumns)
if nColumns != 1
for j = 1 to nColumns - 1
for i = 0 to j - 1
r := 0.0
for k = i to j - 1
r += matrixGet(A = rinv, i = i, j = k, nRows = nColumns) * matrixGet(A = R, i = k, j = j, nRows = nColumns)
matrixSet(A = rinv, value = r, i = i, j = j, nRows = nColumns)
for k = 0 to j - 1
matrixSet(A = rinv, value = -matrixGet(A = rinv, i = k, j = j, nRows = nColumns) / matrixGet(A = R, i = j, j = j, nRows = nColumns), i = k, j = j, nRows = nColumns)
matrixSet(A = rinv, value = 1 / matrixGet(A = R, i = j, j = j, nRows = nColumns), i = j, j = j, nRows = nColumns)
float[] aInv = multiply(A = rinv, B = QT, nRowsA = nColumns, nColumnsA = nColumns, nColumnsB = nRows)
aInv

adfTest(float[] a, int nLag, string confidence) =>
// @function: Augmented Dickey-Fuller unit root test.
// @parameters:
// a :: float[], array containing the data series to test
// Lag :: int, maximum lag included in test
// @returns:
// adf :: float, the test statistic
// crit :: float, critical value for the test statistic at the 10 % levels
// nobs :: int, the number of observations used for the ADF regression and calculation of the critical values
if nLag >= array.size(id = a) / 2 - 2
runtime.error('ADF: Maximum lag must be less than (Length / 2 - 2)')
int nobs = array.size(id = a) - nLag - 1
float[] y = array.new_float(na)
float[] x = array.new_float(na)
float[] x0 = array.new_float(na)
for i = 0 to nobs-1
array.push(id = y, value = array.get(id = a, index = i) - array.get(id = a, index = i + 1))
array.push(id = x, value = array.get(id = a, index = i + 1))
array.push(id = x0, value = 1.0)
float[] X = array.copy(id = x)
int M = 2
X := array.concat(id1 = X, id2 = x0)
if nLag > 0
for n = 1 to nLag
float[] xl = array.new_float(na)
for i = 0 to nobs-1
array.push(id = xl, value = array.get(id = a, index = i + n) - array.get(id = a, index = i + n + 1))
X := array.concat(id1 = X, id2 = xl)
M += 1
float[] c = pInv(A = X, nRows = nobs, nColumns = M)
float[] coeff = multiply(A = c, B = y, nRowsA = M, nColumnsA = nobs, nColumnsB = 1)
float[] yHat = multiply(A = X, B = coeff, nRowsA = nobs, nColumnsA = M, nColumnsB = 1)
float meanX = array.avg(id = x)
float sum1 = 0.0
float sum2 = 0.0
for i = 0 to nobs - 1
sum1 += math.pow(base = array.get(id = y, index = i) - array.get(id = yHat, index = i), exponent = 2) / (nobs - M)
sum2 += math.pow(base = array.get(id = x, index = i) - meanX, exponent = 2)
float se = math.sqrt(number = sum1 / sum2)
float adf = array.get(id = coeff, index = 0) / se
float crit = switch
confidence == '90%' => -2.56677 - 1.5384 / nobs - 2.809 / nobs / nobs
confidence == '95%' => -2.86154 - 2.8903 / nobs - 4.234 / nobs / nobs - 40.040 / nobs / nobs / nobs
confidence == '99%' => -3.43035 - 6.5393 / nobs - 16.786 / nobs / nobs - 79.433 / nobs / nobs / nobs
[adf, crit, nobs]

//User input
float sourceInput = input.source(title = 'Source', defval = close)
var int lookback = input.int(title = 'Length', defval = 240, minval = 2, tooltip = 'The test is applied in a moving window. Length defines the number of points in the sample.')
var int nLagInput = input.int(title = 'Maximum lag', defval = 0, minval = 0, tooltip = 'Maximum lag which is included in test. Generally, lags allow taking into account serial correlation of price changes.')
var string confidenceInput = input.string(title = 'Confidence level', defval = '90%', options = ['90%', '95%', '99%'], tooltip = 'Defines at which confidence level the critical value of the ADF test statistic is calculated. If the test statistic is below the critical value, the time series sample is concluded to be mean-reverting.')

//Market data parsing
float[] a = array.new_float(na)
for i = 0 to lookback - 1
array.push(id = a, value = sourceInput[i])

//Calculations
[tauADF, crit, nobs] = adfTest(a, nLagInput, confidenceInput)

//Plot
infobox = table.new(position = 'bottom_right', columns = 2, rows = 3, bgcolor = color.rgb(red = 149, green = 152, blue = 161, transp = 80), frame_color = color.rgb(red = 149, green = 152, blue = 161, transp = 20), frame_width = 1)
table.cell(table_id = infobox, column = 0, row = 0, text = 'Test statistic:', text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_halign = text.align_left, text_size = size.small)
table.cell(table_id = infobox, column = 0, row = 1, text = confidenceInput + ' critical value:', text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_halign = text.align_left, text_size = size.small)
table.cell(table_id = infobox, column = 0, row = 2, text = 'Mean meverting?', text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_halign = text.align_left, text_size = size.small)
table.cell(table_id = infobox, column = 1, row = 0, text = str.format('{0, number, #.####}',tauADF), text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_size = size.small)
table.cell(table_id = infobox, column = 1, row = 1, text = str.format('{0, number, #.####}',crit), text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_size = size.small)
table.cell(table_id = infobox, column = 1, row = 2, text = (tauADF < crit) ? 'Yes' : 'No', text_color = color.rgb(red = 149, green = 152, blue = 161, transp = 0), text_size = size.small)

color tauColor = switch
tauADF <= crit => #5b95aa
tauADF > crit => #b2182c
=> na
hline(price = 0, title = 'Zeroline', color = color.rgb(red = 149, green = 152, blue = 161, transp = 50), linestyle = hline.style_dotted)
hline(price = 1, title = 'ONE', color = #ff000080, linestyle = hline.style_dotted)
hline(price = 3, title = '3', color = #ff000080, linestyle = hline.style_dotted)
hline(price = 5, title = '5', color = #ff000080, linestyle = hline.style_dotted)
plot(series = crit, color = color.rgb(red = 149, green = 152, blue = 161, transp = 50), title = 'Critical value')
plot(series = tauADF, color = tauColor, title = 'Test statistic', style = plot.style_line)