Packages

Trading technical indicators library for Gleam. SMA, EMA, MACD, RSI, Stochastic, Bollinger Bands, ATR, VWAP, OBV.

Current section

Files

Jump to
ursatoro src ursatoro@trend.erl
Raw

src/ursatoro@trend.erl

-module(ursatoro@trend).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ursatoro/trend.gleam").
-export([sma/2, adx/2, donchian_channel/2, ema/2, macd/4]).
-export_type([macd_result/0, adx_result/0, donchian_result/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type macd_result() :: {macd_result, float(), float(), float()}.
-type adx_result() :: {adx_result, float(), float(), float()}.
-type donchian_result() :: {donchian_result, float(), float(), float()}.
-file("src/ursatoro/trend.gleam", 35).
-spec sma(list(ursatoro@candle:candle()), integer()) -> {ok, list(float())} |
{error, ursatoro@util:indicator_error()}.
sma(Candles, Period) ->
Closes = gleam@list:map(Candles, fun ursatoro@candle:close/1),
gleam@result:'try'(
ursatoro@util:sma_values(Closes, Period),
fun(Values) -> {ok, ursatoro@util:bds_to_floats(Values)} end
).
-file("src/ursatoro/trend.gleam", 82).
-spec ema_multiplier(integer()) -> bigdecimal:big_decimal().
ema_multiplier(Period) ->
Two = ursatoro@util:int_to_bd(2),
Period_plus_one = ursatoro@util:int_to_bd(Period + 1),
bigdecimal:divide(Two, Period_plus_one, half_up).
-file("src/ursatoro/trend.gleam", 256).
?DOC(" Compute directional movement and true range for consecutive candle pairs.\n").
-spec compute_dm_tr(list(ursatoro@candle:candle())) -> list({bigdecimal:big_decimal(),
bigdecimal:big_decimal(),
bigdecimal:big_decimal()}).
compute_dm_tr(Candles) ->
Zero = bigdecimal:zero(),
case Candles of
[] ->
[];
[_] ->
[];
[First | Rest] ->
{Results, _} = gleam@list:fold(
Rest,
{[], First},
fun(Acc, Current) ->
{Rs, Prev} = Acc,
High_diff = bigdecimal:subtract(
ursatoro@candle:high(Current),
ursatoro@candle:high(Prev)
),
Low_diff = bigdecimal:subtract(
ursatoro@candle:low(Prev),
ursatoro@candle:low(Current)
),
{Plus_dm, Minus_dm} = case bigdecimal:compare(
High_diff,
Low_diff
) of
gt ->
case bigdecimal:compare(High_diff, Zero) of
gt ->
{High_diff, Zero};
_ ->
{Zero, Zero}
end;
lt ->
case bigdecimal:compare(Low_diff, Zero) of
gt ->
{Zero, Low_diff};
_ ->
{Zero, Zero}
end;
eq ->
{Zero, Zero}
end,
Tr = ursatoro@util:true_range(Prev, Current),
{[{Plus_dm, Minus_dm, Tr} | Rs], Current}
end
),
lists:reverse(Results)
end.
-file("src/ursatoro/trend.gleam", 295).
?DOC(
" Compute DX and +DI/-DI from smoothed values.\n"
" Returns #(DX_bd, +DI_bd, -DI_bd)\n"
).
-spec compute_dx(
bigdecimal:big_decimal(),
bigdecimal:big_decimal(),
bigdecimal:big_decimal(),
bigdecimal:big_decimal(),
bigdecimal:big_decimal()
) -> {bigdecimal:big_decimal(),
bigdecimal:big_decimal(),
bigdecimal:big_decimal()}.
compute_dx(Smooth_pdm, Smooth_mdm, Smooth_tr, Hundred, Zero) ->
case bigdecimal:compare(Smooth_tr, Zero) of
gt ->
Plus_di = bigdecimal:divide(
bigdecimal:multiply(Smooth_pdm, Hundred),
Smooth_tr,
half_up
),
Minus_di = bigdecimal:divide(
bigdecimal:multiply(Smooth_mdm, Hundred),
Smooth_tr,
half_up
),
Di_sum = bigdecimal:add(Plus_di, Minus_di),
Di_diff = ursatoro@util:bd_abs(
bigdecimal:subtract(Plus_di, Minus_di)
),
Dx = case bigdecimal:compare(Di_sum, Zero) of
gt ->
bigdecimal:divide(
bigdecimal:multiply(Di_diff, Hundred),
Di_sum,
half_up
);
_ ->
Zero
end,
{Dx, Plus_di, Minus_di};
_ ->
{Zero, Zero, Zero}
end.
-file("src/ursatoro/trend.gleam", 139).
-spec adx(list(ursatoro@candle:candle()), integer()) -> {ok, list(adx_result())} |
{error, ursatoro@util:indicator_error()}.
adx(Candles, Period) ->
gleam@result:'try'(
ursatoro@util:validate_period(Period),
fun(_) ->
gleam@result:'try'(
ursatoro@util:validate_length(Candles, (Period * 2) + 1),
fun(_) ->
Period_bd = ursatoro@util:int_to_bd(Period),
Period_minus_one = ursatoro@util:int_to_bd(Period - 1),
Hundred = ursatoro@util:hundred(),
Zero = bigdecimal:zero(),
Dm_tr_list = compute_dm_tr(Candles),
Initial = gleam@list:take(Dm_tr_list, Period),
Remaining = gleam@list:drop(Dm_tr_list, Period),
First_smooth_plus_dm = gleam@list:fold(
Initial,
Zero,
fun(Acc, X) ->
bigdecimal:add(Acc, erlang:element(1, X))
end
),
First_smooth_minus_dm = gleam@list:fold(
Initial,
Zero,
fun(Acc@1, X@1) ->
bigdecimal:add(Acc@1, erlang:element(2, X@1))
end
),
First_smooth_tr = gleam@list:fold(
Initial,
Zero,
fun(Acc@2, X@2) ->
bigdecimal:add(Acc@2, erlang:element(3, X@2))
end
),
{Dx_list, _, _, _} = gleam@list:fold(
Remaining,
{[compute_dx(
First_smooth_plus_dm,
First_smooth_minus_dm,
First_smooth_tr,
Hundred,
Zero
)],
First_smooth_plus_dm,
First_smooth_minus_dm,
First_smooth_tr},
fun(Acc@3, Entry) ->
{Dxs, Prev_pdm, Prev_mdm, Prev_tr} = Acc@3,
Smooth_pdm = bigdecimal:divide(
bigdecimal:add(
bigdecimal:multiply(
Prev_pdm,
Period_minus_one
),
erlang:element(1, Entry)
),
Period_bd,
half_up
),
Smooth_mdm = bigdecimal:divide(
bigdecimal:add(
bigdecimal:multiply(
Prev_mdm,
Period_minus_one
),
erlang:element(2, Entry)
),
Period_bd,
half_up
),
Smooth_tr = bigdecimal:divide(
bigdecimal:add(
bigdecimal:multiply(
Prev_tr,
Period_minus_one
),
erlang:element(3, Entry)
),
Period_bd,
half_up
),
Dx = compute_dx(
Smooth_pdm,
Smooth_mdm,
Smooth_tr,
Hundred,
Zero
),
{[Dx | Dxs], Smooth_pdm, Smooth_mdm, Smooth_tr}
end
),
Ordered_dx = lists:reverse(Dx_list),
Dx_values = gleam@list:map(
Ordered_dx,
fun(D) -> erlang:element(1, D) end
),
Initial_dx = gleam@list:take(Dx_values, Period),
Remaining_dx = gleam@list:drop(Dx_values, Period),
case erlang:length(Initial_dx) >= Period of
false ->
{ok, []};
true ->
First_adx = case ursatoro@util:bd_mean(Initial_dx) of
{ok, M} ->
M;
{error, _} ->
Zero
end,
Initial_result = gleam@list:drop(
Ordered_dx,
Period - 1
),
First_dx_entry = case gleam@list:first(
gleam@list:drop(Ordered_dx, Period - 1)
) of
{ok, E} ->
E;
{error, _} ->
{Zero, Zero, Zero}
end,
{Adx_results, _} = gleam@list:fold(
Remaining_dx,
{[{adx_result,
ursatoro@util:bd_to_float(First_adx),
ursatoro@util:bd_to_float(
erlang:element(
2,
First_dx_entry
)
),
ursatoro@util:bd_to_float(
erlang:element(
3,
First_dx_entry
)
)}],
First_adx},
fun(Acc@4, Dx_bd) ->
{Results, Prev_adx} = Acc@4,
New_adx = bigdecimal:divide(
bigdecimal:add(
bigdecimal:multiply(
Prev_adx,
Period_minus_one
),
Dx_bd
),
Period_bd,
half_up
),
Idx = erlang:length(Results),
Entry@1 = case gleam@list:drop(
Initial_result,
Idx
) of
[E@1 | _] ->
E@1;
[] ->
{Zero, Zero, Zero}
end,
R = {adx_result,
ursatoro@util:bd_to_float(New_adx),
ursatoro@util:bd_to_float(
erlang:element(2, Entry@1)
),
ursatoro@util:bd_to_float(
erlang:element(3, Entry@1)
)},
{[R | Results], New_adx}
end
),
{ok, lists:reverse(Adx_results)}
end
end
)
end
).
-file("src/ursatoro/trend.gleam", 336).
-spec donchian_channel(list(ursatoro@candle:candle()), integer()) -> {ok,
list(donchian_result())} |
{error, ursatoro@util:indicator_error()}.
donchian_channel(Candles, Period) ->
gleam@result:'try'(
ursatoro@util:validate_period(Period),
fun(_) ->
gleam@result:'try'(
ursatoro@util:validate_length(Candles, Period),
fun(_) ->
Two = ursatoro@util:int_to_bd(2),
Len = erlang:length(Candles),
Results = gleam@int:range(
0,
Len - Period,
[],
fun(Acc, Start) ->
Window = gleam@list:take(
gleam@list:drop(Candles, Start),
Period
),
Highs = gleam@list:map(
Window,
fun ursatoro@candle:high/1
),
Lows = gleam@list:map(
Window,
fun ursatoro@candle:low/1
),
Upper = gleam@list:fold(
Highs,
bigdecimal:zero(),
fun ursatoro@util:bd_max/2
),
Lower = gleam@list:fold(Lows, case Lows of
[First | _] ->
First;
[] ->
bigdecimal:zero()
end, fun ursatoro@util:bd_min/2),
Mid = bigdecimal:divide(
bigdecimal:add(Upper, Lower),
Two,
half_up
),
[{donchian_result,
ursatoro@util:bd_to_float(Upper),
ursatoro@util:bd_to_float(Lower),
ursatoro@util:bd_to_float(Mid)} |
Acc]
end
),
{ok, lists:reverse(Results)}
end
)
end
).
-file("src/ursatoro/trend.gleam", 372).
-spec list_split(list(HXR), integer()) -> {list(HXR), list(HXR)}.
list_split(Items, At) ->
{gleam@list:take(Items, At), gleam@list:drop(Items, At)}.
-file("src/ursatoro/trend.gleam", 55).
-spec ema_from_values(list(bigdecimal:big_decimal()), integer()) -> {ok,
list(bigdecimal:big_decimal())} |
{error, ursatoro@util:indicator_error()}.
ema_from_values(Values, Period) ->
gleam@result:'try'(
ursatoro@util:validate_period(Period),
fun(_) ->
gleam@result:'try'(
ursatoro@util:validate_length(Values, Period),
fun(_) ->
K = ema_multiplier(Period),
One_minus_k = bigdecimal:subtract(bigdecimal:one(), K),
{Initial, Rest} = list_split(Values, Period),
gleam@result:'try'(
ursatoro@util:bd_mean(Initial),
fun(First_ema) ->
{Results@1, _} = gleam@list:fold(
Rest,
{[First_ema], First_ema},
fun(Acc, Value) ->
{Results, Prev_ema} = Acc,
New_ema = bigdecimal:add(
bigdecimal:multiply(Value, K),
bigdecimal:multiply(
Prev_ema,
One_minus_k
)
),
{[New_ema | Results], New_ema}
end
),
{ok, lists:reverse(Results@1)}
end
)
end
)
end
).
-file("src/ursatoro/trend.gleam", 46).
-spec ema(list(ursatoro@candle:candle()), integer()) -> {ok, list(float())} |
{error, ursatoro@util:indicator_error()}.
ema(Candles, Period) ->
Closes = gleam@list:map(Candles, fun ursatoro@candle:close/1),
gleam@result:'try'(
ema_from_values(Closes, Period),
fun(Values) -> {ok, ursatoro@util:bds_to_floats(Values)} end
).
-file("src/ursatoro/trend.gleam", 90).
-spec macd(list(ursatoro@candle:candle()), integer(), integer(), integer()) -> {ok,
list(macd_result())} |
{error, ursatoro@util:indicator_error()}.
macd(Candles, Fast, Slow, Signal) ->
gleam@result:'try'(
ursatoro@util:validate_period(Fast),
fun(_) ->
gleam@result:'try'(
ursatoro@util:validate_period(Slow),
fun(_) ->
gleam@result:'try'(
ursatoro@util:validate_period(Signal),
fun(_) ->
Closes = gleam@list:map(
Candles,
fun ursatoro@candle:close/1
),
gleam@result:'try'(
ursatoro@util:validate_length(
Closes,
(Slow + Signal) - 1
),
fun(_) ->
gleam@result:'try'(
ema_from_values(Closes, Fast),
fun(Fast_ema) ->
gleam@result:'try'(
ema_from_values(Closes, Slow),
fun(Slow_ema) ->
Fast_len = erlang:length(
Fast_ema
),
Slow_len = erlang:length(
Slow_ema
),
Offset = Fast_len - Slow_len,
Aligned_fast = gleam@list:drop(
Fast_ema,
Offset
),
Macd_line = gleam@list:map2(
Aligned_fast,
Slow_ema,
fun bigdecimal:subtract/2
),
gleam@result:'try'(
ema_from_values(
Macd_line,
Signal
),
fun(Signal_ema) ->
Macd_len = erlang:length(
Macd_line
),
Signal_len = erlang:length(
Signal_ema
),
Macd_offset = Macd_len
- Signal_len,
Aligned_macd = gleam@list:drop(
Macd_line,
Macd_offset
),
Results = gleam@list:map2(
Aligned_macd,
Signal_ema,
fun(M, S) ->
Histogram = bigdecimal:subtract(
M,
S
),
{macd_result,
ursatoro@util:bd_to_float(
M
),
ursatoro@util:bd_to_float(
S
),
ursatoro@util:bd_to_float(
Histogram
)}
end
),
{ok, Results}
end
)
end
)
end
)
end
)
end
)
end
)
end
).