Seeking API Design Advice for Setting Defaults

I have a constructor for a mutable Chart struct that is used like this.

using OnlineTechnicalIndicators
using TechnicalIndicatorCharts

golden_cross_chart = Chart(
    "AAPL", Week(1);
    indicators = [
        SMA{Float64}(;period=50),         # Setup indicators
        SMA{Float64}(;period=200)
    ],
    visuals = [
        Dict(
            :label_name => "SMA 50",      # Describe how to draw indicators
            :line_color => "#E072A4",
            :line_width => 2
        ),
        Dict(
            :label_name => "SMA 200",
            :line_color => "#3D3B8E",
            :line_width => 5
        )
    ]
)

Thereā€™s a 1-to-1 correspondence between the values in indicators and the values in visuals. This is for my unreleased library TechnicalIndicatorCharts.jl.

What if I donā€™t care about colors?

If I donā€™t care how it looks, and I just want to use the defaults, this is what I currently have to do.

golden_cross_chart = Chart(
    "AAPL", Week(1);
    indicators = [
        SMA{Float64}(;period=50),         # Setup indicators
        SMA{Float64}(;period=200)
    ],
    visuals = [
        Dict(),                           # Not the prettiest, but tolerable.
        Dict()
    ]
)

It can get worse.

That might not be so bad, but what if I added Bollinger Bands which is drawn with 3 lines instead of just one like SMA.

golden_cross_chart = Chart(
    "AAPL", Week(1);
    indicators = [
        SMA{Float64}(;period=50),         # Setup indicators
        SMA{Float64}(;period=200),
        BB{Float64}(;period=20)
    ],
    visuals = [
        Dict(),                           # Not the prettiest
        Dict(),
        Dict(
            :upper => Dict(),             # Now it's getting ugly.
            :central => Dict(),
            :lower => Dict()              # All this typing just for defaults?!
        )
    ]
)

How should I do this?

I want to be able to declare defaults in the visuals vector with way less typing. How could this be accomplished?

1 Like

just a side note: in the post or in the package readme, specify you are dealing with financial technical indicators, thatā€™s not super obvious from people not in the domain :wink:

2 Likes

Iā€™m kinda curious what the non-financial kind of technical indicators might be. (The financial kind are the only ones I know.) To provide a hint to the reader, I added the finance tag to this thread.

In other news, I got a little bit of advice from someone over on Zulip. I was kinda losing hope on getting a response on this, but he had a nice idea that Iā€™m working on implementing today.

Perhaps Im wrong, but I think you could just use (maybe typed) global variables in the module where you define chart and have keywords in the inner constructor.

module ChartVisuals
    
    bollinger_default = Dict(1 => "red")
    
    struct Chart{S,T}
        symbol::S
        visuals::T
        function Chart(s::S; visuals::T = bollinger_default) where {S,T}
            new{S,T}(s,visuals)
        end
    end

end

You can even update the global values in the module just like any other variable ChartVisuals.bollinger_default = Dict_of_new_colors, have a different global, ChartVisuals.sma_default.

Sometimes the ā€œdont use globalsā€ thinking gets a little too dogmatic. I think they have a nice place outside ā€œhot codeā€ like for package related preferences. Not sure if this is exactly what you want.

1 Like

I have to be honest. My Julia skill level is currently at the point where I donā€™t really know how to use where. Maybe Iā€™ll learn today to fully understand your reply, but my understanding is not there yet.

Also, I need Chart to potentially contain multiple indicators so that they can be drawn together. It seems like you restructured Chart to only have one. Thatā€™s not enough for me.

Regarding globals, Iā€™m not opposed to them if theyā€™re essentially constants.

I know it looks freaky, but this pattern brings the types of the variables into scope so that S and T can be used at compile time to build the new type. Basically, if I need to use type parameters as variables, for constructing new objects, I use ā€œwhereā€ even if Im not enforcing fancy dispatch patterns (which is how where is usually discussed in the docs).

I didnā€™t want to learn it either, but I found ā€˜whereā€™ to be super important, and found that it was neccesary in many cases to construct fully specified, fast, concrete types. You donā€™t need much more than the pattern you see above.

EDIT: yes I dropped the indicator field, I was just trying to concisely express the pattern sorry!

1 Like