Recursive Rolling Calculations with Ramda

Some fun using recursion to create moving averages in a functional programming paradigm

John Cothran
Towards Data Science

--

Image by stokpic via Pixabay.com (https://pixabay.com/users/stokpic-692575/)
Image by stokpic via Pixabay.com (https://pixabay.com/users/stokpic-692575/)

The rolling average, or moving average, is among the most popular trend indicators for anyone who has an interest in datasets that fluctuate over time. This is especially true for traders, who are constantly striving to decipher trend signals in the constant flux of the financial markets.

This article is for people who have a background in data science, and who are interested in learning more about the functional approach to programming. Recursion is a concept that may take some time and thought to comprehend, and so I would like to show a practical approach to using recursion in a real-world setting.

My primary goal is to calculate moving averages (specifically exponential moving averages) using the functional approach of recursion rather than ‘for loops’. I will be leaning heavily on Ramda.js, an incredibly useful functional Javascript library.

Recursion

I’m going to assume the reader of this article is familiar with moving averages, so let’s jump straight to recursion. Inspiration of this post comes directly from this article and this article. Typing “recursion” into Google yields the following definition:

The repeated application of a recursive procedure or definition

It’s a recursive definition — cool!

A recursive function is a function that is defined in terms of itself. Another way to think about a recursive function is that it invokes itself — until it doesn’t. Running const squareMe = n => squareMe(n*n) will run over and over again and quickly blow your call stack.

This is why we need a “base case,” which is a condition that defines when the function will terminate.

Additionally, a recursive function actually needs to call itself, which is called a ‘recursion.’

Lastly, in order to do anything useful, a recursive function needs to have an ‘action.’

To demonstrate these three components of a recursive function, let’s write a function that counts by one to a given number:

// ES6
const countTo = (num, arr=[], startingN=1) => {
// Base case:
if (arr.length === num) return arr;
// Recursion and action:
return countTo(num, arr.concat(startingN), startingN + 1);
};

Calling countTo(10) returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], recursively invoking itself by calling the action(arr.concat and startingN+1)until it terminates when it hits the base case arr.length === num.

Yay! No more for loops!

Recursion is an alternative to for loops, and a more functional way of iterating through a dataset to achieve the desired result.

Ramda

Ramda.js is a lightweight, functional Javascript library that is designed for people who want to keep their code pure and functional, and their functions immutable and free of side-effects. Oh, and data comes last — it’s just better.

Simple Moving Average

Let’s start with taking the simple moving average of a list of numbers:

//ES6
import R from 'ramda';
const sma = (num, data, arr = []) => {
if (data.length === 0) return arr;
if (data.length < num) return sma(
num,
data.slice(1),
R.prepend('not yet!', arr),
);
const newData = R.tail(data);
const movingMean = R.mean(
R.slice(0, num, data)
);
return sma(
num,
newData,
R.append(movingMean, arr)
);
};

There are three parts to this function.

The first if statement simply returns the arr as soon as the length of the data argument reaches 0. This is the ‘base case’.

The second if statement recursively calls sma when the length of the data argument reaches less than the num argument, using Ramda’s prepend to add the ‘not yet!’ string to the front of arr.

The final return statement is the meat of this function. It applies Ramda’s mean function to the appropriate elements of the newData, returning a new arr with the appended mean each time.

The last thing to emphasize here is how each time the function invokes itself, it is doing so on newData, which simply returns data without the first element (R.tail). Let’s add a console.log statement before each return statement so we can see exactly what is going on inside sma:

This shows exactly what is going on when you call sma. After the function runs the first time, it returns a copy of arr with the mean of the first three numbers of the initial data. The second time the function runs, it returns a copy of the copy of arr with the mean of the first three numbers of the copy of data that has had its first element sliced off.

You get the point!

Notice that the 'not yet' strings have to be prepended to arr at the end of the recursion process. Also, since these are placeholders, we don’t get actual values, which leads to the next step in this article: the exponential moving average.

Exponential Moving Average

So far, we’ve covered the basics of recursion, applying them to creating a simple moving average on a list with some help from Ramda.

Let’s tackle this concept in a different way, applying it to a real-world situation. We’ll use the IEX API to pull in some longitudinal data from the stock exchange:

This function fetches AAPL data from the IEX trading API and returns a list of objects with a date and value. Charting this dataset will look something like this:

Image by author

Now we need to write a function that gives us the exponentially weighted moving average of this dataset, and for this we’ll use the exponentially weighted moving average that Andrew Ng presents here. Writing it into a javascript function looks like this [line 8]:

Whoa. Let’s break this apart.

First, ewa takes two arguments - decay is simply the number of days you want to weight over, while data is your list of objects you want to operate on. Next, ewa defines beta, which is calculated from decay using a bit of exponential algebra, and essentially controls the smoothing of the algorithm.

The function checkArr is where the magic of the algorithm happens. On the first iteration of ewa, the arr is empty — therefore checkArr just sets the value of the first element of the new arr to the first value in data [line 7]. There is no previous data point to exponentially weight! On the rest of the ewa recursive calls, checkArr returns the weighted value controlled by beta in the algorithm since arr is no longer empty.

Now, we can add an ewa(20, getData()) to our chart:

Image by author

More Ramda!

In functional programming, a ‘curried’ function is one that, if provided less arguments than it takes, returns a new function which takes the remaining arguments. This technique is extremely helpful when you need to compose new functions. Let’s use Ramda’s curry function to create a bunch of different ewa functions:

const curriedEwa = R.curry(ewa);const ewaTwenty = curriedEwa(20);const ewaFifty = curriedEwa(50);const ewaHundred = curriedEwa(100);

Now, charting ewaTwenty, ewaFifty, and ewaHundred gives us this:

Image by author

Functional programming FTW!

There is a lot going on here, and my hope is that I have helped to show how useful recursion and Ramda can be. If you aren’t familiar with these concepts already, it can take a while to get your head around them (at least it did for me). However, I’ve set up an Observable notebook where you can explore in depth the code in this post.

--

--