Custom types

Not only Numbers, but also custom types can be used as time series values. However, it is worth noting that for the full correct use of custom types as TimeArrays values, it is necessary to define some methods, which will be listed below.

Required methods

To work with the main functions of the package, you need to define the following methods:

Required methodsDescription
Base.isnan(::Type{T})Checks whether an object of type T is considered NaN
TimeArray.ta_nan(::Type{T})Returns an object of a custom type T that is treated as a NaN value.

Optional methods

The following methods are useful to define for a custom type:

Optional methodsDescription
Base.isless(x::T, y::T)See isless.
Base.isequal(x::T, y::T)See isequal.
Base.:(==)(x::T, y::T)See ==.
Base.:(+)(x::T, y::T)Addition between custom types.
Base.:(-)(x::T, y::T)Subtraction between custom types.
Base.:(*)(x::T, y::T)Multiplication between custom types.
Base.:(/)(x::T, y::T)Division between custom types.
Base.zero(::Type{T})Returns an object of a custom type T that is treated as a zero value. For example, to use the sum method
Base.one(::Type{T})Returns an object of a custom type T that is treated as a one value. For example, to use the prod method

Example

Let's look at the necessary method overloads for our custom type that describes a candle:

struct OHLC
    o::Float64
    h::Float64
    l::Float64
    c::Float64
end

Next we will define the necessary methods:

Base.zero(::Type{OHLC}) = OHLC(0.0, 0.0, 0.0, 0.0)
Base.isnan(x::OHLC) = isnan(x.o) && isnan(x.h) && isnan(x.l) && isnan(x.c)
TimeArrays.ta_nan(::Type{OHLC}) = OHLC(NaN, NaN, NaN, NaN)

Base.isless(x::OHLC, y::OHLC) = isless(x.h, y.h)

function Base.isequal(x::OHLC, y::OHLC)
    return all([
        isequal(x.o, y.o),
        isequal(x.h, y.h),
        isequal(x.l, y.l),
        isequal(x.c, y.c),
    ])
end

function Base.:(==)(x::OHLC, y::OHLC)
    return all([
        x.o == y.o,
        x.h == y.h,
        x.l == y.l,
        x.c == y.c,
    ])
end

Base.:+(left::OHLC, right::OHLC) = OHLC(left.o + right.o, left.h + right.h, left.l + right.l, left.c + right.c)
Base.:-(left::OHLC, right::OHLC) = OHLC(left.o - right.o, left.h - right.h, left.l - right.l, left.c - right.c)
Base.:*(left::OHLC, right::OHLC) = OHLC(left.o * right.o, left.h * right.h, left.l * right.l, left.c * right.c)
Base.:*(left::Number, right::OHLC) = OHLC(left * right.o, left * right.h, left * right.l, left * right.c)
Base.:*(left::OHLC, right::Number) = right * left
Base.:/(left::Number, right::OHLC) = OHLC(left / right.o, left / right.h, left / right.l, left / right.c)
Base.:/(left::OHLC, right::Number) = OHLC(left.o / right, left.h / right, left.l / right, left.c / right)

Now we can use OHLC as the time series value and use all the available functions:

julia> t_left = TimeArray([
           TimeTick(DateTime("2024-1-1T1"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-1T2"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-1T3"), OHLC(1, 2, 3, 4)),
       ]);

julia> t_right = TimeArray([
           TimeTick(DateTime("2024-1-1T2"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-1T3"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-1T4"), OHLC(1, 2, 3, 4)),
       ]);

julia> t_left + t_right
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-01T01:00:00, OHLC(NaN, NaN, NaN, NaN))
 TimeTick(2024-01-01T02:00:00, OHLC(2.0, 4.0, 6.0, 8.0))
 TimeTick(2024-01-01T03:00:00, OHLC(2.0, 4.0, 6.0, 8.0))
 TimeTick(2024-01-01T04:00:00, OHLC(2.0, 4.0, 6.0, 8.0))
julia> t_nan = TimeArray([
           TimeTick(DateTime("2024-01-02"), OHLC(1, 1, 1, 1)),
           TimeTick(DateTime("2024-01-03"), OHLC(NaN, NaN, NaN, NaN)),
           TimeTick(DateTime("2024-01-04"), OHLC(NaN, NaN, NaN, NaN)),
           TimeTick(DateTime("2024-01-05"), OHLC(10, 10, 10, 10)),
       ]);

julia> ta_forward_fill(t_nan)
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-02T00:00:00, OHLC(1.0, 1.0, 1.0, 1.0))
 TimeTick(2024-01-03T00:00:00, OHLC(10.0, 10.0, 10.0, 10.0))
 TimeTick(2024-01-04T00:00:00, OHLC(10.0, 10.0, 10.0, 10.0))
 TimeTick(2024-01-05T00:00:00, OHLC(10.0, 10.0, 10.0, 10.0))

julia> ta_backward_fill(t_nan)
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-02T00:00:00, OHLC(1.0, 1.0, 1.0, 1.0))
 TimeTick(2024-01-03T00:00:00, OHLC(1.0, 1.0, 1.0, 1.0))
 TimeTick(2024-01-04T00:00:00, OHLC(1.0, 1.0, 1.0, 1.0))
 TimeTick(2024-01-05T00:00:00, OHLC(10.0, 10.0, 10.0, 10.0))

julia> ta_linear_fill(t_nan)
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-02T00:00:00, OHLC(1.0, 1.0, 1.0, 1.0))
 TimeTick(2024-01-03T00:00:00, OHLC(4.0, 4.0, 4.0, 4.0))
 TimeTick(2024-01-04T00:00:00, OHLC(7.0, 7.0, 7.0, 7.0))
 TimeTick(2024-01-05T00:00:00, OHLC(10.0, 10.0, 10.0, 10.0))
julia> t_ohlc = TimeArray([
           TimeTick(DateTime("2024-1-01"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-02"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-03"), OHLC(1, 2, 3, 4)),
           TimeTick(DateTime("2024-1-04"), OHLC(1, 2, 3, 4)),
       ]);

julia> ta_lag(t_ohlc, 2)
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-01T00:00:00, OHLC(NaN, NaN, NaN, NaN))
 TimeTick(2024-01-02T00:00:00, OHLC(NaN, NaN, NaN, NaN))
 TimeTick(2024-01-03T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))
 TimeTick(2024-01-04T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))

julia> ta_rolling(sum, t_ohlc, 3)
4-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-01T00:00:00, OHLC(NaN, NaN, NaN, NaN))
 TimeTick(2024-01-02T00:00:00, OHLC(NaN, NaN, NaN, NaN))
 TimeTick(2024-01-03T00:00:00, OHLC(3.0, 6.0, 9.0, 12.0))
 TimeTick(2024-01-04T00:00:00, OHLC(3.0, 6.0, 9.0, 12.0))

julia> ta_resample(x -> isempty(x) ? ta_nan(x) : sum(x), t_ohlc, Day(2))
2-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-01T00:00:00, OHLC(2.0, 4.0, 6.0, 8.0))
 TimeTick(2024-01-03T00:00:00, OHLC(2.0, 4.0, 6.0, 8.0))

julia> ta_resample(sum, t_ohlc, Hour(12))
7-element TimeArray{DateTime, OHLC}:
 TimeTick(2024-01-01T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))
 TimeTick(2024-01-01T12:00:00, OHLC(0.0, 0.0, 0.0, 0.0))
 TimeTick(2024-01-02T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))
 TimeTick(2024-01-02T12:00:00, OHLC(0.0, 0.0, 0.0, 0.0))
 TimeTick(2024-01-03T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))
 TimeTick(2024-01-03T12:00:00, OHLC(0.0, 0.0, 0.0, 0.0))
 TimeTick(2024-01-04T00:00:00, OHLC(1.0, 2.0, 3.0, 4.0))

Custom methods

If you are going to use in ta_resample or ta_rolling some custom reducing function my_func(...) that takes a vector of elements of type V1 and returns a value of another type V2, then you will need to define a new method TimeArrays.return_type that will return type V2:

function my_func(x::AbstractVector{V1})::V2
    return V2(...)
end

TimeArrays.return_type(::typeof(my_func), ::Type{V1}) = V2

This is needed to determine the type of the resulting TimeArray.

Example

using Dates
using TimeArrays

struct Trade
    price::Float64
    volume::Float64
end

function w_avg(p::AbstractVector{Trade})
    return sum(x -> x.price * x.volume, p) / sum(x -> x.volume, p)
end

TimeArrays.return_type(::typeof(w_avg), ::Type{Trade}) = Float64
julia> t_array = TimeArray([
          TimeTick(DateTime("2024-01-02"), Trade(1.0, 10)),
          TimeTick(DateTime("2024-01-03"), Trade(2.0, 20)),
          TimeTick(DateTime("2024-01-05"), Trade(3.0, 30)),
          TimeTick(DateTime("2024-01-06"), Trade(4.0, 40)),
          TimeTick(DateTime("2024-01-09"), Trade(5.0, 50)),
       ]);

julia> ta_rolling(w_avg, t_array, Day(3))
5-element TimeArray{DateTime, Float64}:
 TimeTick(2024-01-02T00:00:00, NaN)
 TimeTick(2024-01-03T00:00:00, 1.6666666666666667)
 TimeTick(2024-01-05T00:00:00, 2.6)
 TimeTick(2024-01-06T00:00:00, 3.5714285714285716)
 TimeTick(2024-01-09T00:00:00, 5.0)