A third refactor of the analytic functions

We'll refactor simple-moving-average, exponential-moving-average, and bollinger-band at the same time. At each point, we need to take the reduce block of code and change it to a lazy version that fits a function's process criteria:

(defn simple-moving-average
  [options tick-window tick-list]

  (let [start-index tick-window

        {input-key :input
         output-key :output
         etal-keys :etal
         :or {input-key :last-trade-price
              output-key :last-trade-price-average
              etal-keys [:last-trade-price :last-trade-time]}} options]

    (map (fn [ech]

           (let [tsum (reduce (fn [rr ee]
                                (let [ltprice (:last-trade-price ee)]
                                  (+ ltprice rr))) 0 ech)

                 taverage (/ tsum (count ech))]

             (merge
              (zipmap etal-keys
                      (map #(% (last ech)) etal-keys))
              {output-key taverage
               :population ech})))
         (partition tick-window 1 tick-list))))

The simple-moving-average function partitions an input tick list into a sliding window of tick data, incrementing by one tick. We now have a list of lists as input to the original reduce function. It then builds a new list using lazy-cat for each new computation to the end of a result list. However, doing this is unnecessary as each element in the list doesn't need to know about the other. We can simply map over the partitioned list and return each calculation independently:

(defn exponential-moving-average
  ([options tick-window tick-list]
   (exponential-moving-average options tick-window tick-list (simple-moving-average {} tick-window tick-list)))
  ([options tick-window tick-list sma-list]

   ;; 1. calculate 'k'
   ;; k = 2 / N + 1
   ;; N = number of days
   (let [k (/ 2 (+ tick-window 1))

         {input-key :input
          output-key :output
          etal-keys :etal
          :or {input-key :last-trade-price
               output-key :last-trade-price-exponential
               etal-keys [:last-trade-price :last-trade-time]}} options]

     ;; 2. get the simple-moving-average for a given tick - 1
     (last (reductions (fn [rslt ech]

                         ;; 3. calculate the EMA ( for the first tick, EMA(yesterday) = MA(yesterday) )
                         (let [;; price(today)
                               ltprice (input-key ech)

                               ;; EMA(yesterday)
                               ema-last (if (output-key (last rslt))
                                          (output-key (last rslt))
                                          (input-key ech))

                               ;; ** EMA now = price(today) * k + EMA(yesterday) * (1 - k)
                               ema-now (+ (* k ltprice)
                                          (* ema-last (- 1 k)))]

                           (lazy-cat rslt
                                     [(merge
                                       (zipmap etal-keys
                                               (map #(% (last (:population ech))) etal-keys))
                                       {output-key ema-now})])))
                       '()
                       sma-list)))))

The process criteria for exponential-moving-average is slightly different as the calculation of the current iteration needs the last value from the previous calculation. So, map won't work in this case. The reduce function provides access to an accumulation of results, but it isn't lazy. This is where we get to use reductions again. Recall that reductions behaves like reduce, but in a lazy fashion. The reduce function folds over a list and maintains a running result while iterating over each element, whereas reductions returns a lazy sequence of intermediate results along with the final result. Therefore, the only change made to the algorithm is swapping out reduce for reductions and taking out the last item (the accumulated list) from the result:

(defn bollinger-band
  ([tick-window tick-list]
   (bollinger-band tick-window tick-list (simple-moving-average nil tick-window tick-list)))

  ([tick-window tick-list sma-list]

   ;; At each step, the Standard Deviation will be: the square root of the variance (average of the squared differences from the Mean)
   (map (fn [ech]

          (let [;; get the Moving Average
                ma (:last-trade-price-average ech)

                ;; work out the mean
                mean (/ (reduce (fn [rlt ech]
                                  (+ (:last-trade-price ech)
                                     rlt))
                                0
                                (:population ech))
                        (count (:population ech)))

                ;; Then for each number: subtract the mean and square the result (the squared difference)
                sq-diff-list (map (fn [ech]
                                    (let [diff (- mean (:last-trade-price ech))]
                                      (* diff diff)))
                                  (:population ech))

                variance (/ (reduce + sq-diff-list) (count (:population ech)))
                standard-deviation (. Math sqrt variance)]
            {:last-trade-price (:last-trade-price ech)
             :last-trade-time (:last-trade-time ech)
             :upper-band (+ ma (* 2 standard-deviation))
             :lower-band (- ma (* 2 standard-deviation))}))
        sma-list)))

The bollinger-band refactor follows the same pattern as the one used for the simple-moving-average refactor. Though each item is iterated over, it doesn't need to know about any other item. Thus, we can simply map over the source simple-moving-average list, returning a map of the :last-trade-price, :last-trade-time, :upper-band, and :lower-band entries.

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

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