Bollinger Band signals

Bollinger Band signals involve a few more checks. When the band width is very low, this indicates that the price will break out sooner than later. For this strategy, let's call it "A", we need to check for a few things:

  • Is the moving average in an up or down market?
  • Width-wise, we should check for a narrowing Bollinger Band. However, is the bandwidth less than the three previous narrow bands? Three is a random number that can be tuned once this algorithm has been tested on real data.
  • Is the close outside the Bollinger Bands, and previous high or low swing inside the bands?

When the width of the Bollinger Band is high (indicating high volatility), this can mean that a trend is ending soon or that it's either going to change direction or consolidate. For this strategy, let's call it "B", we need to check a different set of conditions:

  • Is the moving average in a sideways (or choppy) market? Here, we will check whether the last three closes are above or below the Bollinger Bands.
  • Width-wise, we should check for a widening Bollinger Band. Is the band width greater than the three previous bands? Three is a random number that can be tuned once this algorithm has been tested on real data.
  • Here, we will apply the RSI divergence, that is, the change of the average gains, relative to the average losses.

Take a look at the following bollinger-band-signals function. We will go through it in detail afterwards:

(declare sort-bollinger-band)
(defn bollinger-band-signals

  ([tick-window tick-list]
   (let [sma-list (simple-moving-average nil tick-window tick-list)
         bband (bollinger-band tick-window tick-list sma-list)]

     (bollinger-band-signals tick-window tick-list sma-list bband)))

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

   (last (reductions (fn [rslt ech-list]

                       (let [
                             ;; track widest & narrowest band over the last 'n' ( 3 ) ticks
                             sorted-bands (sort-bollinger-band ech-list)
                             most-narrow (take 3 sorted-bands)
                             most-wide (take-last 3 sorted-bands)

                             partitioned-list (partition 2 1 (remove nil? ech-list))

                             upM? (up-market? 10 (remove nil? partitioned-list))
                             downM? (down-market? 10 (remove nil? partitioned-list))

                             side-market? (and (not upM?)
                                               (not downM?))
 
                             ;; find last 3 peaks and valleys
                             peaks-valleys (find-peaks-valleys nil (remove nil? ech-list))
                             peaks (:peak (group-by :signal peaks-valleys))
                             valleys (:valley (group-by :signal peaks-valleys))]


                         (if (empty? (remove nil? ech-list))

                           (conj rslt nil)

                           (if (or upM? downM?)

                             ;; A.
                             (calculate-strategy-a rslt ech-list most-narrow upM? peaks valleys)

                             ;; B.
                             (calculate-strategy-b rslt ech-list most-wide peaks valleys)))

                         (conj rslt (first ech-list))))
                     []
                     (partition tick-window 1 bband)))))

The bollinger-band-signals and accompanying helper and calculation functions are rather large, so we'll tackle these one section at a time. The signal function needs a tick list and an accompanying SMA and Bollinger Band. These are passed in or are otherwise generated:

  • Before each strategy is implemented, we will first collect the three widest and narrowest bands over the last 20 ticks (most-narrow and most-wide). The sort-bollinger-band function is defined as follows. At each tick, we will only take the difference between the upper and lower bands and then sort the result using this difference:
    (defn sort-bollinger-band [bband]
      (let [diffs (map (fn [inp]
                          (assoc inp :difference (- (:upper-band inp) (:lower-band inp))))
                       (remove nil? bband))]
        (sort-by :difference diffs)))
  • Similar to the relative-strength-index function, we partition the source tick-list by two in order to determine whether there's an up market or down market (upM? or downM?). This calculation is slightly different, however, in that we determine whether every new tick is greater than the previous one over a 10-tick period (upM?):
    (defn up-market? [period partitioned-list]
      (every? (fn [inp]
                (< (:last-trade-price (first inp))
                   (:last-trade-price (second inp))))
              (take period partitioned-list)))
  • We'll perform the same, but inverse calculation to determine the down market (downM?), over a 10-tick period:
    (defn down-market? [period partitioned-list]
      (every? (fn [inp]
                (> (:last-trade-price (first inp))
                   (:last-trade-price (second inp))))
              (take period partitioned-list)))
  • The next set of calculations finds the peaks and valleys in the source tick list. A peak is a set of three prices where we begin at any price level, go upward, then downward. The opposite of this is applied to a valley (peaks-valleys). Next, we'll group and collect each of both (peaks and valleys):
    (defn find-peaks-valleys [options tick-list]
    
      (let [{input-key :input
             :or {input-key :last-trade-price}} options]
    
        (reduce (fn [rslt ech]
                  (let [fst (input-key (first ech))
                        snd (input-key (second ech))
                        thd (input-key (nth ech 2))
    
                        valley? (and (and (-> fst nil? not) (-> snd nil? not) (-> thd nil? not))
                                     (> fst snd)
                                     (< snd thd))
                        peak? (and (and (-> fst nil? not) (-> snd nil? not) (-> thd nil? not))
                                   (< fst snd)
                                   (> snd thd))]
    
                    (cond
                      peak? (conj rslt (assoc (second ech) :signal :peak))
                      Valley? (conj rslt (assoc (second ech) :signal :valley)))
                    :else rslt)))
                []
                (partition 3 1 tick-list))))

    With the first let block out of the way, we will now either apply strategy A or B based on whether we have an up, down, ("A") or sideways market ("B").

    To calculate strategy A, we will check for a narrowing band. Is the band width less than the three previous narrow bands? Is the close outside the Bollinger Band? Were there previous high or low swings inside the band?

    (defn calculate-strategy-a [rslt ech-list most-narrow upM? peaks valleys]
    
    (let [latest-diff (- (:upper-band (last ech-list)) (:lower-band (last ech-list)))
            less-than-any-narrow? (some (fn [inp] (< latest-diff (:difference inp))) most-narrow)]
    
        (if less-than-any-narrow?
    
          (if upM?
    
            (if (and (< (:last-trade-price (last ech-list)) (:upper-band (last ech-list)))
                     (> (:last-trade-price (last peaks))
                        (:upper-band (last (some #(= (:last-trade-time %)
                                                      (:last-trade-time (last peaks)))
                                                  ech-list)))))
    
              (conj rslt (assoc (last ech-list) :signals [{:signal :down
                                                           :why :bollinger-close-abouve
                                                           :arguments [ech-list peaks]}]))
    
              (conj rslt (last ech-list)))
    
            (if (and (> (:last-trade-price (last ech-list)) (:lower-band (last ech-list)))
                     (< (:last-trade-price (last valleys))
                        (:lower-band (last (some #(= (:last-trade-time %)
                                                     (:last-trade-time (last valleys)))
                                                 ech-list)))))
    
              (conj rslt (assoc (last ech-list) :signals [{:signal :up
                                                           :why :bollinger-close-below
                                                           :arguments [ech-list valleys]}]))
    
              (conj rslt (last ech-list)))))))
  • Of our overall 20 tick window, we have the three most narrow bands (most-narrow). Using these, we will determine whether the latest difference in Bollinger Bands is less than the last three most narrow bands (less-than-any-narrow?).
  • If the latest difference is less than the previous three most narrow bands, and we're in an up market, then we can determine the exit or :down signal. We will also determine the close outside (above) the upper band, and whether the previous swing is high inside the band:
    (and (< (:last-trade-price (last ech-list)) (:upper-band (last ech-list)))
         (> (:last-trade-price (last peaks))
            (:upper-band (first (some #(= (:last-trade-time %)
                                          (:last-trade-time (last peaks)))
                                      ech-list)))))

If the last condition is true, we will return a down signal. Otherwise, we will just return the latest tick:

{:signal :down
 :why :bollinger-close-abouve
 :arguments [ech-list peaks]}

If the latest difference is less than the last three most narrow bands, and we're in a down market, then we make a determination on the entry or :up signal. Is the close outside (below) the lower band, and is the previous swing low inside the band?

(and (> (:last-trade-price (last ech-list)) (:lower-band (last ech-list)))
     (< (:last-trade-price (last valleys))
        (:lower-band (last (some #(= (:last-trade-time %)
                                     (:last-trade-time (last valleys)))
                                 ech-list)))))

If this is the case, we return an up signal. Otherwise, we just return the latest tick:

{:signal :up
 :why :bollinger-close-below
 :arguments [ech-list valleys]}

This concludes strategy A. Strategy "B" is slightly more involved, where we employ the RSI. Determining the set of conditions that are true is the minutiae that we will look at here:

(defn calculate-strategy-b [rslt ech-list most-wide peaks valleys]

  (let [latest-diff (- (:upper-band (last ech-list)) (:lower-band (last ech-list)))
        more-than-any-wide? (some (fn [inp] (> latest-diff (:difference inp))) most-wide)]

    (if more-than-any-wide?

      ;; B iii RSI Divergence
      (let [
            OVER_BOUGHT 80
            OVER_SOLD 20
            rsi-list (relative-strength-index 14 ech-list)


            ;; i. price makes a higher high and
            higher-highPRICE? (if (empty? peaks)
                                false
                                (> (:last-trade-price (last ech-list))
                                   (:last-trade-price (last peaks))))


            ;; ii. rsi divergence makes a lower high
            lower-highRSI? (if (or (empty? peaks)
                                   (some #(nil? (:last-trade-time %)) rsi-list)
                                   #_(not (nil? rsi-list))
                                )
                             false
                             (< (:rsi (last rsi-list))
                                (:rsi (last (filter (fn [inp]
                                                       (= (:last-trade-time inp)
                                                          (:last-trade-time (last peaks))))
                                                     rsi-list)))))

            ;; iii. and divergence should happen abouve the overbought line
            divergence-overbought? (> (:rsi (last rsi-list))
                                      OVER_BOUGHT)



            ;; i. price makes a lower low
            lower-lowPRICE? (if (or (empty? valleys)
                                    (some #(nil? (:last-trade-time %)) rsi-list))
                                false
                                (< (:last-trade-price (last ech-list))
                                   (:last-trade-price (last valleys))))

            higher-highRSI? (if (or (empty? valleys)
                                    (not (nil? rsi-list)))
                              false
                              (> (:rsi (last rsi-list))
                                 (:rsi (last (filter (fn [inp]
                                                        (= (:last-trade-time inp)
                                                           (:last-trade-time (last valleys))))
                                                      rsi-list)))))

            divergence-oversold? (< (:rsi (last rsi-list))
                                    OVER_SOLD)]

        (if (and higher-highPRICE? lower-highRSI? divergence-overbought?)

          (conj rslt (assoc (last ech-list) :signals [{:signal :down
                                                       :why :bollinger-divergence-overbought
                                                       :arguments [peaks ech-list rsi-list]}]))

          (if (and lower-lowPRICE? higher-highRSI? higher-highRSI? divergence-oversold?)

            (conj rslt (assoc (last ech-list) :signals [{:signal :up
                                                         :why :bollinger-divergence-oversold
                                                         :arguments [valleys ech-list rsi-list]}]))

            (conj rslt (last ech-list)))))

      (conj rslt (last ech-list)))))

Starting with the first let block, we can determine whether the latest Bollinger Band difference is wider than the three widest points in the last 20 ticks (more-than-any-wide?). If it is, then we can apply our algorithm:

  • We first set the OVER_BOUGHT and OVER_SOLD levels
  • Then, for a given 20-tick list (ech-list), calculate a list of 14-tick windows that are relative to the strength indices (rsi-list)

If the latest price is higher than the last peak, then we have a price that is higher than the last high price (higher-highPRICE?). We need two other, accompanying metrics:

  • Is the latest RSI less than the latest (time-corresponding) tick from our peaks? If so, then we have a lower high RSI (lower-highRSI?).
  • Is the latest RSI above the OVER_BOUGHT line? If so, then we have an RSI divergence that is overbought (divergence-overbought?).

If the latest price is lower than the last valley, then we have a price that is lower than the last low price (lower-lowPRICE?). Again, we'll need two other accompanying metrics:

  • Is the latest RSI greater than the latest (time-corresponding) tick from our valleys? If so, then we have a higher RSI than our last low tick (higher-highRSI?).
  • Now if the latest RSI is less than the OVER_SOLD level, then we have an RSI divergence that is over sold (divergence-sold?).

In effect, over a given range, if we have a convergence of a higher high price, lower high RSI, and the RSI divergence is overbought, then this indicates a :down (or sell) signal.

If we have a convergence of a lower low price, higher high RSI, and a divergence that is oversold, then we have an :up (or buy) signal.

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

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