Chapter 9. Building Algorithms – Strategies to Manipulate and Compose Functions

The ultimate goal of the kind of analytics system we've been building is to get clear signals for the direction of an asset (stocks, in this case). With regard to our SMAs, EMAs and Bollinger Bands that capture market dynamics, let's see if we can use these analyses to glean whether an underlying asset will go up or down.

This chapter will use all the knowledge we've gained so far to design these signals. We'll see how we can reason about this process, and then encode new business rules on top of existing ones to create new functionality and information. In this chapter, we'll cover the following topics:

  • Structuring our data for further analysis
  • The third refactor of our analytic functions
  • Signals using moving averages
  • The Relative Strength Index
  • Bollinger Band signals

Structuring our data for further analysis

SMAs and EMAs lag an underlying stock price, but in slightly different ways. The SMA assigns equal weight to all input ticks, whereas the EMA assigns greater weight to more recent stock ticks. For example, if we take a baseline time series along with its SMAs and EMAs, we can look out for when the EMA overlaps (above or below) the SMA.

Create the src/edgar/signals.clj file and add this namespace definition at the top:

(ns edgar.signals
  (:require [edgaru.nine.core :as core]
            [edgaru.nine.analytics :as analytics]
            [edgaru.nine.datasource :as datasource]))

To make these comparisons, we'll first have to make a function that joins all our data into one list. Review the join-averages function; we'll go through it step by step here:

(defn join-averages
  ([tick-window tick-list]

   (let [sma-list (simple-moving-average nil tick-window tick-list)
         ema-list (exponential-moving-average nil tick-window tick-list sma-list)]
     (join-averages tick-list sma-list ema-list)))

  ([tick-list sma-list ema-list]

   (let [trimmed-ticks (drop-while #(not (= (:last-trade-time %)
                                            (:last-trade-time (first sma-list))))
                                   tick-list)]

     (map (fn [titem sitem eitem]

            (if (and (and (not (nil? (:last-trade-time sitem)))
                          (not (nil? (:last-trade-time eitem))))
                     (= (:last-trade-time titem) (:last-trade-time sitem) (:last-trade-time eitem)))

              {:last-trade-time (:last-trade-time titem)
               :last-trade-price (if (string? (:last-trade-price titem))
                                   (read-string (:last-trade-price titem))
                                   (:last-trade-price titem))
               :last-trade-price-average (:last-trade-price-average sitem)
               :last-trade-price-exponential (:last-trade-price-exponential eitem)}

              nil))

          trimmed-ticks
          sma-list
          ema-list))))

The join-averages function overlays a stock price tick list, SMA list, and an EMA list into one list. Recall that the moving average values begin 20 ticks (or whatever you set the tick window to be) into the source tick list. As such, the join-averages function will need to line up all three lists to the same trade time. This is what the trimmed-ticks Var represents. We've created it using the drop-while function. This function takes a collection and from left to right keeps dropping its elements until some condition is met. This condition is supplied by the predicate we've provided. Since we know that tick-list will be the longer list, we'll drop its earliest elements (elements on the left) until it's :last-trade-time matches :last-trade-time from the first simple-moving-average.

We can now map over all three lists at once. Remember that the map function accepts as many list as you want to give it. The number of parameters to the mapping function must match the number of lists that are supplied. The map function will also process all lists until the first list is exhausted. In our mapping function, the if block on each iteration will ensure that :last-trade-time in all lists are equal:

(and (and (not (nil? (:last-trade-time sitem)))
              (not (nil? (:last-trade-time eitem))))
         (= (:last-trade-time titem) (:last-trade-time sitem) (:last-trade-time eitem)))

We then simply return a map with all the relevant data elements inside it:

{:last-trade-time (:last-trade-time titem)
 :last-trade-price (if (string? (:last-trade-price titem))
                     (read-string (:last-trade-price titem))
                     (:last-trade-price titem))
 :last-trade-price-average (:last-trade-price-average sitem)
 :last-trade-price-exponential (:last-trade-price-exponential eitem)}

I've introduced a subtle code change that we must address. simple-moving-average and exponential-moving-average (and bollinger-band) compute their final values with the reduce function. The reduce function has a finality to it, in that it returns a concrete list, which is no longer lazy. Inversely, if you execute reduce over a lazy sequence, it will run indefinitely. The join-averages function and all subsequent signal functions will be lazy and expect infinite sequences. Back in Chapter 3, Developing the Simple Moving Average, we learned that while data is being processed, a good architectural principle is to keep it in lazy sequences. As long as everybody's using lazy sequences, we can treat these as data flow pipelines that easily compose functions and pieces of functions. So, now is a good time to return to our core analytic functions and make them lazy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.76.204