This is part of a series on technical analysis indicators in F#, based on the multi-language TA-Lib.

Quick disclaimer : some of these indicators are not verified.

Bands

namespace Trading.Studies

module Band =

  let bandsLookback = 0

  [<TradingStudy;
    Group("Bands");
    Title("Bands");
    Description("Returns an enveloppe built by scaling the input series");
    OutputSeriesNames("lower, upper");
    Overlay;
  >]
  let bands factor (data:float[]) =
    Study.checkPositiveReal factor
    let lookbackTotal = bandsLookback
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute2 startIdx endIdx (fun outLen ->
      Array.map (fun x -> x * (1.0 - factor)) data,
      Array.map (fun x -> x * (1.0 + factor)) data
    )

  //Bollinger bands
  let bbandsLookback (ma:MA) period =
    max (Stat.stDevLookback period) (ma.lookback period)

  [<TradingStudy;
    Group("Bands");
    Title("Bollinger Bands");
    Description("Returns the Bollinger bands");
    OutputSeriesNames("lower, middle, upper");
    Overlay;
  >]
  let bbands (ma:MA) period stdevUp stdevDown (data:float[]) =
    Study.checkPositiveIntMin1 period
    Study.checkPositiveReal stdevUp
    Study.checkPositiveReal stdevDown
    let lookbackTotal = bbandsLookback ma period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute3 startIdx endIdx (fun outLen ->
      let mid = ma.create period data
      let stdev = Stat.stDev false period data
      let h = Array.map2 (fun mid stdev -> mid + stdevUp * stdev) mid stdev
      let l = Array.map2 (fun mid stdev -> mid - stdevDown * stdev) mid stdev
      l, mid, h
    )

  //Donchian channel
  let dchanLookback period =
    Stat.maxLookback period

  [<TradingStudy;
    Group("Bands");
    Title("Donchian Channel");
    Description("Returns the Donchian channel");
    OutputSeriesNames("lower, upper");
    Overlay;
  >]
  let dchan period (h:float[]) l =
    let lookbackTotal = dchanLookback period
    let startIdx = lookbackTotal
    let endIdx = h.Length - 1
    Study.lazyCompute2 startIdx endIdx (fun outLen ->
      (Stat.min period l, Stat.max period h)
    )

  //keltner bands
  let kbandsLookback (ma:MA) period =
    max (ma.lookback period + Price.typLookback) (Volatility.atrLookback period)

  [<TradingStudy;
    Group("Bands");
    Title("Keltner Bands");
    Description("Returns the Keltner bands");
    OutputSeriesNames("lower, middle, upper");
    Overlay;
  >]
  let kbands (ma:MA) period devUp devDown (h:float[]) l c =
    Study.checkPositiveIntMin1 period
    Study.checkPositiveReal devUp
    Study.checkPositiveReal devDown
    Study.checkHighLowClose h l c
    let lookbackTotal = kbandsLookback ma period
    let startIdx = lookbackTotal
    let endIdx = h.Length - 1
    Study.lazyCompute3 startIdx endIdx (fun outLen ->
      let typ = Price.typ h l c
      let mid = ma.create period typ
      let atr = Volatility.atr period h l c
      let offset = atr.Length - mid.Length
      if offset > 0 then
        let h = Array.init mid.Length (fun i -> mid.[i] + atr.[i+offset] * devUp)
        let l = Array.init mid.Length (fun i -> mid.[i] - atr.[i+offset] * devDown)
        (l, mid, h)
      else
        //offset is negative, so we substract it
        let h = Array.init atr.Length (fun i -> mid.[i-offset] + atr.[i] * devUp)
        let l = Array.init atr.Length (fun i -> mid.[i-offset] - atr.[i] * devDown)
        (h, mid, l)
    )

Comments are closed.