Maurizio-Ciullo

Augmented Dickey–Fuller (ADF) test

Jun 28th, 2022 (edited)
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // The augmented Dickey–Fuller (ADF) test is a test of the tendency of a price series sample to mean revert.
  2. // In this script, the ADF test is applied in a rolling window with a user-defined lookback length.
  3. // The computed values of the ADF test statistic are plotted as a time series.
  4. // If the calculated test statistic is smaller than the critical value calculated at the certain confidence
  5. // level (90%, 95% or 99%), then the hypothesis about the mean reversion is accepted (strictly speaking,
  6. // the opposite hypothesis is rejected).
  7.  
  8. //@version=5
  9. indicator('Augmented Dickey–Fuller (ADF) mean reversion test', shorttitle='ADF', overlay=false, max_bars_back=5000, max_lines_count=500)
  10.  
  11. src       = input.source(title='Source',            defval=close)
  12. lookback  = input.int(title='Length',               defval=100, minval = 2, tooltip = 'The test is applied in a moving window. Length defines the number of points in the sample.')
  13. nLag      = 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.')
  14. conf      = 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.')
  15. isInfobox = input.bool(title='Show infobox',        defval=true)
  16.  
  17. // --- functions ---
  18. // To-do: transfer some linear algebra to a separate library, or use public libraries
  19.  
  20. matrix_get(A, i, j, nrows) =>
  21.     // @function: Get the value of the element of an implied 2d matrix
  22.     // @parameters:
  23.     // A     :: float[] array: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
  24.     // i     :: integer: row number
  25.     // j     :: integer: column number
  26.     // nrows :: integer: number of rows in the implied 2d matrix
  27.     array.get(A, i + nrows * j)
  28.  
  29. matrix_set(A, value, i, j, nrows) =>
  30.     // @function: Set a value to the element of an implied 2d matrix
  31.     // @parameters:
  32.     // A     :: float[] array, changed on output: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
  33.     // value :: float: the new value to be set
  34.     // i     :: integer: row number
  35.     // j     :: integer: column number
  36.     // nrows :: integer: number of rows in the implied 2d matrix
  37.     array.set(A, i + nrows * j, value)
  38.     A
  39.  
  40. transpose(A, nrows, ncolumns) =>
  41.     // @function: Transpose an implied 2d matrix
  42.     // @parameters:
  43.     // A        :: float[] array: pseudo 2d matrix A = [[column_0],[column_1],...,[column_(n-1)]]
  44.     // nrows    :: integer: number of rows in A
  45.     // ncolumns :: integer: number of columns in A
  46.     // @returns:
  47.     // AT       :: float[] array: pseudo 2d matrix with implied dimensions: ncolums x nrows
  48.     float[]  AT = array.new_float(nrows * ncolumns, 0)
  49.     for i = 0 to nrows - 1
  50.         for j = 0 to ncolumns - 1
  51.             matrix_set(AT, matrix_get(A, i, j, nrows), j, i, ncolumns)
  52.     AT
  53.  
  54. multiply(A, B, nrowsA, ncolumnsA, ncolumnsB) =>
  55.     // @function: Calculate scalar product of two matrices
  56.     // @parameters:
  57.     // A         :: float[] array: pseudo 2d matrix
  58.     // B         :: float[] array: pseudo 2d matrix
  59.     // nrowsA    :: integer: number of rows in A
  60.     // ncolumnsA :: integer: number of columns in A
  61.     // ncolumnsB :: integer: number of columns in B
  62.     // @returns:
  63.     // C         :: float[] array: pseudo 2d matrix with implied dimensions _nrowsA x _ncolumnsB
  64.     float[]      C = array.new_float(nrowsA * ncolumnsB, 0)
  65.     int     nrowsB = ncolumnsA
  66.     float elementC = 0.0
  67.     for i = 0 to nrowsA - 1
  68.         for j = 0 to ncolumnsB - 1
  69.             elementC := 0
  70.             for k = 0 to ncolumnsA - 1
  71.                 elementC += matrix_get(A, i, k, nrowsA) * matrix_get(B, k, j, nrowsB)
  72.             matrix_set(C, elementC, i, j, nrowsA)
  73.     C
  74.  
  75. vnorm(X) =>
  76.     // @function: Square norm of vector X with size n
  77.     // @parameters:
  78.     // X        :: float[] array, vector
  79.     // @returns :
  80.     // norm     :: float, square norm of X
  81.     int   n    = array.size(X)
  82.     float norm = 0.0
  83.     for i = 0 to n - 1
  84.         norm += math.pow(array.get(X, i), 2)
  85.     math.sqrt(norm)
  86.  
  87. qr_diag(A, nrows, ncolumns) =>
  88.     // @function: QR Decomposition with Modified Gram-Schmidt Algorithm (Column-Oriented)
  89.     // @parameters:
  90.     // A        :: float[] array: pseudo 2d matrix A = [[column_0],[column_1],...,[column_(n-1)]]
  91.     // nrows    :: integer: number of rows in A
  92.     // ncolumns :: integer: number of columns in A
  93.     // @returns:
  94.     // Q        :: float[] array, unitary matrix, implied dimenstions nrows x ncolumns
  95.     // R        :: float[] array, upper triangular matrix, implied dimansions ncolumns x ncolumns
  96.     float[] Q = array.new_float(nrows * ncolumns, 0)
  97.     float[] R = array.new_float(ncolumns * ncolumns, 0)
  98.     float[] a = array.new_float(nrows, 0)
  99.     float[] q = array.new_float(nrows, 0)
  100.     float   r = 0.0
  101.     float aux = 0.0
  102.     //get first column of _A and its norm:
  103.     for i = 0 to nrows - 1
  104.         array.set(a, i, matrix_get(A, i, 0, nrows))
  105.     r := vnorm(a)
  106.     //assign first diagonal element of R and first column of Q
  107.     matrix_set(R, r, 0, 0, ncolumns)
  108.     for i = 0 to nrows - 1
  109.         matrix_set(Q, array.get(a, i) / r, i, 0, nrows)
  110.     if ncolumns != 1
  111.         //repeat for the rest of the columns
  112.         for k = 1 to ncolumns - 1
  113.             for i = 0 to nrows - 1
  114.                 array.set(a, i, matrix_get(A, i, k, nrows))
  115.             for j = 0 to k - 1 by 1
  116.                 //get R_jk as scalar product of Q_j column and A_k column:
  117.                 r := 0
  118.                 for i = 0 to nrows - 1
  119.                     r += matrix_get(Q, i, j, nrows) * array.get(a, i)
  120.                 matrix_set(R, r, j, k, ncolumns)
  121.                 //update vector _a
  122.                 for i = 0 to nrows - 1
  123.                     aux := array.get(a, i) - r * matrix_get(Q, i, j, nrows)
  124.                     array.set(a, i, aux)
  125.             //get diagonal R_kk and Q_k column
  126.             r := vnorm(a)
  127.             matrix_set(R, r, k, k, ncolumns)
  128.             for i = 0 to nrows - 1
  129.                 matrix_set(Q, array.get(a, i) / r, i, k, nrows)
  130.     [Q, R]
  131.  
  132. pinv(A, nrows, ncolumns) =>
  133.     // @function: Pseudoinverse of matrix A calculated using QR decomposition
  134.     // @parameters:
  135.     // A        :: float[] array: implied as a (nrows x ncolumns) matrix A = [[column_0],[column_1],...,[column_(_ncolumns-1)]]
  136.     // nrows    :: integer: number of rows in A
  137.     // ncolumns :: integer: number of columns in A
  138.     // @returns:
  139.     // Ainv     :: float[] array implied as a (ncolumns x nrows)  matrix A = [[row_0],[row_1],...,[row_(_nrows-1)]]
  140.     //
  141.     // First find the QR factorization of A: A = QR, where R is upper triangular matrix. Then do Ainv = R^-1*Q^T.
  142.     [Q, R]     = qr_diag(A, nrows, ncolumns)
  143.     float[] QT = transpose(Q, nrows, ncolumns)
  144.     // Calculate Rinv:
  145.     var   Rinv = array.new_float(ncolumns * ncolumns, 0)
  146.     float    r = 0.0
  147.     matrix_set(Rinv, 1 / matrix_get(R, 0, 0, ncolumns), 0, 0, ncolumns)
  148.     if ncolumns != 1
  149.         for j = 1 to ncolumns - 1
  150.             for i = 0 to j - 1
  151.                 r := 0.0
  152.                 for k = i to j - 1
  153.                     r += matrix_get(Rinv, i, k, ncolumns) * matrix_get(R, k, j, ncolumns)
  154.                 matrix_set(Rinv, r, i, j, ncolumns)
  155.             for k = 0 to j - 1
  156.                 matrix_set(Rinv, -matrix_get(Rinv, k, j, ncolumns) / matrix_get(R, j, j, ncolumns), k, j, ncolumns)
  157.             matrix_set(Rinv, 1 / matrix_get(R, j, j, ncolumns), j, j, ncolumns)
  158.     //
  159.     float[] Ainv = multiply(Rinv, QT, ncolumns, ncolumns, nrows)
  160.     Ainv
  161.  
  162. adftest(a, nLag, conf) =>
  163.     // @function: Augmented Dickey-Fuller unit root test.
  164.     // @parameters:
  165.     // a          :: float[], array containing the data series to test
  166.     // Lag        :: int, maximum lag included in test
  167.     // @returns:
  168.     // adf        :: float, the test statistic
  169.     // crit       :: float, critical value for the test statistic at the 10 % levels
  170.     // nobs       :: int, the number of observations used for the ADF regression and calculation of the critical values
  171.     if nLag >= array.size(a)/2 - 2
  172.         runtime.error("ADF: Maximum lag must be less than (Length/2 - 2)")
  173.     int   nobs = array.size(a)-nLag-1
  174.     //
  175.     float[]  y = array.new_float(na)
  176.     float[]  x = array.new_float(na)
  177.     float[] x0 = array.new_float(na)
  178.     //
  179.     for i = 0 to nobs-1
  180.         array.push( y, array.get(a,i)-array.get(a,i+1))             // current difference, dependent variable
  181.         array.push( x, array.get(a,i+1))                            // previous-bar value, predictor (related to tauADF)
  182.         array.push(x0, 1.0)                                         // constant, predictor
  183.     //
  184.     float[] X = array.copy(x)
  185.     int     M = 2
  186.     X := array.concat(X, x0)
  187.     //
  188.     // introduce lags
  189.     if nLag > 0
  190.         for n = 1 to nLag
  191.             float[] xl = array.new_float(na)
  192.             for i = 0 to nobs-1
  193.                 array.push(xl, array.get(a,i+n)-array.get(a,i+n+1))  // lag-n difference, predictor
  194.             X   := array.concat(X, xl)
  195.             M   += 1
  196.     //
  197.     // Regression
  198.     float[] c      = pinv(X, nobs, M)
  199.     float[] coeff  = multiply(c, y, M, nobs, 1)
  200.     //
  201.     // Standard error
  202.     float[] Yhat   = multiply(X,coeff,nobs,M,1)
  203.     float   meanX  = array.avg(x)
  204.     float   sum1   = 0.0            // mean square error (MSE) of regression
  205.     float   sum2   = 0.0            //
  206.     for i = 0 to nobs-1
  207.         sum1  += math.pow(array.get(y,i) - array.get(Yhat,i), 2)/(nobs-M)
  208.         sum2  += math.pow(array.get(x,i) - meanX, 2)
  209.     float   SE = math.sqrt(sum1/sum2)
  210.     //
  211.     // The test statistic
  212.     float  adf = array.get(coeff,0) /SE
  213.     //
  214.     // Critical value of the ADF test statistic (90%, model1: constant, no trend)
  215.     // MacKinnon, J.G. 2010. “Critical Values for Cointegration Tests.” Queen”s University, Dept of Economics, Working Papers.
  216.     float crit  = switch
  217.         conf == "90%" => -2.56677 - 1.5384/nobs -  2.809/nobs/nobs
  218.         conf == "95%" => -2.86154 - 2.8903/nobs -  4.234/nobs/nobs - 40.040/nobs/nobs/nobs
  219.         conf == "99%" => -3.43035 - 6.5393/nobs - 16.786/nobs/nobs - 79.433/nobs/nobs/nobs
  220.     //
  221.     // output
  222.     [adf, crit, nobs]
  223.  
  224.  
  225. // --- main ---
  226.  
  227. // load data from a moving window into an array
  228. float[] a = array.new_float(na)
  229. for i = 0 to lookback-1
  230.     array.push(a,src[i])
  231.  
  232. // perform the ADF test
  233. [tauADF, crit, nobs] = adftest(a, nLag, conf)
  234.  
  235. // plot
  236. color    tauColor =  switch
  237.     tauADF < crit => #7AF54D
  238.     tauADF > crit => color.from_gradient(math.abs(tauADF), 0.0, math.abs(crit), color.white, #F5DF4D)//#939597, #F5DF4D)
  239.  
  240. bgcolor(#64416b)
  241. plot(0.0,    color = #939597,   title = "Zero")
  242. plot(crit,   color = #c84df5,   title = "Critical value")
  243. plot(tauADF, color = tauColor,  title = "Test statistic",     style = plot.style_cross, linewidth = 2)
  244.  
  245.  
  246.  
  247. if barstate.islast and isInfobox
  248.     infobox = table.new("bottom_left", 2, 3, bgcolor = #faedf5, frame_color = (tauADF < crit)?#7AF54D:#C84DF5, frame_width = 1)
  249.     table.cell(infobox, 0, 0, text = "Test Statistic",   text_color = color.black, text_size = size.small)
  250.     table.cell(infobox, 0, 1, text = conf+" Critical Value",    text_color = color.black, text_size = size.small)
  251.     table.cell(infobox, 0, 2, text = "Mean Reverting?",   text_color = color.black, text_size = size.small)
  252.     table.cell(infobox, 1, 0, text = str.format("{0, number, #.####}",tauADF),   text_color = color.black, text_size = size.small)
  253.     table.cell(infobox, 1, 1, text = str.format("{0, number, #.####}",crit),     text_color = color.black, text_size = size.small)
  254.     table.cell(infobox, 1, 2, text = (tauADF < crit)?"Yes":"No",   text_color = color.black, text_size = size.small)
  255.  
  256.  
Add Comment
Please, Sign In to add comment