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

Comments are closed.