Layers

A Layer may be thought of as a more generalized form of a technical indicator. Every Collection may contain one or more Layers. All Assets in a Collection contain the same Layers. Layers are always stacked on top of each other in a chain, where each Layer may use information from the Layers below as well as the raw time series data associated with the Collection.

Layers provide increased flexibility compared to ordinary technical indicators. For example, an MACD or a Bollinger Bands Layer may be created upon any moving average layer (SMA, EMA etc).

Adding Layers

A new layer is added with the add_layer() function.

Example:

collection_sma = add_layer(collection, LayerSMA; :win_sizes => [12, 26]);

collection_sma.settings_pending[LayerMACD][:signal_period] = 9

collection_macd = add_layer(collection_sma, LayerMACD);

Here an SMA Layer is added to the Collection collection creating a new Collection collection_sma. The layer contains two moving averages with window sizes 12 and 26.

Thesignal_period parameter is set for the pending settings of the collection and n MACD Layer is then added to collection_sma creating collection_macd.

The parameters for the new layer is determined in the following way, ordered by priority:

1, The settings provided when creating a new layer.

2, The pending settings in the collection. (see Collection Settings).

3, The default parameters for the layer type. For example the default parameters for SMA layers is listed by default_parameters(LayerSMA).

Adding Several Layers At Once

A Layer further down the chain may be added directly if at least one of the following criteria is true:

  1. Only one possible chain of layers to the output layer exists.

  2. The settings_pending provided uniquely specify which layers to add.

  3. The function defaultlayerover(LayerType) exists for all relevant layers.

Further, in the case that all layers up to and including the requested output layer already exist in the collection, the layers that has seen changed settings will be redone.

Example:

TFire> collection_macd = add_layer(collection, LayerMACD);
Error. Settings not unique. Can not determine which layers to produce
No layers added

TFire> collection.layer_settings_pending # Empty

TFire> collection.layer_settings_pending[LayerSMA][:win_sizes] = [12,26]

TFire> collection.layer_settings_pending
LayerSMA
:win_sizes => [12, 26]
   ---

TFire> collection_macd = add_layer(collection, LayerMACD)
-| Collection |- (Continuous) -> LayerSMA -> LayerMACD
Tickers: 399, MMM to ZION
Samples: 798446

Above, a Layer to pick above LayerMACD is first not uniquely specified, and thus an error is thrown. Settings for LayerSMA are then added to the Collection and LayerMACD may be added. In reality though, the default_layer_over() function would determine that LayerEMA is the only possible layer above LayerMACD.

Listing Available Layers

Available layertypes may be listed with the list_available_layertypes() function.

Repainting vs NonRepainting

Technical indicators may be repainting (also called noncausal) or nonrepainting (causal) and naturally the same goes for Layers in TFire. A repainting indicator may update past values depending on future information while a nonrepainting one may not. TFire is designed to be able to handle repainting indicators (Layers) without introducing look ahead-bias.

Note

There can never be a nonrepainting layer below a repainting one. However there CAN be a repainting layer below a nonrepainting one.

To check if a layer is repainting or nonrepainting, use the LayerRepaintingTrait() function.

Creating User Defined Layers

TFire comes with a set of predefined layers, but it is also possible to create new ones. Below follows an example of how to add a new user defined layer for calculating simple moving averages (SMA).

Define The Layer Struct

mutable struct LayerSMA <: LayersMovingAverage
    ma_values::Dict{Int64, Vector{Float64}} 
end

First, define a mutable struct for the layer. The type of the added layer should be a subtype of some abstryact subtype of AssetLayer - which in this this example menas a subtype of LayersMovingAverage which in its turn is a subtype of AssetLayer.

Further, the struct should contain all necessary fields for the layer to store its state. In the case of a non-repainting layer (like SMA) this can often be in the type of Vectors of values, while in the case of a repainting layer it often needs to be Vectors of Vectors of values. Here, the layer supports many window sizes simultaneously and therefore it needs to be a Dict of Vectors.

Define The Repainting Trait

function LayerRepaintingTrait(::Type{<:LayerSMA})
    return NonRepainting()
end

Next, define a function to specify the repainting trait. SMA is a non-repainting indicator, so return NonRepainting():

Define Default Parameters

Then define a function to return the default parameters for the layer:

function default_parameters(::Type{LayerSMA})
    return Parameters(LayerSMA, Dict{Symbol, Any}(
        :win_sizes => [45, 60], 
        :price => DefaultPrice()))
end

Implement The Layer Logic

The main logic goes in _produce_layer. Get the parameters, calculate the SMA values, and return the layer struct. The variable to_keep representing which values to keep is returned along the layer struct.

Various types of DataViews are typically used as input. For non-repainting layers it is common to use NonRepainting DataViews.

function _produce_layer(::Type{LayerSMA}, asset::Asset, settings::Settings,settings_used::Settings,
                        collection_settings::SettingsCollection; common_input=nothing)
    # input scalars
    win_sizes = get_parameter(settings, LayerSMA, :win_sizes)
    data_field = get_parameter(settings, LayerSMA, :data_field)
    if data_field in DEFAULT_PRICE_FIELDS
       price_field = collection_settings.price_type.MAPPING[DEFAULT_PRICE_MAPPING[data_field]]
    end
    max_winsize = maximum(win_sizes)

    # Maximum we can look back
    available_lookback = DVCheckBackAvailable(asset, price_field)
    go_back = min(max_winsize - 1, available_lookback)

    dvts = nothing
    try
        dvts = DVNonRepaintingTimeSeriesBack(asset, price_field, go_back)
    catch e
        println("An error occurred: ", e)
        to_keep = falses(length(asset))
        return LayerSMA(Dict{Int64, Vector}()), to_keep
    end

    sma_values = Dict{Int64, Vector{Float64}}()

    for win_size in win_sizes
        sma = Vector{Float64}(undef, length(dvts) - max_winsize + 1)
        window = CircularDeque{Float64}(win_size)
        
        sum_window = 0.0
        # Fill initial window
        for i in 1:win_size
            push!(window, dvts[i])
            sum_window += dvts[i]
        end
        
        # Store first value if we're past max_winsize
        if win_size >= max_winsize
            sma[1] = sum_window / win_size
        end
        
        # Calculate remaining SMAs
        for i in (win_size+1):length(dvts)
            sum_window -= popfirst!(window)
            push!(window, dvts[i])
            sum_window += dvts[i]
            if i >= max_winsize
                sma[i - max_winsize + 1] = sum_window / win_size
            end
        end
        
        sma_values[win_size] = sma
    end

    # to_keep based on maximum window size
    to_keep = trues(length(asset))
    to_keep[1:(max_winsize - 1 - go_back)] .= false

    return LayerSMA(sma_values), to_keep
end

Adding Layer Specific Plotting

function plot_asset_graph(asset::Asset{T,N}, ind::Integer, col::Collection) where T where N <: LayerSMA
    x = dates_index_for_plot(asset, col.collection_settings.resolution, ind)
    win_sizes = col.layer_settings_active[LayerSMA][:win_sizes]
    plots=Plot()

    for win_size in win_sizes
        y = DVLayerDictKey(asset, :ma_values, win_size)[ind]
        p = plot_line(x,y; name="SMA "*string(win_size))
        plots(p)
    end
    return plots, nothing
end

function plot_whole_asset_graph(asset::Asset{T,N}, col::Collection) where T where N <: LayerSMA
    x = dates_index_for_plot(asset, col.collection_settings.resolution)
    win_sizes = col.layer_settings_active[LayerSMA][:win_sizes]
    plots=Plot()

    for win_size in win_sizes
        y = collect(DVNonRepaintingLayerDictKey(asset, :ma_values, win_size))
        p = plot_line(x,y; name="SMA "*string(win_size))
        plots(p)
    end
    return plots, nothing
end

Functions

Layers - Functions

```