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:
Only one possible chain of layers to the output layer exists.
The settings_pending provided uniquely specify which layers to add.
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.
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
```