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.
18.117.187.113