Ok, it’s presidential election time, and some bored people on the trading floor are making markets. How to make money by betting against them ?
Read more »
Functions to extend the F# Event module.
Read more »
This post describes a module to perform common socket operations using asynchronous operations and reactive programming.
Read more »
Every day, the European Central Bank publishes “official” reference FX (foreign exchange) rates where currencies are quoted against the Euro (expressed as EUR/XXX), or 1 EUR = xxx XXX.
The exchange rates of the last thirty sessions are available in xml format which we shall try to parse.
In order to do so, we use F# and active patterns, based on Don Syme’s draft paper.
Last time, we provided the components to actually make the SEO helpers into a real application.
Last time, we created a library of Search Engine Optimization (SEO) helpers. This time we’ll see how to use them to actually fetch information about your webiste rankings (and your competitors’).
In this post, we describe how to implement functions that help fetch information that can be used to study how well (or bad) the keywords are used on a given website in comparison with other (successful) competitors.
Update (June 25): some functions were modified to better fit the needs of the example application.
Update (June 27): the html code of Google links changes between the first page and the next ones. Hence the “old” code would only find links in the first page. The code has been modified to take this into account.
Update (June 28): shame on me, the google, bing and yahoo code for backlinks were buggy. Hopefully now that works !
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.
Reflection : retrieve information on the studies
In this part, we show how to use reflection to fetch the metadata attributes we have described in the first part of the technical analysis series.
namespace Trading.Chart open System open System.Reflection open Microsoft.FSharp.Reflection open Trading.Studies // // Store info // module Reflection = type ParamInfo(p:ParameterInfo) = let name = let arr = p.GetCustomAttributes(typeof<DisplayNameAttribute>, false) if arr.Length = 0 then p.Name else (arr.[0] :?> DisplayNameAttribute).Name let defaultValue = let arr = p.GetCustomAttributes(typeof<DefaultValueAttribute>, false) if arr.Length = 0 then None else Some (arr.[0] :?> DefaultValueAttribute) let isBool = let arr = p.GetCustomAttributes(typeof<BoolAttribute>, false) arr.Length <> 0 let numAttr = let arr = p.GetCustomAttributes(typeof<NumericAttribute>, false) if arr.Length = 0 then None else Some (arr.[0] :?> NumericAttribute) let allPositiveAttr = let arr = p.GetCustomAttributes(typeof<AllPositiveAttribute>, false) arr.Length <> 0 member x.Name = name member x.Type = p.ParameterType.Name member x.Position = p.Position member x.IsBool = isBool member x.NumericAttribute = numAttr member x.AllPositiveAttribute = allPositiveAttr override x.ToString() = sprintf "{Name=%s; Type=%s; Position:%d; IsBool:%A; IsNumeric:%s}" x.Name x.Type x.Position x.IsBool ( match x.NumericAttribute with | None -> match x.Type.ToLower() with | "int32" | "double" -> "yes;" | _ -> "no;" | Some attr -> let min = if attr.MinValue = "nan" then "" else sprintf "min:%s; " attr.MinValue let max = if attr.MaxValue = "nan" then "" else sprintf "max:%s; " attr.MaxValue let step = if attr.Step = "nan" then "" else sprintf "step:%s;" attr.Step let np = if (min.Length > 0 || max.Length > 0 || step.Length > 0) then sprintf " NumericParams:{%s%s%s}" min max step else "" sprintf "yes;%s" np ) type StudyInfo = { Group : string Title : string Description : string DoesOverlay : bool HasMultipleInputSeries : bool Parameters : ParamInfo[] OutputTypes : string[] OutputSeriesNames : string[] } with static member create g t desc d multipleSeries p ot series = { Group = g Title = t Description = desc DoesOverlay = d HasMultipleInputSeries = multipleSeries Parameters = p OutputTypes = ot OutputSeriesNames = series } //==================================================================== // // Extract the meta information from studies within the // assembly containing "Trading.Studies.TradingStudyAttribute" // //==================================================================== let getMetainfo (m:MethodInfo) = let ps = m.GetParameters() |> Array.map (fun p -> new ParamInfo(p)) let outTypes = let ty = m.ReturnType if FSharpType.IsTuple ty then FSharpType.GetTupleElements ty |> Array.map (fun ty -> ty.Name) else [|ty.Name|] let group = let arr = m.GetCustomAttributes((typeof<GroupAttribute>),false) if arr.Length = 0 then "" else (arr.[0] :?> GroupAttribute).Group let title = let arr = m.GetCustomAttributes((typeof<TitleAttribute>),false) if arr.Length = 0 then "" else (arr.[0] :?> TitleAttribute).Title let description = let arr = m.GetCustomAttributes((typeof<DescriptionAttribute>),false) if arr.Length = 0 then "" else (arr.[0] :?> DescriptionAttribute).Description let hasMultipleSeries = let arr = m.GetCustomAttributes((typeof<MultipleInputSeriesAttribute>),false) arr.Length <> 0 let seriesNames = let arr = m.GetCustomAttributes((typeof<OutputSeriesNamesAttribute>),false) if arr.Length = 0 then [|"output"|] else (arr.[0] :?> OutputSeriesNamesAttribute).Series let doesOverlay = let arr = m.GetCustomAttributes((typeof<OverlayAttribute>),false) arr.Length <> 0 StudyInfo.create group title description doesOverlay hasMultipleSeries ps outTypes seriesNames let isStudy (m:MemberInfo) = (m.GetCustomAttributes((typeof<TradingStudyAttribute>),false)).Length <> 0 && (m :? MethodInfo) let getStudiesInfo() = typeof<Trading.Studies.TradingStudyAttribute>.Assembly.GetTypes() |> Array.filter FSharpType.IsModule |> Array.map (fun m -> m.GetMembers() |> Array.filter isStudy ) |> Array.concat |> Array.map (fun m -> (m.Name, getMetainfo (m :?> MethodInfo))) |> Map.ofArray
Example use
do for KeyValue(name, info) in Trading.Chart.Reflection.getStudiesInfo() do printfn "%s : %A" name info
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.
Ehlers
Most of these studies are recursive, and thus the first 50 values or so should be discarded to saty out of the “construction phase” of the series.
namespace Trading.Studies // // John Ehlers - mostly from Cybernetic Analysis for stocks and futures (2004) // module Ehlers = //helpers let CURRENT_BAR_6 = 5 let arrayGet (data:float[]) i = if i >= 0 then data.[i] else 0.0 let quadrature (data:float[]) i = let a = arrayGet data i let b = arrayGet data (i-2) let c = arrayGet data (i-4) let d = arrayGet data (i-6) 0.0962*a + 0.5769*b - 0.5769*c - 0.0962*d let inPhase (data:float[]) i = arrayGet data (i-3) //Trigger line used for any study let triggerLookback = 1 [<TradingStudy; Group("Misc"); Title("Trigger Line"); Description("Returns a trigger line to generate cross-over signals"); Overlay; >] let trigger (data:float[]) = Array.init (Array.length data - 1) (fun i -> data.[i+1]) //Fisher transform let fisherTLookback period = Osc.stochFastKLookback period [<TradingStudy; Group("Cycle"); Title("Ehlers Fisher Transform"); Description("Returns the Ehlers Fisher transform of a series - from Cybernetic Analysis for stocks and futures (2004)"); >] let fisherT period (h:float[]) l c = Study.checkPositiveIntMin1 period Study.checkHighLowClose h l c let lookbackTotal = fisherTLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let hh = Stat.max period h let ll = Stat.min period l let mutable normalizedRatio = 0.0 let mutable fisher = 0.0 for i in startIdx .. endIdx do let outIdx = i - startIdx let fastKIdx = outIdx let fastK = let denom = hh.[fastKIdx] - ll.[fastKIdx] if denom <> 0.0 then (c.[i] - ll.[fastKIdx]) / denom else 0.0 normalizedRatio <- (fastK-0.5) + 0.5*normalizedRatio |> min 0.9999 |> max (-0.9999) fisher <- 0.25*log((1.0+normalizedRatio)/(1.0-normalizedRatio)) + 0.5*fisher out.[outIdx] <- fisher out ) //=============================== // // Instantaneous trendline // //=============================== let itrendLookback = MA.trimaLookback 3 [<TradingStudy; Group("Smoothing"); Title("Ehlers Instantaneous Trendline"); Description("Returns the Ehlers instantaneous trendline - from Cybernetic Analysis for stocks and futures (2004)"); Overlay; >] let itrend alpha (data:float[]) = Study.checkPositiveReal alpha let lookbackTotal = itrendLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let trimaEndIdx = min endIdx CURRENT_BAR_6 for i in startIdx .. trimaEndIdx do let outIdx = i - startIdx out.[outIdx] <- 0.25 * (data.[i] + 2.0*data.[i-1] + data.[i-2]) if endIdx > trimaEndIdx then let a = alpha * alpha let b = 1.0 - alpha let c = alpha - (0.25 * a) let d = 0.5 * a let e = alpha - (0.75 * a) let f = 2.0 * b let g = b * b for i in trimaEndIdx+1 .. endIdx do let outIdx = i - startIdx out.[outIdx] <- c*data.[i] + d*data.[i-1] - e*data.[i-2] + f*out.[outIdx-1] - g*out.[outIdx-2] out ) //=============================== // // Cyber cycle // //=============================== let ccycleLookback = 2 let ccycleKnownSmooth startIdx endIdx alpha (data:float[]) (smooth:float[]) = Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let trimaEndIdx = min endIdx CURRENT_BAR_6 for i in startIdx .. trimaEndIdx do let outIdx = i - startIdx out.[outIdx] <- 0.25 * (data.[i] - 2.0*data.[i-1] + data.[i-2]) if endIdx > trimaEndIdx then let a = 1.0 - alpha let b = 1.0 - 0.5 * alpha let c = b * b let d = 2.0 * a let e = a * a for i in trimaEndIdx+1 .. endIdx do let outIdx = i - startIdx //smooth = 4-bar triangular mov avg //first output values = 3-bar triangular mov avg //--> smooth has a one-bar lag vs output values let smoothIdx = outIdx - 1 let x = smooth.[smoothIdx] - 2.0*smooth.[smoothIdx-1] + smooth.[smoothIdx-2] out.[outIdx] <- c*x + d*out.[outIdx-1] - e*out.[outIdx-2] out ) [<TradingStudy; Group("Cycle"); Title("Ehlers Cyber Cycle"); Description("Returns the Ehlers cyber cycle - from Cybernetic Analysis for stocks and futures (2004)"); >] let ccycle alpha (data:float[]) = Study.checkPositiveReal alpha let lookbackTotal = ccycleLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> ccycleKnownSmooth startIdx endIdx alpha data (MA.trima 4 data) ) //=============================== // //Center of gravity // //=============================== let cgLookback period = period - 1 //used for Centrer of gravity && adaptive ceter of gravity let cgAux wsum sum delta = -(wsum / sum) + delta [<TradingStudy; Group("Cycle"); Title("Ehlers Center of Gravity"); Description("Returns the Ehlers center of gravity - from Cybernetic Analysis for stocks and futures (2004)"); >] let cg period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = cgLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let mutable wsum = 0.0 let mutable sum = 0.0 let n = float period let delta = (n + 1.0) / 2.0 let mutable w = n for i in startIdx - lookbackTotal .. startIdx do wsum <- wsum + (w * data.[i]) sum <- sum + data.[i] w <- w - 1.0 out.[0] <- cgAux wsum sum delta for i in startIdx+1 .. endIdx do let outIdx = i - startIdx sum <- sum + data.[i] - data.[i-period] wsum <- wsum + sum - (n * data.[i-period]) out.[outIdx] <- cgAux wsum sum delta out ) //=============================== // //Relative vigor index // //=============================== let rviLookback period = MA.trimaLookback 4 + Math.sumLookback period let rviAux x y = Array.map2 (-) x y |> MA.trima 4 [<TradingStudy; Group("Cycle"); Title("Ehlers Relative Vigor Index"); Description("Returns the Ehlers relative vigor index - from Cybernetic Analysis for stocks and futures (2004)"); >] let rvi period o h l (c:float[]) = Study.checkPositiveIntMin1 period Study.checkOpenHighLowClose o h l c let lookbackTotal = rviLookback period let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let co = rviAux c o |> Math.sum period let hl = rviAux h l |> Math.sum period Array.map2 (fun co hl -> if hl <> 0.0 then co / hl else 0.0) co hl ) //=============================== // // Stochastization & Fisherization // //=============================== let stochFastKLookback period = Osc.stochFastKLookback period [<TradingStudy; Group("Cycle"); Title("Ehlers Fast K"); Description("Returns the Ehlers fast K of a series - from Cybernetic Analysis for stocks and futures (2004)"); >] let stochFastK period data = Osc.stochFastK period data data data let stochFastLookback fastKPeriod slowKPeriod = Osc.stochFastLookback fastKPeriod MA.Weighted slowKPeriod [<TradingStudy; Group("Cycle"); Title("Ehlers Fast Stochastics"); Description("Returns the Ehlers fast K and slow K of a series - from Cybernetic Analysis for stocks and futures (2004)"); >] let stochFast fastKPeriod slowKPeriod data = Osc.stochFast fastKPeriod MA.Weighted slowKPeriod data data data let stochSlowLookback fastKPeriod slowKPeriod slowDPeriod = Osc.stochSlowLookback fastKPeriod MA.Weighted slowKPeriod MA.Weighted slowDPeriod [<TradingStudy; Group("Cycle"); Title("Ehlers Slow Stochastics"); Description("Returns the Ehlers fast K, slow K and slow D of a series - from Cybernetic Analysis for stocks and futures (2004)"); >] let stochSlow fastKPeriod slowKPeriod slowDPeriod data = Osc.stochSlow fastKPeriod MA.Weighted slowKPeriod MA.Weighted slowDPeriod data data data [<TradingStudy; Group("Cycle"); Title("Ehlers Fisher Transform"); Description("Same as fisherT : returns the Ehlers Fisher transform - from Cybernetic Analysis for stocks and futures (2004)"); >] let fisher data = Signal.fisherT data let fisherLookback = Signal.fisherTLookback //=============================== // //Dominant Cycle Period // //=============================== let dcpSmoothLookback = MA.trimaLookback 4 let dcpQ1Lookback = 6 let dcpDPLookback = 1 let dcpMedianDeltaLookback = 4 let dcpLookback = ccycleLookback + dcpQ1Lookback + dcpDPLookback + dcpMedianDeltaLookback let dcpFullAux alpha (ccycle:float[]) = let startIdx = 0 let endIdx = ccycle.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let mutable prevQ1 = 0.0 let mutable prevI1 = 0.0 let mutable q1 = 0.0 let mutable i1 = 0.0 let mutable deltaPhase = 0.0 let mutable medianDelta = Array.zeroCreate 5 let mutable dc = 0.0 let mutable instPeriod = 0.0 let mutable period = 0.0 for i in 0 .. ccycle.Length - 1 do prevQ1 <- q1 prevI1 <- i1 q1 <- quadrature ccycle i * (0.5 + 0.08*instPeriod) i1 <- inPhase ccycle i deltaPhase <- if (q1 <> 0.0 && prevQ1 <> 0.0) then let re = i1/q1 - prevI1/prevQ1 let im = 1.0 + (i1*prevI1)/(q1*prevQ1) re/im else 0.0 medianDelta.[Study.circularIndex i medianDelta] <- deltaPhase let median = Study.median medianDelta |> max 0.1 |> min 1.1 dc <- if median = 0.0 then 15.0 else 6.28318/median + 0.5 instPeriod <- 0.33*dc + 0.67*instPeriod period <- 0.15*instPeriod + 0.85*period out.[i] <- period out ) [<TradingStudy; Group("Cycle"); Title("Ehlers Dominant Cycle Period"); Description("Returns the Ehlers dominant cycle period - from Cybernetic Analysis for stocks and futures (2004)"); >] let dcp alpha (data:float[]) = (ccycle alpha data |> dcpFullAux alpha).[dcpLookback-ccycleLookback..] //=============================== // //Adaptive Cyber Cycle // //=============================== let accycleLookback = dcpLookback [<TradingStudy; Group("Cycle"); Title("Ehlers Adaptive Cyber Cycle"); Description("Returns the Ehlers adaptive cyber cycle - from Cybernetic Analysis for stocks and futures (2004) - Adaptive indicators rely on the Ehlers dominant cycle period rather than on a fixed period"); >] let accycle alpha (data:float[]) = Study.checkPositiveReal alpha let lookbackTotal = accycleLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen //smooth is also computed by ccycle below //but we need it to compute the adaptive values. //Besides since the formula is recursive, we also need //the values which are used to seed the first dominant cycle period //value, but which are not returned by the indicator. //Hence, we need to reuse the whole dominant cycle period computation. let smooth = MA.trima 4 data let ccycle = ccycleKnownSmooth ccycleLookback endIdx alpha data smooth let dcpFull = dcpFullAux alpha ccycle //adaptive output values let mutable twoAgo = (data.[4] - 2.0*data.[3] + data.[2]) / 4.0 let mutable oneAgo = (data.[5] - 2.0*data.[4] + data.[3]) / 4.0 let mutable current = 0.0 let ccycleStartIdx = startIdx - ccycleLookback //The cyber cycle formula gets recursive after //at its 5th value (ccycle_idx=6) - or currentBar=7. for i in 4 .. ccycle.Length - 1 do //we create a coefficient based on the full dominant cycle period let beta = 2.0 / (dcpFull.[i] + 1.0) let a = 1.0 - beta let b = 1.0 - 0.5 * beta let c = b * b let d = 2.0 * a let e = a * a //smooth returns one bar less than the cyber cycle let smoothIdx = i - 1 let x = smooth.[smoothIdx] - 2.0*smooth.[smoothIdx-1] + smooth.[smoothIdx-2] current <- c*x + d*oneAgo - e*twoAgo twoAgo <- oneAgo oneAgo <- current if i >= ccycleStartIdx then out.[i - ccycleStartIdx] <- current out ) //=============================== // //Adaptive Center of Gravity // //=============================== let acgLookback = dcpLookback [<TradingStudy; Group("Cycle"); Title("Ehlers Adaptive Center of Gravity"); Description("Returns the Ehlers adaptive center of gravity - from Cybernetic Analysis for stocks and futures (2004) - Adaptive indicators rely on the Ehlers dominant cycle period rather than on a fixed period"); >] let acg alpha (data:float[]) = let endIdx = data.Length - 1 dcp alpha data |> Array.mapi (fun dcpIdx period -> //the integer portion of the half dominant cycle period is used let intPart = period / 2.0 |> floor let intPeriod = int intPart let delta = (intPart + 1.0) / 2.0 let mutable wsum = 0.0 let mutable sum = 0.0 let dataIdx = dcpIdx + dcpLookback for i in dataIdx .. -1 .. max 0 (dataIdx - intPeriod + 1) do let w = dataIdx - i + 1 |> float wsum <- wsum + (w * data.[i]) sum <- sum + data.[i] cgAux wsum sum delta ) //=============================== // //Adaptive Relative vigor index // //=============================== let arviDcpAverageLookback = //in fact, it's not a real weighted moving average, but the formula //is pretty resembling MA.wmaLookback 5 let arviLookback = dcpLookback + arviDcpAverageLookback [<TradingStudy; Group("Cycle"); Title("Ehlers Adaptive Relative Vigor Index"); Description("Returns the Ehlers adaptive relative vigor index - from Cybernetic Analysis for stocks and futures (2004) - Adaptive indicators rely on the Ehlers dominant cycle period rather than on a fixed period"); >] let arvi alpha data o h l (c:float[]) = Study.checkPositiveReal alpha Study.checkOpenHighLowClose o h l c let lookbackTotal = arviLookback let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let co = rviAux c o let hl = rviAux h l //Dominant cycle period is smoothed contrary to other adaptive indicators //the smoothing looks like a weighted ma, but the denominator is larger //than the sum of the weights so that the study takes into account the //integer portion of the half dominant cycle period. // //The values taken into account do not follow each other in the average : //we have 4 values spaced over a 5-bar interval. // //More weight is given to the most recent bar. let smoothedPeriod = let dcp = dcp alpha data Array.init (dcp.Length - 4) (fun i -> (dcp.[i] + 2.0*dcp.[i+1] + 3.0*dcp.[i+3] + 4.0*dcp.[i+4]) / 20.0 |> int ) let mutable v1 = 0.0 let mutable v2 = 0.0 for i in startIdx .. endIdx do let valueIdx = i - dcpSmoothLookback let periodIdx = i - startIdx let outIdx = periodIdx v1 <- 0.0 v2 <- 0.0 for j in valueIdx .. -1 .. max 0 (valueIdx - smoothedPeriod.[periodIdx]) do v1 <- v1 + co.[j] v2 <- v2 + hl.[j] out.[outIdx] <- if v2 <> 0.0 then v1 / v2 else 0.0 out ) //=============================== // // Sinewave indicator // //=============================== let sinewaveLookback = dcpLookback [<TradingStudy; Group("Cycle"); Title("Ehlers Sinewave"); Description("Returns the Ehlers sinewave - from Cybernetic Analysis for stocks and futures (2004)"); OutputSeriesNames("sine, lead"); >] let sinewave alpha (data:float[]) = Study.checkPositiveReal alpha let lookbackTotal = sinewaveLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let sine = Array.zeroCreate outLen let sineLead = Array.zeroCreate outLen let cc = ccycle alpha data let dcp = dcp alpha data let mutable re = 0.0 let mutable im = 0.0 let offset = sinewaveLookback - ccycleLookback let mutable dcPhase = 0.0 for periodIdx in 0 .. dcp.Length - 1 do let dcperiod = floor dcp.[periodIdx] re <- 0.0 im <- 0.0 let ccIdx = periodIdx + offset for i in ccIdx .. -1 .. max 0 (ccIdx - int dcperiod + 1) do let theta = let x = ccIdx - i |> float 360.0 * x / dcperiod |> Study.degToRad re <- re + cc.[i] * sin theta im <- im + cc.[i] * cos theta if abs im > 0.001 then dcPhase <- atan(re/im) |> Study.radToDeg elif re > 0.0 then dcPhase <- 90.0 else dcPhase <- -90.0 dcPhase <- dcPhase + 90.0 if im < 0.0 then dcPhase <- dcPhase + 180.0 if dcPhase > 315.0 then dcPhase <- dcPhase - 360.0 let outIdx = periodIdx sine.[outIdx] <- dcPhase |> Study.degToRad |> sin sineLead.[outIdx] <- dcPhase + 45.0 |> Study.degToRad |> sin sine, sineLead ) //=============================== // //Adaptive momentum // //=============================== let amomLookback = dcpLookback [<TradingStudy; Group("Cycle"); Title("Ehlers Adaptive Momentum"); Description("Returns the Ehlers adaptive momentum - from Cybernetic Analysis for stocks and futures (2004) - Adaptive indicators rely on the Ehlers dominant cycle period rather than on a fixed period"); >] let amom cutoff alpha (data:float[]) = Study.checkPositiveReal alpha let lookbackTotal = amomLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen //smooth is also computed by ccycle below //but we need it to compute the adaptive values... argh ! //Besides since the formula is recursive, we also need //the values which are used to seed the first dominant cycle period //value, but which are not returned by the indicator. //Hence, we need to reuse the whole dominant cycle period computation. let smooth = MA.trima 4 data let ccycle = ccycleKnownSmooth ccycleLookback endIdx alpha data smooth let dcpFull = dcpFullAux alpha ccycle //adaptive mom constants let a = exp(-System.Math.PI / cutoff) let b = 2.0 * a * cos(1.7318 * 180.0 / cutoff |> Study.degToRad) let c = a * a let coef2 = b + c let coef3 = -(c + b*c) let coef4 = c * c let coef1 = 1.0 - coef2 - coef3 - coef4 let amomStartIdx = startIdx - ccycleLookback let computeValue1 i = let dataIdx = i + ccycleLookback let dataIdxLag = dataIdx - (int dcpFull.[i] - 1) if dataIdxLag >= 0 then data.[dataIdx] - data.[dataIdxLag] else 0.0 //adaptive mom let mutable value1 = computeValue1 0 //adaptive output values let mutable threeAgo = 0.0 let mutable twoAgo = 0.0 let mutable oneAgo = 0.0 let mutable current = value1 for i in 1 .. ccycle.Length - 1 do //adaptive momentum let value1 = computeValue1 i //Algo says currentBar < 4 //However since the first ccycle bar is at currentBar = 3 //there only needs one such definition, which is done prior to looping current <- coef1*value1 + coef2*oneAgo + coef3*twoAgo + coef4*threeAgo threeAgo <- twoAgo twoAgo <- oneAgo oneAgo <- current //The cyber cycle formula gets recursive after //at its 5th value (ccycle_idx=6) - or currentBar=7. if i >= amomStartIdx then out.[i - amomStartIdx] <- current out ) //=============================== // // Two-Pole Butterworth filter // //=============================== let twoPbfLookback = 0 let twoPoleAux coef1 coef2 coef3 (data:float[]) = let out = Array.zeroCreate data.Length out.[0] <- data.[0] //there could be only one value in the input data. if data.Length > 1 then out.[1] <- data.[1] if data.Length > 2 then let smooth = MA.trima 3 data let recursionStartIdx = MA.trimaLookback 3 let endIdx = data.Length - 1 for i in recursionStartIdx .. endIdx do //The tradestation formula is inconsistent with the e-signal version //the latter seems more in-line with previous formulas, and we therefore use it out.[i] <- coef1*smooth.[i-recursionStartIdx] + coef2*out.[i-1] + coef3*out.[i-2] out [<TradingStudy; Group("Cycle"); Title("Ehlers Two-Pole Butterworth Filter"); Description("Returns the Ehlers two-pole Butterworth filter - from Cybernetic Analysis for stocks and futures (2004)"); >] let twoPbf period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = twoPbfLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //constants let period = float period let a = exp(-1.414*System.Math.PI / period) let b = 2.0 * a * cos(1.414 * 180.0 / period |> Study.degToRad) let coef2 = b let coef3 = (-a) * a let coef1 = (1.0 - b + a*a) / 4.0 twoPoleAux coef1 coef2 coef3 data ) //=============================== // // Three-Pole Butterworth filter // //=============================== let threePbfLookback = 0 let threePoleAux coef1 coef2 coef3 coef4 (data:float[]) = let out = Array.zeroCreate data.Length out.[0] <- data.[0] //there could be only one value in the input data. if data.Length > 1 then out.[1] <- data.[1] if data.Length > 2 then out.[2] <- data.[2] if data.Length > 2 then let smooth = MA.trima 4 data let recursionStartIdx = MA.trimaLookback 4 let endIdx = data.Length - 1 for i in recursionStartIdx .. endIdx do out.[i] <- coef1*smooth.[i-recursionStartIdx] + coef2*out.[i-1] + coef3*out.[i-2] + coef4*out.[i-3] out [<TradingStudy; Group("Cycle"); Title("Ehlers Three-Pole Butterworth Filter"); Description("Returns the Ehlers three-pole Butterworth filter - from Cybernetic Analysis for stocks and futures (2004)"); >] let threePbf period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = threePbfLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //constants let period = float period let a = exp(-1.414*System.Math.PI / period) let b = 2.0 * a * cos(1.414 * 180.0 / period |> Study.degToRad) let c = a * a let coef2 = b + c let coef3 = -(c + b*c) let coef4 = c * c let coef1 = (1.0 - b + c) * (1.0 - c) / 8.0 threePoleAux coef1 coef2 coef3 coef4 data ) //=============================== // // Two-Pole Super Smoother // //=============================== let twoPssLookback = 0 [<TradingStudy; Group("Cycle"); Title("Ehlers Two-Pole Super Smoother"); Description("Returns the Ehlers two-pole super smoother - from Cybernetic Analysis for stocks and futures (2004)"); >] let twoPss period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = twoPssLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //constants let period = float period let a = exp(-1.414*System.Math.PI / period) let b = 2.0 * a * cos(1.414 * 180.0 / period |> Study.degToRad) let coef2 = b let coef3 = (-a) * a let coef1 = (1.0 - b + a*a) / 4.0 twoPoleAux coef1 coef2 coef3 data ) //=============================== // // Three-Pole Super Smoother // //=============================== let threePssLookback = 0 [<TradingStudy; Group("Cycle"); Title("Ehlers Three-Pole Super Smoother"); Description("Returns the Ehlers three-pole super smoother - from Cybernetic Analysis for stocks and futures (2004)"); >] let threePss period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = threePssLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //constants let period = float period let a = exp(-1.414*System.Math.PI / period) let b = 2.0 * a * cos(1.414 * 180.0 / period |> Study.degToRad) let c = a * a let coef2 = b + c let coef3 = -(c + b*c) let coef4 = c * c let coef1 = (1.0 - b + c) * (1.0 - c) / 8.0 threePoleAux coef1 coef2 coef3 coef4 data ) //=============================== // // laguerre Filter // //=============================== let laguerreLookback = 0 [<TradingStudy; Group("Cycle"); Title("Ehlers Laguerre Filter"); Description("Returns the Ehlers Laguerre filter - from Cybernetic Analysis for stocks and futures (2004)"); >] let laguerre gamma (data:float[]) = Study.checkPositiveReal gamma let lookbackTotal = laguerreLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let alpha = 1.0 - gamma let beta = -gamma let mutable prevL0 = 0.0 let mutable prevL1 = 0.0 let mutable prevL2 = 0.0 let mutable prevL3 = 0.0 let mutable L0 = 0.0 let mutable L1 = 0.0 let mutable L2 = 0.0 let mutable L3 = 0.0 for i in 0 .. endIdx do L0 <- alpha*data.[i] + gamma*L0 L1 <- beta*L0 + prevL0 + gamma*L1 L1 <- beta*L1 + prevL1 + gamma*L2 L1 <- beta*L2 + prevL2 + gamma*L3 prevL0 <- L0 prevL1 <- L1 prevL2 <- L2 prevL3 <- L3 if i >= startIdx then out.[i-startIdx] <- (L0 + 2.0*L1 + 2.0*L2 + L3) / 6.0 out ) //=============================== // // Laguerre RSI // //=============================== let laguerreRsiLookback = 0 let laguerreRsiAux x y (cu, cd) = let delta = x - y if delta > 0.0 then (cu + delta, cd) else (cu, cd - delta) [<TradingStudy; Group("Cycle"); Title("Ehlers Laguerre RSI"); Description("Returns the Ehlers Laguerre RSI (relative strength index) - from Cybernetic Analysis for stocks and futures (2004)"); >] let laguerreRsi gamma (data:float[]) = Study.checkPositiveReal gamma let lookbackTotal = laguerreRsiLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let alpha = 1.0 - gamma let beta = -gamma let mutable prevL0 = 0.0 let mutable prevL1 = 0.0 let mutable prevL2 = 0.0 let mutable prevL3 = 0.0 let mutable L0 = 0.0 let mutable L1 = 0.0 let mutable L2 = 0.0 let mutable L3 = 0.0 for i in 0 .. endIdx do L0 <- alpha*data.[i] + gamma*L0 L1 <- beta*L0 + prevL0 + gamma*L1 L2 <- beta*L1 + prevL1 + gamma*L2 L3 <- beta*L2 + prevL2 + gamma*L3 prevL0 <- L0 prevL1 <- L1 prevL2 <- L2 prevL3 <- L3 if i >= startIdx then let CU, CD = (0.0, 0.0) |> laguerreRsiAux L0 L1 |> laguerreRsiAux L1 L2 |> laguerreRsiAux L2 L3 let denom = CU + CD out.[i-startIdx] <- if denom <> 0.0 then CU / denom else 0.0 out ) //=============================== // // Leading indicator // //=============================== let leadLookback = 1 [<TradingStudy; Group("Cycle"); Title("Ehlers Leading Indicator"); Description("Returns the Ehlers leading indicator - from Cybernetic Analysis for stocks and futures (2004)"); >] let lead alpha1 alpha2 (data:float[]) = Study.checkPositiveReal alpha1 Study.checkPositiveReal alpha2 let lookbackTotal = leadLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let a = alpha1 - 2.0 let b = 1.0 - alpha1 let c = 1.0 - alpha2 let mutable lead = 0.0 let mutable netLead = 0.0 for i in startIdx .. endIdx do lead <- 2.0*data.[i] + a*data.[i-1] + b*lead netLead <- alpha2*lead + c*netLead out.[i-startIdx] <- netLead out ) //=============================== // // Fractal Moving Average // //=============================== let framaLookback period = Stat.maxLookback period [<TradingStudy; Group("Smoothing"); Title("Ehlers Fractal Moving Average"); Description("Returns the Ehlers fractal moving average"); Overlay; >] let frama period (data:float[]) = Study.checkPositiveIntMin1 period if period % 2 <> 0 then raise <| BadParam "period must be even" let lookbackTotal = framaLookback 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 n3 = let hh = Stat.max period data let ll = Stat.min period data Array.map2 (fun h l -> (h - l) / n) hh ll let halfPeriod = period / 2 let n = float halfPeriod let n1, n2 = let hh = Stat.max halfPeriod data let ll = Stat.min halfPeriod data let n12 = Array.map2 (fun h l -> (h - l) / n) hh ll n12.[0..n12.Length-1 - halfPeriod], n12.[halfPeriod..] let mutable prevOut = data.[startIdx] let mutable dimen = 0.0 let hpOffset = n1.Length - n3.Length let log2 = log 2.0 for i in 0 .. Array.length n3 - 1 do let hpIdx = i + hpOffset if n1.[hpIdx] > 0.0 && n2.[hpIdx] > 0.0 && n3.[i] > 0.0 then dimen <- (log(n1.[hpIdx] + n2.[hpIdx]) - log(n3.[i])) / log2 let alpha = exp (-4.60 * (dimen - 1.0)) |> max 0.01 |> min 1.0 out.[i] <- alpha*data.[i+lookbackTotal] + (1.0-alpha) * prevOut prevOut <- out.[i] out )
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.
Signal
namespace Trading.Studies module Signal = let fisherTAux x = 0.5 * log ((1.0 + x) / (1.0 - x)) let fisherTLookback = 0 let fisherT (data:float[]) = let lookbackTotal = fisherTLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> data |> Study.toRange (-0.99999) 0.99999 |> Array.map fisherTAux ) let fisherInv x = let x = exp (2.0 * x) (x - 1.0) / (x + 1.0) let fisherInvTLookback = 0 let fisherInvT (data:float[]) = let lookbackTotal = fisherInvTLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> data |> Study.toRange (-0.99999) 0.99999 |> Array.map fisherInv )
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) )
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.
Volatility
namespace Trading.Studies module Volatility = let tHighLookback = 1 [<TradingStudy; Group("Volatility"); Title("True High"); Description("Returns the true high"); Overlay; >] let tHigh h (c:float[]) = Study.checkHighLow h c let lookbackTotal = tHighLookback let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun i -> max c.[i] h.[i + 1]) ) let tLowLookback = 1 [<TradingStudy; Group("Volatility"); Title("True Low"); Description("Returns the true low"); Overlay; >] let tLow l (c:float[]) = Study.checkHighLow c l let lookbackTotal = tLowLookback let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun i -> min c.[i] l.[i + 1]) ) let tRangeLookback = 1 [<TradingStudy; Group("Volatility"); Title("True Range"); Description("Returns the true range"); Overlay; >] let tRange h l (c:float[]) = let lookbackTotal = tRangeLookback let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let th = tHigh h c let tl = tLow l c Array.map2 ( - ) th tl ) let atrLookback period = MA.wilmaLookback period + tRangeLookback [<TradingStudy; Group("Volatility"); Title("Average True Range"); Description("Returns the average true range"); Overlay; >] let atr period h l c = tRange h l c |> MA.wilma period //John Forman - Technical Analysis of Stock & Commodities - May 2006 let natrLookback period = atrLookback period [<TradingStudy; Group("Volatility"); Title("Normalized Average True Range"); Description("Returns the normalized average true range"); >] let natr period h l c = atr period h l c |> Array.mapi (fun i x -> if c.[i + period] <> 0.0 then 100.0 * (x / c.[i + period]) else 0.0 ) //Chaikin Volatility let chVolLookback maPeriod rocPeriod = MA.emaLookback maPeriod + Osc.rocLookback rocPeriod [<TradingStudy; Group("Volatility"); Title("Chaikin Volatility"); Description("Returns the Chaikin volatility"); >] let chVol maPeriod rocPeriod (h:float[]) l = Study.checkHighLow h l Array.map2 (fun h l -> h - l) h l |> MA.ema maPeriod |> Osc.roc true nan rocPeriod //Olivier Seban Super Trend //See : http://hk-lisse.over-blog.com/article-19983903.html [<TradingStudy; Group("Volatility"); Title("Seban Super Trend"); Description("Returns Seban's Super Trend"); Overlay; >] let superTrendLookback period = atrLookback period + 1 let superTrend period devUp devDn high low (close:float[]) = Study.checkPositiveIntMin1 period Study.checkPositiveReal devUp Study.checkPositiveReal devDn Study.checkHighLowClose high low close let lookbackTotal = superTrendLookback period let startIdx = lookbackTotal let endIdx = close.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let atr = atr period high low close let atrLookback = atrLookback period let median =Price.med high low let mutable trend = 1 let mutable prevTrend = 1 let mutable prevUp = median.[startIdx-1] + devUp * atr.[0] let mutable prevDown = median.[startIdx-1] - devDn * atr.[0] for i in startIdx .. endIdx do let atrIdx = i - atrLookback let mutable up = median.[i] + devUp * atr.[atrIdx] let mutable down = median.[i] - devDn * atr.[atrIdx] if close.[i] > prevUp then trend <- 1 elif close.[i] < prevDown then trend <- -1 let flag = if trend < 0 && prevTrend > 0 then 1 else 0 let flagh = if trend > 0 && prevTrend < 0 then 1 else 0 if trend > 0 && down < prevDown then down <- prevDown if trend < 0 && up > prevUp then up <- prevUp if flag = 1 then up <- median.[i] + devUp * atr.[atrIdx] if flagh = 1 then down <- median.[i] - devDn * atr.[atrIdx] out.[i-startIdx] <- if trend = 1 then down else up prevTrend <- trend prevUp <- up prevDown <- down out ) let dmAux highPrev lowPrev highToday lowToday= let deltaHighs = highToday - highPrev let deltaLows = lowPrev - lowToday if (deltaHighs = deltaLows) || (deltaHighs < 0.0 && deltaLows < 0.0) then (0.0, 0.0) elif deltaHighs > deltaLows then (0.0, deltaHighs) else (deltaLows, 0.0) let dmLookback period = MA.wilmaLookback period [<TradingStudy; Group("Volatility"); Title("Directional Movement DM"); Description("Returns the directional movement DM"); OutputSeriesNames("DM-, DM+"); >] let dm period high (low:float[]) = Study.checkPositiveIntMin1 period Study.checkHighLow high low let lookbackTotal = dmLookback period let startIdx = lookbackTotal let endIdx = low.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let lookback = MA.wilmaLookback period //in Wilder's approach to compute the ADX, rather than //using the first n values : the first value is assumed //to be 0.0, and therefore, only the actual n-1 first values //are used in the wilder summations. // //For instance, the first value of a 5-period wilder summation //will be computed as the sum of the first four values of the //input series. Other values are computed normally. // //We therefore create a 0.0-filled array where the first item //is never computed, and thus left at 0.0, prior let n = outLen + lookback + (* 0.0 adjustment *) 1 let dmPlus = Array.zeroCreate n let dmMinus = Array.zeroCreate n let startIdx = startIdx - lookback //leave the first value, 0.0, untouched for today in startIdx + 1 .. endIdx do let prev = today - 1 let (deltaLows, deltaHighs) = dmAux high.[prev] low.[prev] high.[today] low.[today] let outIdx = today - startIdx dmMinus.[outIdx] <- deltaLows dmPlus.[outIdx] <- deltaHighs (Misc.wilSum period dmMinus, Misc.wilSum period dmPlus) ) let diLookback period = dmLookback period + 1 [<TradingStudy; Group("Volatility"); Title("Directional Movement DI"); Description("Returns the directional index DI"); OutputSeriesNames("DI-, DI+"); >] let di period high low (close:float[]) = Study.checkPositiveIntMin1 period Study.checkHighLowClose high low close let lookbackTotal = diLookback period let startIdx = lookbackTotal let endIdx = close.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let diPlus = Array.zeroCreate outLen let diMinus = Array.zeroCreate outLen let dmMinus, dmPlus = dm period high low let summedTRange = let tr = tRange high low close //Create an array with the same number of items + 1 let tmp = Array.zeroCreate (tr.Length + 1) //copy the true range into this array, starting at i=1, //thus leaving the first value at 0.0 Array.blit tr 0 tmp 1 tr.Length //Compute the wilder summation over this expanded array Misc.wilSum period tmp //Ignore the first DI value to compensate for the 0.0 values //that were added for each wilder summation, both in the dm //and in the true range. //Compute other values normally. for i in 1 .. summedTRange.Length - 1 do let outIdx = i - 1 diPlus.[outIdx] <- 100.0 * dmPlus.[i] / summedTRange.[i] diMinus.[outIdx] <- 100.0 * dmMinus.[i] / summedTRange.[i] (diMinus, diPlus) ) let dxLookback period = diLookback period [<TradingStudy; Group("Volatility"); Title("Directional Movement DX"); Description("Returns the directional movement DX"); >] let dx period h l c = let diMinus, diPlus = di period h l c Array.map2 (fun m p -> let diSum = p + m if diSum = 0.0 then 0.0 else abs (p - m) / diSum * 100.0 ) diPlus diMinus let adxLookback dxPeriod adxPeriod = dxLookback dxPeriod + MA.wilmaLookback adxPeriod [<TradingStudy; Group("Volatility"); Title("Directional Movement ADX"); Description("Returns the average directional movement ADX"); >] let adx dxPeriod adxPeriod h l (c:float[]) = let lookbackTotal = adxLookback dxPeriod adxPeriod let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> dx dxPeriod h l c |> MA.wilma adxPeriod ) let adxrLookback dxPeriod adxPeriod adxrPeriod = adxLookback dxPeriod adxPeriod + MA.wilmaLookback adxrPeriod [<TradingStudy; Group("Volatility"); Title("Directional Movement ADXR"); Description("Returns the average directional movement rating ADXR"); >] let adxr dxPeriod adxPeriod adxrPeriod h l (c:float[]) = let lookbackTotal = adxrLookback dxPeriod adxPeriod adxrPeriod let startIdx = lookbackTotal let endIdx = c.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> adx dxPeriod adxPeriod h l c |> MA.wilma adxrPeriod ) //Parabolic stop and reverse let sarLookback = 1 let isLong startsAsLong (high:float[]) (low:float[]) = if startsAsLong <> 0 then startsAsLong > 0 else let dmMinus, dmPlus = dmAux high.[0] low.[0] high.[1] low.[1] dmPlus >= dmMinus //Make sure the value is not too high or too low let minLow (low:float[]) idx v = if idx > 1 then min v low.[idx] |> min low.[idx - 1] elif idx = 1 then min v low.[idx] else invalidArg "idx" "idx must be equal or larger than 1" let maxHigh (high:float[]) idx v = if idx > 1 then max v high.[idx] |> max high.[idx - 1] elif idx = 1 then max v high.[idx] else invalidArg "idx" "idx must be equal or larger than 1" [<TradingStudy; Group("Volatility"); Title("Parabolic SAR - Advanced"); Description("Returns the parabolic stop and reverse with advanced parameters"); Overlay; >] let sarExtended startsAsLong offsetOnReverse aFLongInit aFLongStep aFLongMax aFShortInit aFShortStep aFShortMax (high:float[]) low = Study.checkPositiveReal offsetOnReverse Study.checkPositiveReal aFLongInit Study.checkPositiveReal aFLongStep Study.checkPositiveReal aFLongMax Study.checkPositiveReal aFShortInit Study.checkPositiveReal aFShortStep Study.checkPositiveReal aFShortMax Study.checkHighLow high low let lookbackTotal = sarLookback let startIdx = lookbackTotal let endIdx = high.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen // // Sanitize arguments // let aFLongInit = min aFLongInit aFLongMax let aFShortInit = min aFShortInit aFShortMax let aFLongStep = min aFLongStep (aFLongMax - aFLongInit) let aFShortStep = min aFShortStep (aFShortMax - aFShortInit) let aFLongMax = max aFLongInit aFLongMax let aFShortMax = max aFShortInit aFShortMax let mutable aFLong = aFLongInit let mutable aFShort = aFShortInit let offsetOnReverseToShortCoeff = 1.0 + offsetOnReverse let offsetOnReverseToLongCoeff = 1.0 - offsetOnReverse // // Initialize the algorithm state values // let mutable isLong = isLong startsAsLong high low let mutable ep = 0.0 let mutable sar = 0.0 let prev = startIdx - 1 if isLong then ep <- high.[startIdx] sar <- low.[prev] else ep <- low.[startIdx] sar <- high.[prev] for today in startIdx .. endIdx do let outIdx = today - startIdx if isLong then if low.[today] < sar then //init short isLong <- false sar <- (maxHigh high today ep) * offsetOnReverseToShortCoeff //store the sar out.[outIdx] <- sar //compute next sar ep <- low.[today] aFShort <- aFShortInit sar <- sar + aFShort * (ep - sar) else //store the sar out.[outIdx] <- sar //update long if high.[today] > ep then ep <- high.[today] aFLong <- min aFLongMax (aFLong + aFLongStep) //compute next long let newSar = sar + aFLong * (ep - sar) sar <- minLow low today newSar else if high.[today] > sar then //init long isLong <- true sar <- (minLow low today ep) * offsetOnReverseToLongCoeff //store the sar out.[outIdx] <- sar //compute next sar ep <- high.[today] aFLong <- aFLongInit sar <- sar + aFLong * (ep - sar) else //store the sar out.[outIdx] <- sar //update short if low.[today] < ep then ep <- low.[today] aFShort <- min aFShortMax (aFShort + aFShortStep) //compute next short let newSar = sar + aFShort * (ep - sar) sar <- maxHigh high today newSar out ) [<TradingStudy; Group("Volatility"); Title("Parabolic SAR"); Description("Returns the parabolic stop and reverse"); Overlay; >] let sar aFInit aFStep aFMax high low = sarExtended 0 0.0 aFInit aFStep aFMax aFInit aFStep aFMax high low let maxPeriod p1 p2 p3 = max p1 p2 |> max p3 let largestLookback p1 p2 p3 = maxPeriod p1 p2 p3 |> MA.smaLookback let ultOscLookback p1 p2 p3 = largestLookback p1 p2 p3 + tRangeLookback [<TradingStudy; Group("Volatility"); Title("Ultimate Oscillator"); Description("Returns the ultimate oscillator"); >] let ultOsc p1 p2 p3 high low (close:float[]) = Study.checkPositiveIntMin1 p1 Study.checkPositiveIntMin1 p2 Study.checkPositiveIntMin1 p3 Study.checkHighLowClose high low close let lookbackTotal = ultOscLookback p1 p2 p3 let startIdx = lookbackTotal let endIdx = close.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let periods = [|p1; p2; p3|] |> Array.sort let p1 = periods.[0] //shortest period let p2 = periods.[1] let p3 = periods.[2] //longest period //make true range and delta start so that the slowest //moving average will yield a result at startIdx let trueRange = tRange high low close let delta = let tlow = tLow low close let offset = close.Length - tlow.Length Array.init tlow.Length (fun i -> close.[i+offset] - tlow.[i]) let a1 = Math.sum p1 delta let a2 = Math.sum p2 delta let a3 = Math.sum p3 delta let b1 = Math.sum p1 trueRange let b2 = Math.sum p2 trueRange let b3 = Math.sum p3 trueRange let offset13 = a1.Length - a3.Length let offset23 = a2.Length - a3.Length let alpha = 100.0 / 7.0 Array.init outLen (fun i -> let a = a1.[i+offset13] / b1.[i+offset13] let b = a2.[i+offset23] / b2.[i+offset23] let c = a3.[i] / b3.[i] (4.0 * a + 2.0 * b + c) * alpha ) )
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.
Misc
namespace Trading.Studies module Misc = let slopeLookback (period:int) = period (* (value_n - value_0) / numberOfPeriods *) [<TradingStudy; Group("Misc"); Title("Slope"); Description("Returns the unit slope between n observations"); >] let slope period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = slopeLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let n = float period Array.init (data.Length - period) (fun i -> (data.[i + period] - data.[i]) / n) ) let wilSumLookback period = period - 1 [<TradingStudy; Group("Misc"); Title("Wilder summation"); Description("Returns the Wilder summation"); >] let wilSum period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = wilSumLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen let coeff = 1.0 - (1.0 / float period) for i in startIdx - lookbackTotal .. startIdx do out.[0] <- out.[0] + data.[i] for i in startIdx + 1 .. endIdx do let outIdx = i - startIdx out.[outIdx] <- coeff * out.[outIdx-1] + data.[i] out ) //========================================== // // NOT CHECKED // //========================================== let cftppLookback = 1 ///Chicago Floor Trading Pivotal Point [<TradingStudy; Group("Misc"); Title("Chicago Floor Trading Pivotal Point"); Description("Returns the Chicago floor trading pivotal point"); OutputSeriesNames("s2, s1, r1, r2"); >] let cftpp (h:float[]) l c = Study.checkHighLowClose h l c let lookbackTotal = cftppLookback let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute4 startIdx endIdx (fun outLen -> let typ = Price.typ h l c ( Array.init outLen (fun i -> typ.[i+1] - h.[i] + l.[i]), Array.init outLen (fun i -> 2.0 * typ.[i+1] - h.[i]), Array.init outLen (fun i -> 2.0 * typ.[i+1] - l.[i]), Array.init outLen (fun i -> typ.[i+1] + h.[i] - l.[i]) ) ) let pvrLookback (period:int) = period [<TradingStudy; Group("Volume"); Title("Price Volume Rank"); Description("Returns the price volume rank"); >] let pvr period (data:float[]) volume = Study.checkPositiveIntMin1 period Study.checkSameInputLength [volume; data] Study.checkVolume volume let lookbackTotal = pvrLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //1 : price and volume increase //2 : price increase without volume //3 : price decrease without volume //4 : price decrease with volume Array.init outLen (fun prev -> let today = prev + period if data.[today] > data.[prev] then if volume.[today] > volume.[prev] then 1.0 else 2.0 elif volume.[today] > volume.[prev] then 4.0 else 3.0 ) ) ///Ease of movement let eomLookback = 1 [<TradingStudy; Group("Volume"); Title("Ease of movement"); Description("Returns the ease of movement indicator"); >] let eom volumeDivisor h l volume = Study.checkPositiveReal volumeDivisor Study.checkSameInputLength [volume; h] Study.checkHighLow h l Study.checkVolume volume let lookbackTotal = eomLookback let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun prev -> let today = prev + 1 let delta = h.[today] - l.[today] if delta <> 0.0 && volume.[today] <> 0.0 then (0.5 * (h.[today] - l.[today] - h.[prev] + l.[prev])) / ((volume.[prev] / volumeDivisor) / delta) else 0.0 ) ) ///Trend score let tsLookback period = Math.sumLookback period + 1 [<TradingStudy; Group("Misc"); Title("Trend score"); Description("Returns the ease of movement indicator"); >] let ts period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = tsLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun prev -> let today = prev + 1 if data.[today] >= data.[prev] then 1.0 else (-1.0) ) |> Math.sum period ) (* Tushar S. Chande - March, 1992 - Technical Analysis of Stocks & Commodities magazine A standard deviation was used as the Volatility Index. In his October, 1995 article in the same magazine, Chande modified the VIDYA to use his own Chande momentum Osc (CMO) as the Volatility Index. Examples of volatility indexes : - standard deviation (e.g., 9 period) - standard deviation percentage oscillator (e.g., 10 period divd by 50 period) - Chande momentum Osc (e.g., 9 period) *) let vidyaLookback period = Osc.cmoLookback period - 1 [<TradingStudy; Group("Smoothing"); Title("Vidya MA"); Description("Returns the Vidya (or Variable) moving average"); >] let vidya period (data:float[]) = Study.checkPositiveIntMin1 period let lookbackTotal = tsLookback 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 = 2.0 / (n + 1.0) let cmo = Osc.cmo period data 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 cmoIdx = outIdx let alpha = coeff * cmo.[cmoIdx] out.[outIdx] <- alpha * data.[i] + (1.0 - alpha) * out.[outIdx-1] out )
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.
Oscillators
namespace Trading.Studies module Osc = let wmomLookback period = BaseStudies.wmomLookback period [<TradingStudy; Group("Cycle"); Title("Weighted Momentum"); Description("Returns the weighted momentum"); >] let wmom coeffToday coeffLag period data = BaseStudies.wmom coeffToday coeffLag period data let momLookback period = BaseStudies.momLookback period [<TradingStudy; Group("Cycle"); Title("Momentum"); Description("Returns the momentum"); >] let mom period data = BaseStudies.mom period data let rocLookback period = BaseStudies.rocLookback period [<TradingStudy; Group("Cycle"); Title("Rate of Change"); Description("Returns the rate of change"); >] let roc isBase100 divByZeroDefaultValue period data = BaseStudies.roc isBase100 divByZeroDefaultValue period data (* The Relative momentum Index is a variation on the Relative Strength Index. To determine up and down days, the RSI uses the close compared to the previous close. The RMI uses the close compared to the close n days ago. An RMI with a time period of 1 is equal to the RSI. The Relative momentum Index was developed by Roger Altman and was introduced in his article in the February, 1993 issue of Technical Analysis of Stocks & Commodities magazine. Note that since RMI is more "versatile" than RSI, we define the RSI as a function of the RMI. See the RSI module for more information. RMI = 100 - (100 / (1 + RM)) where RM = average gain / average loss over the period <=> RMI = 100 * (avg_gain / (avg_gain+avg_loss)) average gain = moving average of the up moves average loss = moving average of the down moves where the moving averages have a period = average_period and up_move_i and down_move_i are based upon value_i - value_(i-momentum_period) Note that since gains (resp. losses) are smoothed, there is a convergence effect : the first values of the RMI are less smoothed than the following ones. Therefore, the further one advances in the calculation, the less impact this initial divergence has, the more stability you have. *) let gainsLookback period = period - 1 [<TradingStudy; Group("Misc"); Title("Gains"); Description("Returns the n-period gains"); >] let gains period (data:float[]) = let lookbackTotal = gainsLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> data |> mom period |> Array.map (max 0.0) ) let lossesLookback period = period - 1 [<TradingStudy; Group("Misc"); Title("Losses"); Description("Returns the n-period losses"); >] let losses period (data:float[]) = let lookbackTotal = lossesLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> data |> mom period |> Array.map (min 0.0) ) let rmiLookback momPeriods avgPeriods = momLookback momPeriods + MA.wilmaLookback avgPeriods let rmiAux gain loss = if gain <> loss then 100.0 * (gain / (gain - loss)) else 0.0 [<TradingStudy; Group("Cycle"); Title("Relative Momentum Index"); Description("Returns the relative momentum index"); >] let rmi momPeriods avgPeriods (data:float[]) = let lookbackTotal = rmiLookback momPeriods avgPeriods let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let gains = data |> gains momPeriods |> MA.wilma avgPeriods let losses = data |> losses momPeriods |> MA.wilma avgPeriods Array.map2 (fun gain loss -> rmiAux gain loss) gains losses ) let rsiLookback period = rmiLookback 1 period [<TradingStudy; Group("Cycle"); Title("Relative Strength Index"); Description("Returns the relative strength index"); >] let rsi period data = rmi 1 period data //Tuschar Chande momentum oscillator let cmoLookback avgPeriods = momLookback 1 + MA.wilmaLookback avgPeriods let cmoAux gain loss = if gain <> loss then 100.0 * (gain + loss) / (gain - loss) else 0.0 [<TradingStudy; Group("Cycle"); Title("Chande Momentum Oscillator"); Description("Returns the Chande momentum oscillator"); >] let cmo avgPeriods (data:float[]) = let lookbackTotal = cmoLookback avgPeriods let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let gains = data |> gains 1 |> MA.wilma avgPeriods let losses = data |> losses 1 |> MA.wilma avgPeriods Array.map2 (fun gain loss -> cmoAux gain loss) gains losses ) // Donald R. Lambert - CCI let cciLookback period = MA.smaLookback period [<TradingStudy; Group("Cycle"); Title("Commodity Channel Index"); Description("Returns the cmmodity channel index"); >] let cci period (h:float[]) l c = let lookbackTotal = cciLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let typ = Price.typ h l c let ma = MA.sma period typ let absdev = Stat.avgDev period typ let offset = typ.Length - ma.Length Array.init ma.Length (fun i -> if absdev.[i] <> 0.0 then (typ.[i + offset] - ma.[i]) / (0.015 * absdev.[i]) else 0.0 ) ) let trixLookback period = 3 * MA.emaLookback period + rocLookback 1 [<TradingStudy; Group("Cycle"); Title("Trix"); Description("Returns the Trix"); >] let trix period (data:float[]) = let lookbackTotal = trixLookback period let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> data |> MA.ema period |> MA.ema period |> MA.ema period |> roc true 100.0 1 ) ///Williams %R let willRLookback period = period - 1 let willRAux h l c = let delta = h - l if delta <> 0.0 then (-100.0) * (h - c) / delta else 0.0 [<TradingStudy; Group("Cycle"); Title("Williams %R"); Description("Returns the Williams %R"); >] let willR period h l c = Study.checkPositiveIntMin1 period Study.checkHighLowClose h l c let lookbackTotal = willRLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let hh = Stat.max period h let ll = Stat.max period l let offset = h.Length - hh.Length Array.init outLen (fun i -> willRAux h.[i] l.[i] c.[i+offset]) ) ///Stochastics Fast K let stochFastKLookback period = Stat.maxLookback period let stochFastKAux hh ll c = let delta = hh - ll if delta <> 0.0 then 100.0 * (c - ll) / delta else 0.0 [<TradingStudy; Group("Cycle"); Title("Stochastics Fast K"); Description("Returns the stochastics fast K"); >] let stochFastK period h l c = Study.checkPositiveIntMin1 period Study.checkHighLowClose h l c let lookbackTotal = stochFastKLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let hh = Stat.max period h let ll = Stat.min period l let offset = h.Length - hh.Length Array.init outLen (fun i -> stochFastKAux hh.[i] ll.[i] c.[i+offset]) ) ///Stochastics Fast (Fast K + Slow K) let stochFastLookback fastKPeriod (ma:MA) slowKPeriod = stochFastKLookback fastKPeriod + ma.lookback slowKPeriod [<TradingStudy; Group("Cycle"); Title("Stochastics Fast K/Slow K"); Description("Returns the stochastics fast K and slow K"); OutputSeriesNames("fast K, slow K"); >] let stochFast fastKPeriod (ma:MA) slowKPeriod (h:float[]) l c = let lookbackTotal = stochFastLookback fastKPeriod ma slowKPeriod let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let fastK = stochFastK fastKPeriod h l c let slowK = ma.create slowKPeriod fastK let offset = fastK.Length - slowK.Length fastK.[offset..], slowK ) ///Stochastics Slow (Fast D + Slow D, where Fast D = Slow K) let stochSlowLookback fastKPeriod maK slowKPeriod (maD:MA) slowDPeriod = stochFastLookback fastKPeriod maK slowKPeriod + maD.lookback slowDPeriod [<TradingStudy; Group("Cycle"); Title("Stochastics Slow K/Slow D"); Description("Returns the stochastics slow K and slow D"); OutputSeriesNames("slow K, slow D"); >] let stochSlow fastKPeriod maK slowKPeriod maD slowDPeriod (h:float[]) l c = let lookbackTotal = stochSlowLookback fastKPeriod maK slowKPeriod maD slowDPeriod let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let fastK, slowK = stochFast fastKPeriod maK slowKPeriod h l c let slowD = maD.create slowDPeriod slowK let offset = slowK.Length - slowD.Length slowK.[offset..], slowD ) ///Moving Average Convergence Divergence let macdLookback (ma:MA) slowPeriod signalPeriod = ma.lookback slowPeriod + ma.lookback signalPeriod [<TradingStudy; Group("Cycle"); Title("MACD"); Description("Returns the moving average convergence-divergence"); OutputSeriesNames("macd, signal, histogram"); >] let macd (ma:MA) fastPeriod slowPeriod signalPeriod (data:float[]) = if fastPeriod > slowPeriod then invalidArg "fastPeriod" "fastPeriod > slowPeriod" let lookbackTotal = macdLookback ma slowPeriod signalPeriod let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute3 startIdx endIdx (fun outLen -> let fastMA = ma.create fastPeriod data let slowMA = ma.create slowPeriod data let offset = fastMA.Length - slowMA.Length let macd = Array.init slowMA.Length (fun i -> fastMA.[i + offset] - slowMA.[i]) let signal = ma.create signalPeriod macd let offset = macd.Length - signal.Length let hist = Array.init signal.Length (fun i -> macd.[i + offset] - signal.[i]) macd.[offset..], signal, hist ) ///Detrended price oscillator let dpoPeriod period = period / 2 + 1 let dpoLookback period = MA.smaLookback (dpoPeriod period) [<TradingStudy; Group("Cycle"); Title("Detrended Price Oscillator"); Description("Returns the detrended price oscillator"); >] let dpo period data = let ma = MA.sma (dpoPeriod period) data let offset = data.Length - ma.Length Array.init ma.Length (fun i -> data.[i + offset] - ma.[i]) ///Chande Dynamic Momentum Index ///Tushar S. Chande and Stanley Kroll and is described in their 1994 book The New Technical Trader let cdmiLookback = Stat.stDevLookback 5 + MA.smaLookback 10 [<TradingStudy; Group("Cycle"); Title("Dynamic Momentum Index"); Description("Returns the Chande dynamic momentum index"); >] let cdmi data = let sd = Stat.stDev true 5 data let asd = MA.sma 10 sd let offset = sd.Length - asd.Length Array.init asd.Length (fun i -> 14.0 * asd.[i] / sd.[i + offset]) ///On-Balance Volume let obvLookback = 0 [<TradingStudy; Group("Volume"); Title("On-Balance Volume"); Description("Returns the on-balance volume"); >] let obv (data:float[]) volume = Study.checkSameInputLength [volume; data] Study.checkVolume volume let lookbackTotal = obvLookback let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen out.[0] <- volume.[startIdx] for today in startIdx + 1 .. endIdx do let outIdx = today - startIdx if data.[today] > data.[today - 1] then out.[outIdx] <- out.[outIdx-1] + volume.[today] elif data.[today] < data.[today - 1] then out.[outIdx] <- out.[outIdx-1] - volume.[today] else out.[outIdx] <- out.[outIdx-1] out ) ///Accumulation / Distribution let adLookback = 0 //CLV = ((C - L) - (H - C)) / (H - L) // = (2C - H - L) / (H-L) let volumeClv h l c v = let delta = h - l if delta <> 0.0 then (2.0 * c - h - l) / delta * v else 0.0 let adLineClv h l c volume = Study.checkSameInputLength [volume; h] Study.checkVolume volume Study.checkHighLowClose h l c let lookbackTotal = adLookback let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun i -> volumeClv h.[i] l.[i] c.[i] volume.[i]) ) [<TradingStudy; Group("Volume"); Title("Accumulation / Distribution"); Description("Returns the Chande accumulation / distribution"); >] let ad h l c volume = let adLine = adLineClv h l c volume let startIdx = adLookback let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let out = Array.zeroCreate outLen out.[0] <- adLine.[0] for today in startIdx + 1.. endIdx do let outIdx = today - startIdx out.[outIdx] <- out.[outIdx - 1] + adLine.[today] out ) ///Chaikin Osc let chOscLookback slowPeriod = adLookback + MA.emaLookback slowPeriod [<TradingStudy; Group("Cycle"); Title("Chaikin Oscillator"); Description("Returns the Chaikin oscillator"); >] let chOsc fastPeriod slowPeriod (h:float[]) l c volume = if fastPeriod > slowPeriod then raise <| BadParam "fast period > slowPeriod" let lookbackTotal = chOscLookback slowPeriod let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let ad = ad h l c volume let fast = MA.ema fastPeriod ad let slow = MA.ema slowPeriod ad let offset = fast.Length - slow.Length Array.init slow.Length (fun i -> fast.[i + offset] - slow.[i]) ) ///Chaikin Money Flow let chMfLookback period = adLookback + Math.sumLookback period [<TradingStudy; Group("Cycle"); Title("Chaikin Money Flow"); Description("Returns the Chaikin money flow"); >] let chMf period (h:float[]) l c volume = Study.checkPositiveIntMin1 period let lookbackTotal = chMfLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> //adLineClv has 0 lookback, so we can use a map2 afterward let ad = adLineClv h l c volume |> Math.sum period let vol = Math.sum period volume Array.map2 ( / ) ad vol ) ///Balance of Power let bpowLookback = 0 [<TradingStudy; Group("Cycle"); Title("Balance of Power"); Description("Returns the balance of power"); >] let bpow o h l c = Study.checkOpenHighLowClose o h l c let lookbackTotal = bpowLookback let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> Array.init outLen (fun i -> let idx = startIdx + i let delta = h.[idx] - l.[idx] if delta <> 0.0 then (c.[idx] - o.[idx]) / delta else 0.0 ) ) let aroonLookback period = Stat.maxLookback period + 1 [<TradingStudy; Group("Cycle"); Title("Aroon"); Description("Returns the Aroon indicator"); OutputSeriesNames("aroon down, aroon up"); >] let aroon period h l = Study.checkPositiveIntMin1 period Study.checkHighLow h l let lookbackTotal = aroonLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute2 startIdx endIdx (fun outLen -> let extremasPeriod = period + 1 let highestHighs = Stat.maxIdx extremasPeriod h let lowestLows = Stat.minIdx extremasPeriod l let aroonUp = Array.zeroCreate outLen let aroonDown = Array.zeroCreate outLen let n = float period let coeff = 100.0 / n let f pos extremaIdx = coeff * (n - float (pos - extremaIdx)) Array.iteri2 (fun i hh ll -> let g = f (startIdx + i) aroonUp.[i] <- g hh aroonDown.[i] <- g ll ) highestHighs lowestLows aroonDown, aroonUp ) let aroonOscLookback period = aroonLookback period [<TradingStudy; Group("Cycle"); Title("Aroon oscillator"); Description("Returns the Aroon oscillator"); >] let aroonOsc period h l = let down, up = aroon period h l Array.map2 (-) up down ///Money Flow Index let mfiLookback period = Price.typLookback + Math.sumLookback period let mfiAux plus minus = //100 - (100 / (1 + MoneyRatio)) <=> 100 * sum+ / (sum+ + sum-) //to avoid excessively large values, we ilter based on the denominator let mr = plus / minus if mr = 1.0 then 0.0 else 100.0 * (mr / (1.0 + mr)) [<TradingStudy; Group("Cycle"); Title("Money Flow Index"); Description("Returns the money flow index"); >] let mfi period h l c volume = Study.checkPositiveIntMin1 period Study.checkVolume volume Study.checkHighLowClose h l c let lookbackTotal = aroonLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let tp = Price.typ h l c let mf = Array.map2 ( * ) tp volume let mfPlus = Array.zeroCreate tp.Length let mfMinus = Array.zeroCreate tp.Length for i in 1 .. tp.Length - 1 do if tp.[i] > tp.[i - 1] then mfPlus.[i] <- mfPlus.[i-1] + mf.[i] else mfMinus.[i] <- mfMinus.[i-1] + mf.[i] Array.map2 mfiAux (Math.sum period mfPlus) (Math.sum period mfMinus) ) ///Absolute Price Osc let apoLookback (ma:MA) slowPeriod = ma.lookback slowPeriod [<TradingStudy; Group("Cycle"); Title("Absolute Price Oscillator"); Description("Returns the absolute price oscillator"); >] let apo (ma:MA) fastPeriod slowPeriod (data:float[]) = Study.checkPositiveIntMin1 fastPeriod if fastPeriod > slowPeriod then invalidArg "fastPeriod" "fast period > slow period" let lookbackTotal = apoLookback ma slowPeriod let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let fastMA = ma.create fastPeriod data let slowMA = ma.create slowPeriod data let offset = fastMA.Length - slowMA.Length Array.init slowMA.Length (fun i -> fastMA.[i + offset] - slowMA.[i]) ) ///Percentage Price Osc let ppoLookback (ma:MA) slowPeriod = ma.lookback slowPeriod [<TradingStudy; Group("Cycle"); Title("Percentage Price Oscillator"); Description("Returns the percentage price oscillator"); >] let ppo (ma:MA) fastPeriod slowPeriod (data:float[]) = Study.checkPositiveIntMin1 fastPeriod if fastPeriod > slowPeriod then invalidArg "fastPeriod" "fast period > slow period" let lookbackTotal = apoLookback ma slowPeriod let startIdx = lookbackTotal let endIdx = data.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let fastMA = ma.create fastPeriod data let slowMA = ma.create slowPeriod data let offset = fastMA.Length - slowMA.Length Array.init slowMA.Length (fun i -> 100.0 * (fastMA.[i + offset] / slowMA.[i] - 1.0)) ) ///Vertical Horizon Filter let vhfLookback period = Math.sumLookback period + rocLookback 1 [<TradingStudy; Group("Cycle"); Title("Vertical Horizon Filter"); Description("Returns the vertical horizon filter"); >] let vhf period (h:float[]) l c = Study.checkPositiveIntMin1 period Study.checkHighLowClose h l c let lookbackTotal = vhfLookback period let startIdx = lookbackTotal let endIdx = h.Length - 1 Study.lazyCompute startIdx endIdx (fun outLen -> let highs = Stat.max period h let lows = Stat.min period l let rocSum = roc true 0.0 1 c |> Math.sum period let offset = highs.Length - rocSum.Length Array.init rocSum.Length (fun i -> (highs.[i+offset] - lows.[i+offset]) / rocSum.[i] ) )
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