>Common Lisp: Calculating the ATR

>The following code is an example of how to use the array of BAR-objects (Created here to create simplifications of the price information (The raw data) that will then be used to make trading decisions.

As before, the code is available as a Lisp-file here.

ATR stands for Average True Range. The true range of a price-bar is a formula that can be found quite easily using any search engine. The function CALCULATE-TRUE-RANGE takes a vector (Called “data” here. Maybe I should change the name in the code to signify that it has to be in the form of a vector.) and then walks over the vector and calculates the true range for all but the first BAR-object.

To calculate the true range, the current bar’s high and low values are checked along with the value of the previous bar’s close. This is acceptable since the true range of the bar is only calculated after the bar has closed (So that we know what the actual highest and lowest prices are). After the true range has been calculated, we store that value in the “trb” slot of the current bar.

;; Find the true range for each bar and place into object.
(defun calculate-true-range (data)
"This function will traverse a vector of BAR objects and calculate the true range for each."
(do ((i 1 (1+ i))
(length-of-array (length data)))
((>= i length-of-array))
(let ((high (high (aref data i)))
(low (low (aref data i)))
(previous-close (closeb (aref data (1- i)))))
(setf (trb (aref data i))
(max (- high low)
(abs (- high previous-close))
(abs (- previous-close low)))))))

After that we have another function walk over the array again. This function has 2 arguments; the first one is for the name of the vector, and the second one is a positive integer. The function calculates the average of the “trb” values of last N bars, and then stores that in the current bar’s “atrb” slot. Naturally, we cannot calculate this for the first N bars in the vector.

;; Calculate the ATR of last N bars.
(defun calculate-atr (data n)
"This function should calculate the ATR of the last N bars in array 'data'."
(do ((i n (1+ i))
(length-of-array (length data)))
((>= i length-of-array))
(setf (atrb (aref data i))
(/ (do ((x (- i n) (incf x))
(sum 0))
((> x i) sum)
(incf sum (trb (aref data x))))

Now, the ATR is by itself a measure of the commodity’s volatility, but according to Dr. Van K. Tharp the ATR of a bar as a percentage of the closing price of the bar is a better (More robust) measure of volatility. This is since (For example) an ATR of 50 points would have meant a lot more to the S&P500 50 years ago than it does now. Read Dr. Tharp’s newsletters if you want to get into the details.

Like the other functions, this one walks over the vector and stores the values in “atrperb”.

;; Calculate ATR as a percentage of the close.
(defun calculate-atrper (data)
"This function calculates ATR as a percentage of the close of each bar."
(dotimes (i (length data))
(setf (atrperb (aref data i))
(/ (atrb (aref data i))
(closeb (aref data i))))))

Now that we know the range of values the ATR has taken as a percentage of closing prices, we should be able to calculate what counts as “normal” volatility and the varying amounts of “abnormal” (Too much or too little) volatility. The first following function calculates the average ATR-as-a-percentage-of-the-close and saves it in the global variable “*average-atr-percentage*”

The second function compares each ATR-as-a-percentage-of-the-close value to the average (Explained above). The third function then finds the average of all deviations-from-the-mean (Which is what is calculated in the second function), the standard deviation.

;; Find the average and std.dev. of the ATR%.
(defun average-atr-percentage (data n)
"Return the average ATR% in vector 'data' of ATR(n)."
(do ((i (1- n) (1+ i))
(sum 0))
((>= i (length data))
(setf *average-atr-percentage* (/ sum (- (length data) n))))
(incf sum (atrperb (aref data i)))))

(defun calculate-atr-percentage-deviations (data n)
"Find the deviation of each ATR% in the vector (For ATR(n))."
;; Find the deviations of each bar.
(do ((i (1- n) (1+ i))
(length-of-array (length data)))
((>= i length-of-array))
(setf (atr-perc-deviation (aref data i))
(- (atrb (aref data i))

(defun standard-deviation-of-atrper (data n)
(let ((sum-of-squared-deviations 0))
(do ((i (1- n) (1+ i))
(length-of-array (length data)))
((>= i length-of-array))
;; Sum of squared deviations.
(incf sum-of-squared-deviations (expt (atr-perc-deviation (aref data i)) 2)))
(setq *atrperstddev*
;; Standard deviation is the sqrt of the expected (Average) squared deviation.
(sqrt (/ sum-of-squared-deviations
(- (length data) n))))))

Finally, we have a function that goes over the ATR-as-a-percentage-of-close, and compares it to the average and the standard deviations. Then it categorises each bar as either quiet, normal, volatile, or very volatile (0, 1, 2, 3).

;; The distinctions are given in newsletter 429 of Tharp's Thoughts.
(defun demarcate-volatilities (data n)
(let ((quiet (- *average-atr-percentage* (/ *atrperstddev* 2)))
(normal (+ *average-atr-percentage* (/ *atrperstddev* 2)))
(volatile (+ *average-atr-percentage* (* 3 *atrperstddev*))))
(do ((i (1- n) (1+ i))
(length-of-array (length data)))
((>= i length-of-array))
(let ((atr-percentage (atrperb (aref data i))))
(cond ((< atr-percentage quiet)
(setf (volatility-type (aref data i)) 0))
((< atr-percentage normal)
(setf (volatility-type (aref data i)) 1))
((< atr-percentage volatile)
(setf (volatility-type (aref data i)) 2))
(setf (volatility-type (aref data i)) 3)))))))

And finally over here we have examples of me using these functions on the vector of BAR-objects.

;; Perform actions
(calculate-true-range *array*)
(calculate-atr *array* 20)
(calculate-atrper *array*)
(average-atr-percentage *array* 20)
(calculate-atr-percentage-deviations *array* 20)
(standard-deviation-of-atrper *array* 20)
(demarcate-volatilities *array* 20)

This code took me a fair bit of time to develop. All of them walk over the vector of BAR-objects and perform the calculations and save the results within the objects themselves. There is a lot of number-crunching going on in these functions. I don’t know if there is a more efficient or elegant way to do these calculations, but it would be good to find out.

I use the ‘do’ macro to loop over the vector instead of the more modern ‘loop’ macro. ‘Do’ has been in Lisp for a while now and it is the basic (And yet most versatile) looping implementation in Common Lisp. I use this one instead of ‘loop’ since I find the syntax of the do-macro more structured. I like structure.

Something that concerned me those months ago when I developed this code was that it felt like I wasn’t doing things in a “Lisp way”, but that I was coding in a style someone would use in C; using a more procedural style of programming than a functional one. One thing that does make me happy is that the code is very quick to run. For all but the first 2 functions or so, the functions finish parsing through the vector for 1-hour GBPUSD data (~54,000 observations, leading back to January 2001) before the garbage-collector is able to kick in and do its thing! They take usually less than a second to run. Even for the first 2, total run-time doesn’t take more than 2 seconds, and ~25% of it is used by the garbage collector. Very quick.

When I first started thinking of using Lisp and I found out its a high-level language, I was concerned that it might run slowly. While lisp was traditionally interpreted, SBCL (The implementation that I use) compiles the code. Languages aren’t fast or slow, implementations are fast or slow.

Leave a Reply