Signals using moving averages

Now that our analytic functions are lazy and we can join them into one list, we can start thinking about what signals we may be interested in. A simple and direct signal is finding out whether the EMA in one tick element has crossed above the SMA from a previous element. Another signal would be the inverse of this—we can find out whether, in the first element, the EMA has crossed below the SMA from the second element. This is another process criteria where a current tick needs to know about a previous tick. However, in this situation, the previous computation isn't needed by the current one. We can just partition our ticks by two to check for a crossover. Let's take a look at an implementation now:

(defn moving-averages-signals
  "Takes baseline time series, along with 2 other moving averages.
   Produces a list of signals where the 2nd moving average overlaps (abouve or below) the first.
   By default, this function will produce a Simple Moving Average and an Exponential Moving Average."

  ([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)]
     (moving-averages-signals tick-list sma-list ema-list)))

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

   ;; create a list where i) tick-list ii) sma-list and iii) ema-list are overlaid
   (let [joined-list (join-averages tick-list sma-list ema-list)
         partitioned-join (partition 2 1 (remove nil? joined-list))]

     ;; find time points where ema-list (or second list) crosses over the sma-list (or 1st list)
     (map (fn [[fst snd]]
            (let [
                  ;; in the first element, has the ema crossed abouve the sma from the second element
                  signal-up (and (< (:last-trade-price-exponential snd) (:last-trade-price-average snd))
                                 (> (:last-trade-price-exponential fst) (:last-trade-price-average fst)))

                  ;; in the first element, has the ema crossed below the sma from the second element
                  signal-down (and (> (:last-trade-price-exponential snd) (:last-trade-price-average snd))
                                   (< (:last-trade-price-exponential fst) (:last-trade-price-average fst)))

                  raw-data fst]

              ;; return either i) :up signal, ii) :down signal or iii) nothing, with just the raw data
              (if signal-up
                (assoc raw-data :signals [{:signal :up
                                           :why :moving-average-crossover
                                           :arguments [fst snd]}])
                (if signal-down
                  (assoc raw-data :signals [{:signal :down
                                             :why :moving-average-crossover
                                             :arguments [fst snd]}])
                  raw-data))))
          partitioned-join))))

In the preceding code, moving-averages-signals will generate a simple and exponential moving average if it hasn't been passed in. In the second let block, we'll join the underlying ticks, SMA, and EMA into one list (called joined-list). As described previously, we then partition this joined-list into twos (called partitioned-join).

Now we map over partitioned-join and apply our algorithm. The mapping function takes each element and destructures it into the first and second elements (fst and snd). Recall from Chapter 4, Strategies for Calculating and Manipulating Data, that destructuring is a small custom language used to extract values from data structures. In this case, each element in partitioned-join will be a collection and not an associative structure (a map). So, [[fst snd]] is the list destructuring syntax needed to extract elements 0 and 1 and bind them to fst and snd. We can now use these Vars to see whether the second exponential price is below the second average price and the first exponential price is above the first average price. We then assign the true or false result to signal-up:

(and (< (:last-trade-price-exponential snd) (:last-trade-price-average snd))
     (> (:last-trade-price-exponential fst) (:last-trade-price-average fst)))

We will now make the inverse check and assign this true or false result to signal-down:

(and (> (:last-trade-price-exponential snd) (:last-trade-price-average snd))
     (< (:last-trade-price-exponential fst) (:last-trade-price-average fst)))

As these calculations are contingent on the intersections between the ticks, either the first or second tick can be considered a signal point. I've chosen the first (assigned to raw-data) as any algorithm would see this signal sooner than later. However, this choice will have to be remembered for any future signals that we'd like to line up.

The last expressions we'll look at are nested if statements. Logically, we know that signal-up and signal-down are mutually exclusive. So, if signal-up is true, we can return raw data with an :up signal attached to it along with any other information such as the reason. If signal-up is false and signal-down is true, then we'll return a :down signal and any other relevant data. If neither is true, we can just return raw-data:

(if signal-up
  (assoc raw-data :signals [{:signal :up
                             :why :moving-average-crossover
                             :arguments [fst snd]}])
  (if signal-down
    (assoc raw-data :signals [{:signal :down
                               :why :moving-average-crossover
                               :arguments [fst snd]}])
    raw-data)
..................Content has been hidden....................

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