1

I am testing this in R using the SAR() function from the great TTR package implemented by Joshua Ulrich. I am not sure if this is a standard behavior of Parabolic SAR. If yes, I would need some help with implementing "future blind" SAR.

To make things simple I will work with short vectors and integer values instead of real time series data.

L <- c(1:4, 5)
H <- c(2:5, 6)

ParSAR <- SAR(cbind(H, L))
cbind(L, H, ParSAR)

     L H   ParSAR
[1,] 1 2 1.000000
[2,] 2 3 1.000000
[3,] 3 4 1.080000
[4,] 4 5 1.255200
[5,] 5 6 1.554784

I will only change one value on the last interval, where the Low - High range will be 5 - 7 now, instead of 5 - 6.

L <- c(1:4, 5)
H <- c(2:5, 7)

We get:

     L H    ParSAR
[1,] 1 2 0.5527864
[2,] 2 3 0.5817307
[3,] 3 4 0.6784614
[4,] 4 5 0.8777538
[5,] 5 7 1.2075335

Is it expected behavior that all the history of Parabolic SAR gets modified dramatically? If SAR values on rows from 1 to 4 are modified by a different future value on the row 5, it introduces look-ahead bias to the prior rows.

If this is a standard behavior of Parabolic SAR and I need it for a backtest, I will have to recompute it for every row always masking all future data (rows).

Desired result is to have Parabolic SAR value for each row as I could have witness it at the particular moment in time, not knowing the future.


EDIT 2016-06-18

Simplified code example for user3666197:

> SAR(cbind(c(2, 3, 4, 5, 6), c(1, 2, 3, 4, 5)), c(0.02, 0.2))
[1] 1.000000 1.000000 1.080000 1.255200 1.554784

> SAR(cbind(c(2, 3, 4, 5, 7), c(1, 2, 3, 4, 5)), c(0.02, 0.2))
[1] 0.5527864 0.5817307 0.6784614 0.8777538 1.2075335
Community
  • 1
  • 1
Dave
  • 132
  • 1
  • 11

3 Answers3

1

@user3666197 :

Your test gives identical results for both short examples because in both cases the default value initGap = 0. is used. That is not the case with the R wrapper function, as noted above:

  # Gap for inital SAR
  initGap <- sd(drop(coredata(HL[,1] - HL[,2])), na.rm=TRUE)
user.51U15
  • 11
  • 1
  • This is clear from the very start. Kindly consider the perspective of the O/P question *(cit.:)* **Q:** "if this is a **standard behavior** of Parabolic SAR". **A: No, it is not.** Further reasoning and evidence were developed in the detailed answer above. Any assignment other than `initGap = 0.` is a particular wrapper injected assertion, **not a generally accepted `pSAR()` feature / side-effect introducing a look-ahead bias.** So keeping the original post perspective, **the answer is thus "No, it is not".** altogether with a solution for implementing look-ahead blind wrapper correction. – user3666197 Sep 06 '16 at 11:20
  • You are wrong all the time. The original question was about R, R implementation, R solution and so on. You had completely derailed it from the very beginning and then failed to come with anything enlightening. Later you abused you power to downvote the only real answer. You basically trashed my question as well as my whole Stack Overflow experience.; @user.51U15: You are right with your approach. Thank you for stopping by. – Dave Sep 06 '16 at 12:50
  • @Dave **Are you sure?** If reading well the architecture, **R does not calculate the p-SAR value, does it?** It happens to call a correctly implemented sar() and receives the value. The returning value is wrong not due to sar() look-ahead bias, or other magic, but due to inappropriate initialisation, that came from R-side wrapper and that causes the side-effect, that you are asking about. **Sorry to repeat it again -- No, parabolic-SAR ( sar() ) does not have look-ahead bias, but R-side wrapper injects one such due to a poor choice of initialisation values. Q.E.D.** – user3666197 Sep 07 '16 at 04:22
  • You do not have to say sorry and you do not have to repeat. These are my findings, not yours. Let me quote yours: "There is zero look-ahead peeking in the Joshua ULRICH's implementation of the pSAR algorithm observed. Q.E.D." Wrong. There is. The only correct and complete answer is the very short one below. You are only making big noise to answer nonexistent questions. Read the first character of the title, read the first flag, read the first sentence of the first post. I was asking about "R" implementation. – Dave Sep 07 '16 at 15:57
0

pSAR does not have any look-ahead peeking implemented

Upon a slight code-review of Joshua ULRICH's pSAR implementation one may confirm a zero look-ahead peeking in the source code.

The issues may come on a first look from the function call-signature, but the R-wrapper fixes these.

#'@export
"SAR" <-
function(HL, accel=c(.02,.2)) {

  # Parabolic Stop-and-Reverse (SAR)
  # ----------------------------------------------
  #       HL = HL vector, matrix, or dataframe
  # accel[1] = acceleration factor
  # accel[2] = maximum acceleration factor
  ...

  # Gap for inital SAR
  initGap <- sd(drop(coredata(HL[,1] - HL[,2])), na.rm=TRUE)

  # Call C routine
  sar <- .Call("sar", HL[,1], HL[,2], accel, initGap, PACKAGE = "TTR")

  reclass( sar, HL )
}

SAR() implementation uses the published calling interface of sar():

SEXP sar ( SEXP hi, /* HIGH[]-s */ SEXP lo, /* LOW[]-s */ SEXP xl, /* [initial AF value, max AF value] */ SEXP ig /* initial GAP */ )

where there is no peeking into the future ( zero look-ahead ) in any of the for(){...} scanners ( all are purely a look-back only ).


UPDATE

Test outputs from both given data-sets:

|>>> sar( ( 2, 3, 4, 5, 6 ), ( 1, 2, 3, 4, 5 ), ( 0.02, 0.2 ) )
[1.0, 1, 1.08, 1.2552, 1.5547840000000002]
|
|>>> sar( ( 2, 3, 4, 5, 7 ), ( 1, 2, 3, 4, 5 ), ( 0.02, 0.2 ) )
[1.0, 1, 1.08, 1.2552, 1.5547840000000002]

There is zero look-ahead peeking in the Joshua ULRICH's implementation of the pSAR algorithm observed.

Q.E.D.



Ex-post

as noted from the very beginning, the sar() function was proved to be correctly implemented pSAR technical indicator. The R wrapper was the issue, as it decides on what parameters the underlying, correctly working, function sar() is being invoked. Upon a case the call-signature is loaded with parameters, that carry a forward looking bias, the function is not to be blamed for a such behaviour, but rather the user. Q.E.D.

#----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # CRAN - R-Project TTR lib: indicators::                        # Joshua ULRICH: >>> https://github.com/joshuaulrich/TTR/tree/master/src + https://cran.r-project.org/web/packages/TTR/TTR.pdf
        def sar( Hi, Lo, acceleration_factor = ( 0.02, 0.2 ), initGap = 0. ):
            """                                                         __doc__
            USAGE:      calculate parabolic SAR on given inputs
                        sar( Hi,
                             Lo,
                             acceleration_factor = ( 0.02, 0.2 ),
                             initGap = 0.
                             )
            PARAMS:     Hi:                     High[]s in []-alike representation
                        Lo:                     Low[]-s in []-alike representation
                        acceleration_factor:    [ afSTEP, afMAX ], ref. pSAR rules
                        initGap:                initial gap under first valid Low[]
            RETURNS:    pSAR[]
            THROWS:     n/a
            EXAMPLE:    |>>> QuantFX.sar( (2,3,4,5,6), (1,2,3,4,5) )
                        [1.0, 1, 1.08, 1.2552, 1.5547840000000002]
                        |
                        |>>> QuantFX.sar( (2,3,4,5,7), (1,2,3,4,5) )
                        [1.0, 1, 1.08, 1.2552, 1.5547840000000002]
            """
            """
            #___________________________________________________________/* Initalize loop and PROTECT counters */
            int i, P=0;
            #___________________________________________________________/* Ensure all arguments are double */
            if(TYPEOF(hi) != REALSXP) {
              PROTECT(hi   = coerceVector(hi, REALSXP)); P++;
            }
            if(TYPEOF(lo) != REALSXP) {
              PROTECT(lo   = coerceVector(lo, REALSXP)); P++;
            }
            if(TYPEOF(xl) != REALSXP) {
              PROTECT(xl   = coerceVector(xl, REALSXP)); P++;
            }
            double initGap = asReal( ig );                              ### ------------------------ extern ( ig )
            #___________________________________________________________/* Pointers to function arguments */
            double *d_hi   = REAL(hi);
            double *d_lo   = REAL(lo);
            double *d_xl   = REAL(xl);
            """
            #___________________________________________________________/* Input object length */
            #int nr        = nrows(hi);
            nRows          = len(  Hi )
            #___________________________________________________________/* Initalize result R object */
            #SEXP sar; PROTECT(sar = allocVector(REALSXP,nr)); P++;
            #double *d_sar = REAL(sar);
            sar            = [None] * nRows
            #___________________________________________________________/* Find first non-NA value */
            #int beg       = 1;
            begFrom        = 1
            #for(i=0; i < nr; i++) {
            for i in xrange( nRows ):
              if (  np.isnan( Hi[i] )
                 or np.isnan( Lo[i] )
                 ):
                 sar[i]    = None                                       # NA_REAL; /* skip-it */
                 begFrom  += 1
              else:
                 break                                                  #          /* break   */
              pass
            pass
            #___________________________________________________________/* Initialize values needed by the routine */
            #int sig0 = 1, sig1 = 0;
            sig0           = 1
            sig1           = 0

            xpt0           = Hi[begFrom-1]
            xpt1           = 0

            afStp          = acceleration_factor[0]
            afMAX          = acceleration_factor[1]
            af0            = afStp
            af1            = 0

            sar[begFrom-1] = Lo[begFrom-1] - initGap                    # /* SUB initGap from 1st Lo[] to begin from */

            for i in xrange( begFrom, nRows ):
              #_________________________________________________________/* Increment signal, extreme point, and acceleration factor */
              sig1         = sig0
              xpt1         = xpt0
              af1          = af0
              #_________________________________________________________/* Local extrema */
              lmin         = min( Lo[i-1], Lo[i] )                                      #?? pSAR_RULE_Exc 2 ?? 2 last bars checked
              lmax         = max( Hi[i-1], Hi[i] )
              #_________________________________________________________/* Create signal and extreme price vectors */
              if ( sig1   == 1 ):                                                         #/* Previous buy signal */
                   #ig0    = ( Lo[i] > d_sar[i-1]) ?  1 : -1                              #/* New signal */
                   sig0    =                          1 if ( Lo[i] > sar[i-1] ) else -1   #/* New signal */
                   xpt0    = max( lmax, xpt1 )                                            #/* New extreme price */
              else:                                                                       #/* Previous sell signal */
                   #ig0    = ( Hi[i] < d_sar[i-1]) ? -1 : 1                               #/* New signal */
                   sig0    =                         -1 if ( Hi[i] < sar[i-1] ) else 1    #/* New signal */
                   xpt0    = min( lmin, xpt1 )                                            #/* New extreme price */
              pass
              """
              /*
               * Calculate acceleration factor (af)
               * and stop-and-reverse (sar) vector
               */
              """
              #_________________________________________________________/* No signal change */
              if ( sig0   == sig1 ):
                #_______________________________________________________/* Initial calculations */
                sar[i]     =     sar[i-1] + ( xpt1 - sar[i-1] ) * af1
                #f0        = ( af1 == afMAX ) ? afMAX : ( afStp + af1 )
                af0        =                    afMAX if ( af1 == afMAX ) else ( afStp + af1 )
                #_______________________________________________________/* Current buy signal */
                if ( sig0 == 1 ):
                     #f0   = (xpt0 > xpt1) ? af0 : af1                                  #/* Update acceleration factor */
                     af0   =                 af0 if (xpt0 > xpt1) else af1              #/* Update acceleration factor */
                     sar[i]= min( sar[i], lmin )                                        #/* Determine sar value */
                #_______________________________________________________/* Current sell signal */
                else:
                     #f0   = (xpt0 < xpt1) ? af0 : af1                                  #/* Update acceleration factor */
                     af0   =                 af0 if (xpt0 < xpt1) else af1              #/* Update acceleration factor */
                     sar[i]= max( sar[i], lmax )                                        #/* Determine sar value */
              #_________________________________________________________/* New signal */
              else:
                af0        = afStp                                                      #/* reset acceleration factor */
                sar[i]     = xpt0                                                       #/* set sar value */
              pass
            pass
            #___________________________________________________________/* UNPROTECT R objects and return result */
            #UNPROTECT(P);
            #return(sar);
            return  sar
user3666197
  • 1
  • 6
  • 50
  • 92
  • Sorry, I did not get it. I still do not understand how can a value change on the 5th row affect all the previous values. If I have data of length n, I cannot trust the values computed for rows 1:n-1. Just take my second example and use rows 1:4 only. Compute the SAR. Add the last row and recompute. All values change dramatically. This is problem since SAR is usually applied on the whole length of data at once. – Dave Jun 13 '16 at 19:08
  • Having cross-validated the source code to provide correct results, I can hardly ***both* reproduce** and **explain** the difference of results, presented in your post. What came to my mind was, just as a hypothesis from other similar trouble, the case of erroneously designed parallelisation of computational strategy, where a naturally **`SEQ`** part of a code was wrongly made as a vectorised ( a weak form of **`PAR`** processing ) code and the erroneous results were clearly produced because of the violation of the dependency-chains in the `SEQ` process-flow. – user3666197 Jun 17 '16 at 15:07
  • I have no idea where the example in your edit comes from because it is not valid R code. I had corrected it and ran in the R console. You can see the results in an edit of my original post. As you can see, my results are completely different and clearly show a look-ahead bias, where the future data affects/changes the past data. I am wondering if @JoshuaUlrich would know why is this happening. Maybe this is related?: [link](https://github.com/joshuaulrich/TTR/issues/23) – Dave Jun 18 '16 at 08:22
  • The validation was performed on underlying c-code that is being used by @JoshuaUlrich re-wrapper for R. `InitGap`, reported in the link is something not common for me in pSAR usage landscape. Normally, the initial Flip/Flop decision is left for deep-enough head of the TimeSERIEs history, after which the indicator serves well in the near-end of the historic data. Using the code on just a few datapoints is thus an opposite extreme of the principle. Anyway, the python was used to test the algorithmic part of the hypothesis, which proved itself to be correct. The rest is in compiler / wrapper / R – user3666197 Jun 18 '16 at 08:29
  • So, you failed to read and understand the title and the original question - everything was related to 'R' from the beginning. Then, you failed to identify the problem, unleashed your ego instead and down-voted the only real answer in the end. Well, be proud of yourself and enjoy your little playground. :) – Dave Jul 14 '16 at 15:26
  • I don't think so, Dave. I have both proved and demonstrated why there is a zero look-ahead in pSAR algorithm and have demasked the Joshua's R/wrapper assumption for setting an initGap value. – user3666197 Jul 14 '16 at 15:53
0

R implementation of Parabolic SAR comes with a look-ahead bias.

The initGap value is a Standard Deviation of all time HL data:

initGap <- sd(drop(coredata(HL[, 1] - HL[, 2])), na.rm = TRUE)

References: https://github.com/joshuaulrich/TTR/issues/23

The drastical impact on my original example is caused by short sample of data and the extreme values used.

Dave
  • 132
  • 1
  • 11