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 !
Read more »
Production :
20-25 cookies
Ingrédients :
- farine 150g
- levure chimique 3g (1 cuillère à café)
- beurre demi-sel 60g
- beurre de cacahuètes 60g
- sucre blanc 60g
- sucre roux 60g
- œuf moyen 1 (55g)
- extrait de vanille liquide 5g (1½ cuillère à café)
- pépites de chocolat 60-70
Préparation :
Mélangez la farine tamisée et la levure chimique, et laissez le mélange de côté.
Sortir le beurre demi-sel 30-40mn en avance afin d’obtenir un beurre pommade. Incorporez le beurre de cacahuètes, puis les sucre, l’extrait de vanille liquide, et enfin l’œuf.
Incorporez la farine en mélangeant jusqu’à obtention d’un pâton homogène.
Prenez des boulettes de pâte, écrasez-les entre les paumes, et posez-les sur une plaque recouverte de papier sulfurisé.
Faites cuire à 180°C 10 à 12mn (les bords doivent colorer, le centre peu), et attendre que les cookies aient refroidi (et donc durci un peu).
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
)
Ingrédients :
- blancs d’œufs 100g (3 œufs environ)
- sucre semoule 150g (50g par blanc d’œuf)
Préparation :
Préchauffez le four à 90°C.
À l’aide d’un batteur électrique, monter les blancs en neige.
Lorsqu’ils forment des “pics mous” (qui retombent), incorporez le sucre progressivement en continuant à battre les blancs. Verser le sucre progressivement permet de s’assurer qu’il est bien incorporé.
Lorsque tout le sucre a été versé, les blancs doivent en être fermes (vous pouvez retourner le cul-de-poule sans qu’ils bougent, et les pics sont fermes lorsque vous retirez les branches du fouet des blancs). Continuez à battre deux minutes.
Déposer les blancs à la poche à douille (ou à la cuillère à soupe) sur des plaques de cuisson en formant des petits tas (de la taille d’une chouquette environ).
Enfourner les plaques. Laisser cuire deux heures. Sortir et laisser refroidir à température ambiante.
Notes :
La présence de jaunes dans les blancs empêche les blancs de monter de façon satisfaisante.
Si les meringues sont trop collantes, c’est que le sucre n’a pas été assez intégré aux blancs ou que le four était trop chaud.
Ingrédients :
Préparation :
Préparez la crème à l’orange.
Ciselez la menthe et la coriandre. Incorporez à la spatule. Réservez au frais.
Étalez la pâte sucrée sur 5mm et faites cuire à blanc 20 minutes à 180°C (à peu près thermostat 6), puis 10mn sans le papier sulfurisé et les haricots secs (ou les billes etc.).
Ajoutez la crème à l’orange.
Réservez au moins une heure au frais.
Cette recette reprend les proportions utilisées dans “ma” version de la crème au citron de Pierre Hermé, avec un tout petit peu plus de jus d’oranges.
Production :
440g de crème
Ingrédients :
- sucre semoule 100g
- jus d’orange 100g (1½ oranges)
- zestes d’orange 10g (1½ oranges)
- œufs moyens 2 (110g)
- beurre 120g
Préparation :
Prélevez et hachez finement les zestes d’orange. Mettez-les quelques heures dans le sucre en mélangeant de temps en temps (pour parfumer le sucre).
Dans un cul-de-poule, blanchissez les œufs avec le sucre. Ajoutez le jus d’orange et mélangez.
Faites cuire au bain marie jusqu’à 83°C (pour éviter la cuisson des œufs), soit un peu avant l’ébullition en mélangeant en continu. La crème doit avoir épaissi sans avoir trop réduit.
Filtrez le mélange au tamis et mélangez doucement pour refroidir jusqu’à ce que le mélange soit tiède (ce qui, avec cette quantité, est quasiment instantané). Incorporez le beurre découpé en petits morceaux en lissant au fouet. Mixez au mixer plongeant pour disperser les molécules de matières grasse (comme pour une mayonnaise).
Filmez (directement au contact de la crème pour éviter qu’elle croûte) et réserver 2 heures au réfrigérateur.
Note : si la crème est trop acide pour vous, remplacez une partie du jus d’oranges par de l’eau.
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
This is part of a series on technical analysis indicators in F#, based on the multi-language TA-Lib.
Some base studies we’ll use in other studies
namespace Trading.Studies
module internal BaseStudies =
let wmomLookback (period:int) = period
let wmom coeffToday coeffLag period (data:float[]) =
Study.checkPositiveIntMin1 period
let lookbackTotal = wmomLookback period
let startIdx = lookbackTotal
let endIdx = data.Length - 1
Study.lazyCompute startIdx endIdx (fun outLen ->
let f =
match coeffToday, coeffLag with
| 0.0, 0.0 -> fun today -> 0.0
| 0.0, 1.0 -> fun today -> -data.[today - lookbackTotal]
| 0.0, beta -> fun today -> -beta * data.[today - lookbackTotal]
| 1.0, 0.0 -> fun today -> data.[today]
| 1.0, 1.0 -> fun today -> data.[today] - data.[today - lookbackTotal]
| alpha, 0.0 -> fun today -> alpha * data.[today]
| alpha, beta -> fun today -> alpha * data.[today] - beta * data.[today - lookbackTotal]
Array.init outLen (fun i -> f (startIdx + i))
)
let momLookback period = wmomLookback period
let mom period data = wmom 1.0 1.0 period data
let rocLookback (period:int) = period
let roc isBase100 divByZeroDefaultValue period (data:float[]) =
Study.checkPositiveIntMin1 period
let lookbackTotal = wmomLookback period
let startIdx = lookbackTotal
let endIdx = data.Length - 1
Study.lazyCompute startIdx endIdx (fun outLen ->
if isBase100 then
Array.init outLen (fun i ->
let today = startIdx + i
let lagValue = today - period
if data.[lagValue] <> 0.0 then
100.0 * (data.[today] / data.[lagValue] - 1.0)
else
divByZeroDefaultValue
)
else
Array.init outLen (fun i ->
let today = startIdx + i
let lagValue = today - period
if data.[lagValue] <> 0.0 then
data.[today] / data.[lagValue] - 1.0
else
divByZeroDefaultValue
)
)
The bitfield tracks which pieces of the transfer are already downloaded, and which aren’t. We therefore only need a structure which can track two states per value, which is why we use a bitarray library. In fact, we use only a subset of these functions, and rename a few to make them more expressive.
We also need to be able to translate the bitfield from/to the network where the high bit (right-most in network order) in the first byte corresponds to piece index 0.
Read more »