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.

Moving Averages

namespace Trading.Studies

type MA =
  | Simple
  | Wilder
  | Exponential
  | DoubleExponential
  | TripleExponential
  | Tillson3 of float
  | Triangular
  | Weighted
  | KaufmanAdaptive
  | ZeroLag of float
  | VolumeWeighted of float[]
  | Hull
  | SineWeighted 

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MA =

  let smaLookback period = Stat.meanLookback period

  [<TradingStudy;
    Group("Smoothing");
    Title("Simple MA");
    Description("Returns the simple moving average");
    Overlay;
  >]
  let sma period data = Stat.mean period data

  let baseEmaLookback period = period - 1

  let baseEMA period coeff (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = baseEmaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let out = Array.zeroCreate outLen

      let n = float period

      for i in startIdx - lookbackTotal .. startIdx do
        out.[0] <- out.[0] + (data.[i] / n)

      for i in startIdx+1 .. endIdx do
        let outIdx = i - startIdx
        let prev = out.[outIdx-1]
        out.[outIdx] <- prev + coeff * (data.[i] - prev)

      out
    )

  let wilmaLookback period = baseEmaLookback period

  [<TradingStudy;
    Group("Smoothing");
    Title("Wilder MA");
    Description("Returns the Wilder moving average");
    Overlay;
  >]
  let wilma period data =
    let coeff = 1.0 / float period
    baseEMA period coeff data

  let emaLookback period = baseEmaLookback period

  [<TradingStudy;
    Group("Smoothing");
    Title("Exponential MA");
    Description("Returns the exponential moving average");
    Overlay;
  >]
  let ema period data =
    let coeff = 2.0 / (1.0 + float period)
    baseEMA period coeff data    

  let demaLookback period = 2 * (emaLookback period)

  [<TradingStudy;
    Group("Smoothing");
    Title("Double Exponential MA");
    Description("Returns the double exponential moving average");
    Overlay;
  >]
  let dema period (data:float[]) =
    let lookbackTotal = baseEmaLookback period
    let startIdx = demaLookback period
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let ema1 = ema period data
      let ema2 = ema period ema1
      let offset = ema1.Length - ema2.Length
      Array.init ema2.Length (fun i -> 2.0 * ema1.[i + offset] - ema2.[i])
    ) 

  let temaLookback period = 3 * (emaLookback period)

  [<TradingStudy;
    Group("Smoothing");
    Title("Triple Exponential MA");
    Description("Returns the triple exponential moving average");
    Overlay;
  >]
  let tema period (data:float[]) =
    let lookbackTotal = baseEmaLookback period
    let startIdx = temaLookback period
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let ema1 = ema period data
      let ema2 = ema period ema1
      let ema3 = ema period ema2
      let o13 = ema1.Length - ema3.Length
      let o23 = ema2.Length - ema3.Length
      Array.init ema3.Length (fun i ->
        3.0 * ema1.[i + o13] - 3.0 * ema2.[i + o23] + ema3.[i]
      )
    )

  (* Tim Tillson - TechnicalAnalysis of Stocks and Commodities - January 1998 *)
  let t3Lookback period = 6 * (emaLookback period)

  [<TradingStudy;
    Group("Smoothing");
    Title("Tillson's T3");
    Description("Returns Tillson's T3 - TechnicalAnalysis of Stocks and Commodities (Jan. 1998)");
    Overlay;
  >]
  let t3 period volumeFactor (data:float[]) =
    let lookbackTotal = baseEmaLookback period
    let startIdx = t3Lookback period
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let ema1 = ema period data
      let ema2 = ema period ema1
      let ema3 = ema period ema2
      let ema4 = ema period ema3
      let ema5 = ema period ema4
      let ema6 = ema period ema5
      //constants
      let vf2 = volumeFactor * volumeFactor
      let vf3 = vf2 * volumeFactor
      let c1 = -vf3
      let c2 = 3.0 * (vf2 + vf3)
      let c3 = -6.0*vf2 - 3.0*(volumeFactor + vf3)
      let c4 = 1.0 + 3.0*volumeFactor + vf3 + 3.0*vf2
      //offsets
      let lookbackOneEma = emaLookback period
      let o36 = 3 * lookbackOneEma
      let o46 = 2 * lookbackOneEma
      let o56 = lookbackOneEma
      //output
      Array.init ema6.Length (fun i ->
        c1*ema6.[i] + c2*ema5.[i+o56] + c3*ema4.[i+o46] + c4*ema3.[i+o36]
      )
    )

  (* A triangular SMA is an SMA of SMA whose period are computed as follow :
      if period = even -> SMA(period/2 + 1, SMA(period/2, data))
      if period = odd -> SMA((period+1)/2, SMA((period+1)/2 , data))
  *)
  let trimaLookback period = smaLookback period

  [<TradingStudy;
    Group("Smoothing");
    Title("Triangular MA");
    Description("Returns the triangular moving average");
    Overlay;
  >]
  let trima period (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = trimaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let period1, period2 =
        if period % 2 = 0 then
          let tmp = period / 2
          tmp, tmp + 1
        else
          let tmp = (period+1) / 2
          tmp, tmp  

      sma period1 data |> sma period2
    )

  let wmaLookback period = period - 1

  [<TradingStudy;
    Group("Smoothing");
    Title("Weighted MA");
    Description("Returns the weighted moving average");
    Overlay;
  >]
  let wma period (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = wmaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let out = Array.zeroCreate outLen

      let n = float period
      let coeff = n * (n + 1.0) / 2.0
      let mutable substract = 0.0
      let mutable acc = 0.0

      let mutable weight = 1.0
      for i in startIdx - lookbackTotal .. startIdx do
        acc <- acc + data.[i] * weight
        weight <- weight + 1.0
        substract <- substract + data.[i]

      out.[0] <- acc / coeff

      for i in startIdx + 1 .. endIdx do
        let outIdx = i - startIdx
        acc <- acc + (n * data.[i]) - substract
        substract <- substract + data.[i] - data.[i-period]
        out.[outIdx] <- acc / coeff

      out
    )

  (*  Perry Kaufman - Stocks & Commodities magazine - March, 1995
      Note that contrary to most moving averages, even if period = 1, the output
      differs from the input. This is because "period" doesn't refer to the number
      of values serving as an input to construct an output, but it corresponds
      to an index offset, in the same way as it does for the Rate of change or momentum
      indicators
  *)
  let kamaLookback period =
    Math.sumLookback period + BaseStudies.momLookback 1

  [<TradingStudy;
    Group("Smoothing");
    Title("Kaufman Adaptive MA");
    Description("Returns the Kaufman adaptive moving average");
    Overlay;
  >]
  let kama period (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = kamaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let out = Array.zeroCreate outLen

      let direction = BaseStudies.mom period data

      let volatility =
        BaseStudies.mom 1 data |> Array.map abs |> Math.sum period

      let slow = 2.0 / 31.0
      let diff = (2.0/3.0) - slow

      let smoothAux i currentVolatility =
        let efficiencyRatio =
          let dir = direction.[i]
          if currentVolatility <> 0.0 then abs <| dir / currentVolatility else 1.0
        (slow + diff * efficiencyRatio) ** 2.0

      let smooth = Array.mapi smoothAux volatility

      let mutable prev = data.[startIdx-1]
      out.[0] <- prev + smooth.[0] * (data.[startIdx] - prev)

      for today in startIdx + 1 .. endIdx do
        let outIdx = today - startIdx
        prev <- out.[outIdx - 1]
        out.[outIdx] <- prev + smooth.[outIdx] * (data.[today] - prev)

      out
    )

  //==========================================
  //
  // NOT CHECKED
  //
  //==========================================  

  let zlmaLag period = (period - 1) / 2

  let zlmaLookback period = emaLookback period + zlmaLag period

  [<TradingStudy;
    Group("Smoothing");
    Title("Zero-Lag MA");
    Description("Returns the zero-lag moving average");
    Overlay;
  >]
  let zlma k period data =
    data
      |> BaseStudies.wmom (1.0+k) k (zlmaLag period)
      |> ema period

  let vwmaLookback period = period - 1

  [<TradingStudy;
    Group("Smoothing");
    Title("Volume-Weighted MA");
    Description("Returns the volume-weighted moving average");
    Overlay;
  >]
  let vwma period volume (data:float[]) =
    Study.checkPositiveIntMin1 period
    Study.checkVolume volume
    let lookbackTotal = vwmaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let out = Array.zeroCreate outLen

      let mutable sum = 0.0
      let mutable sumProd = 0.0

      for i in startIdx - lookbackTotal .. startIdx do
        sum <- sum + volume.[i]
        sumProd <- sumProd + (data.[i] * volume.[i])

      out.[0] <- sumProd / sum

      let trailingStartIdx = startIdx - lookbackTotal
      for i in startIdx + 1 .. endIdx do
        let outIdx = i - startIdx
        let trailingIdx = i - (lookbackTotal+1)
        sum <- sum + volume.[i] - volume.[trailingIdx]
        sumProd <- sumProd + (data.[i] * volume.[i]) - (data.[trailingIdx] * volume.[trailingIdx])
        out.[outIdx] <- sumProd / sum

      out
    )

  let hmaSquareRoot = float >> sqrt >> int

  let hmaLookbackSquareRoot period =
    wmaLookback (hmaSquareRoot period)

  let hmaLookback period =
    wmaLookback period + hmaLookbackSquareRoot period

  [<TradingStudy;
    Group("Smoothing");
    Title("Hull MA");
    Description("Returns the Hull moving average");
    Overlay;
  >]
  let hma period (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = hmaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let wma1 = wma (period/2) data
      let wma2 = wma period data
      let offset = wma1.Length - wma2.Length
      let deltas = Array.init wma2.Length (fun i -> 2.0 * wma1.[i+offset] - wma2.[i])
      wma (hmaSquareRoot period) deltas
    )

  let swmaLookback period = period - 1

  let swmaWeights period =
    let n = float period
    Array.init period (fun i -> (float (i+1) * System.Math.PI) / n |> sin)

  [<TradingStudy;
    Group("Smoothing");
    Title("Sine-Weighted MA");
    Description("Returns the sine-weighted moving average");
    Overlay;
  >]
  let swma period (data:float[]) =
    Study.checkPositiveIntMin1 period
    let lookbackTotal = vwmaLookback period
    let startIdx = lookbackTotal
    let endIdx = data.Length - 1
    Study.lazyCompute startIdx endIdx (fun outLen ->
      let ws = swmaWeights period
      let denom = Array.reduce (+) ws
      Array.init outLen (fun i ->
        let mutable sum = 0.0
        let today = startIdx + i
        for weightIdx in 0 .. period - 1 do
          sum <- sum + ws.[weightIdx] * data.[today - weightIdx]
        sum / denom
      )
    )

type MA
  with
    member x.create p =
      match x with
      | Simple -> MA.sma p
      | Wilder -> MA.wilma p
      | Exponential -> MA.ema p
      | DoubleExponential -> MA.dema p
      | TripleExponential -> MA.trima p
      | Tillson3 volumeFactor -> MA.t3 p volumeFactor
      | Triangular -> MA.trima p
      | Weighted -> MA.wma p
      | KaufmanAdaptive -> MA.kama p
      | ZeroLag k -> MA.zlma k p
      | VolumeWeighted volumeData -> MA.vwma p volumeData
      | Hull -> MA.hma p
      | SineWeighted -> MA.swma p 

    member x.lookback p =
      match x with
      | Simple -> MA.smaLookback p
      | Wilder -> MA.wilmaLookback p
      | Exponential -> MA.emaLookback p
      | DoubleExponential -> MA.demaLookback p
      | TripleExponential -> MA.trimaLookback p
      | Tillson3 volumeFactor -> MA.t3Lookback p
      | Triangular -> MA.trimaLookback p
      | Weighted -> MA.wmaLookback p
      | KaufmanAdaptive -> MA.kamaLookback p
      | ZeroLag _ -> MA.zlmaLookback p
      | VolumeWeighted volumeData -> MA.vwmaLookback p
      | Hull -> MA.hmaLookback p
      | SineWeighted -> MA.swmaLookback p 

Comments are closed.