""" Indicators.jl — Vectorized technical indicator library. Standalone module. No includes. No external deps beyond Statistics. """ module Indicators using Statistics export sma, ema, wma, tema, dema, rsi, macd, stoch, cci, williams_r, atr, bbands, keltner, donchian, adx, vwap, obv, cmf, zscore, std_dev, momentum, roc, highest, lowest, crossover, crossunder # ── Trend ───────────────────────────────────────────── function sma(x::Vector{Float64}, n::Int)::Vector{Float64} len = length(x); out = fill(NaN, len); s = 0.0 for i in 1:len s += x[i] if i >= n i > n && (s -= x[i-n]) out[i] = s / n end end return out end function ema(x::Vector{Float64}, n::Int)::Vector{Float64} len = length(x); out = fill(NaN, len) k = 2.0 / (n + 1.0) # seed: SMA of first n non-NaN values s = 0.0; cnt = 0; seed_i = 0 for i in 1:len isnan(x[i]) && continue s += x[i]; cnt += 1 if cnt == n seed_i = i; out[i] = s / n val = out[i] for j in (i+1):len isnan(x[j]) && continue val = x[j] * k + val * (1.0 - k) out[j] = val end break end end return out end function wma(x::Vector{Float64}, n::Int)::Vector{Float64} len = length(x); out = fill(NaN, len) ws = n * (n+1) / 2.0 for i in n:len s = 0.0 for j in 1:n; s += x[i-n+j] * j; end out[i] = s / ws end return out end tema(x::Vector{Float64}, n::Int) = let e1=ema(x,n),e2=ema(e1,n),e3=ema(e2,n); 3.0.*e1 .- 3.0.*e2 .+ e3 end dema(x::Vector{Float64}, n::Int) = let e1=ema(x,n),e2=ema(e1,n); 2.0.*e1 .- e2 end # ── Oscillators ─────────────────────────────────────── function rsi(close::Vector{Float64}, n::Int=14)::Vector{Float64} len = length(close); out = fill(NaN, len) ag = 0.0; al = 0.0 for i in 2:(n+1) i > len && break d = close[i] - close[i-1] d > 0 ? (ag += d) : (al += abs(d)) end ag /= n; al /= n n+1 <= len && (out[n+1] = 100.0 - 100.0/(1.0 + (al==0 ? 1e10 : ag/al))) for i in (n+2):len d = close[i] - close[i-1] g = d > 0 ? d : 0.0; l = d < 0 ? abs(d) : 0.0 ag = (ag*(n-1)+g)/n; al = (al*(n-1)+l)/n out[i] = 100.0 - 100.0/(1.0 + (al==0 ? 1e10 : ag/al)) end return out end function macd(close::Vector{Float64}; fast::Int=12, slow::Int=26, sig::Int=9) ml = ema(close,fast) .- ema(close,slow) sl = ema(ml, sig) return ml, sl, ml .- sl end function stoch(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}; k::Int=14, d::Int=3) len = length(close); K = fill(NaN, len) for i in k:len hh = maximum(high[i-k+1:i]); ll = minimum(low[i-k+1:i]) K[i] = hh==ll ? 50.0 : 100.0*(close[i]-ll)/(hh-ll) end return K, sma(K, d) end function cci(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, n::Int=20)::Vector{Float64} len = length(close); tp = (high.+low.+close)./3.0; out = fill(NaN, len) for i in n:len w = tp[i-n+1:i]; m = mean(w); md = mean(abs.(w.-m)) out[i] = md==0 ? 0.0 : (tp[i]-m)/(0.015*md) end return out end function williams_r(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, n::Int=14)::Vector{Float64} len = length(close); out = fill(NaN, len) for i in n:len hh = maximum(high[i-n+1:i]); ll = minimum(low[i-n+1:i]) out[i] = hh==ll ? -50.0 : -100.0*(hh-close[i])/(hh-ll) end return out end momentum(x::Vector{Float64}, n::Int=10) = let len=length(x),out=fill(NaN,len); for i in (n+1):len; out[i]=x[i]-x[i-n]; end; out end roc(x::Vector{Float64}, n::Int=10) = let len=length(x),out=fill(NaN,len); for i in (n+1):len; out[i]=x[i-n]==0 ? 0.0 : 100.0*(x[i]-x[i-n])/x[i-n]; end; out end # ── Volatility ──────────────────────────────────────── function _tr(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64})::Vector{Float64} len = length(close); tr = fill(NaN, len) tr[1] = high[1]-low[1] for i in 2:len; tr[i] = max(high[i]-low[i], abs(high[i]-close[i-1]), abs(low[i]-close[i-1])); end return tr end atr(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, n::Int=14) = ema(_tr(high,low,close), n) function bbands(close::Vector{Float64}, n::Int=20, k::Float64=2.0) len = length(close); mid = sma(close,n); sd = fill(NaN, len) for i in n:len; sd[i] = std(close[i-n+1:i]; corrected=false); end return mid.+k.*sd, mid, mid.-k.*sd end function keltner(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, n::Int=20, k::Float64=2.0) mid = ema(close,n); a = atr(high,low,close,n) return mid.+k.*a, mid, mid.-k.*a end function donchian(high::Vector{Float64}, low::Vector{Float64}, n::Int=20) len = length(high); u = fill(NaN,len); l = fill(NaN,len) for i in n:len; u[i]=maximum(high[i-n+1:i]); l[i]=minimum(low[i-n+1:i]); end return u, (u.+l)./2.0, l end function std_dev(x::Vector{Float64}, n::Int=20)::Vector{Float64} len = length(x); out = fill(NaN, len) for i in n:len; out[i] = std(x[i-n+1:i]; corrected=false); end return out end function zscore(x::Vector{Float64}, n::Int=20)::Vector{Float64} mu = sma(x,n); sd = std_dev(x,n); out = fill(NaN, length(x)) for i in eachindex(x) !isnan(mu[i]) && !isnan(sd[i]) && sd[i]>0 && (out[i]=(x[i]-mu[i])/sd[i]) end return out end # ── Trend strength ──────────────────────────────────── function adx(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, n::Int=14) tr = _tr(high,low,close) up = diff(vcat(high[1],high)); dn = diff(vcat(low[1],low)) pdm = map((u,d)->u>d&&u>0 ? u : 0.0, up, dn) ndm = map((u,d)->d>u&&d>0 ? d : 0.0, up, dn) sm=ema(tr,n); pdi=100.0.*ema(pdm,n)./(sm.+1e-10); ndi=100.0.*ema(ndm,n)./(sm.+1e-10) dx = 100.0.*abs.(pdi.-ndi)./(pdi.+ndi.+1e-10) return ema(dx,n), pdi, ndi end # ── Volume ──────────────────────────────────────────── function vwap(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, volume::Vector{Float64})::Vector{Float64} tp = (high.+low.+close)./3.0 return cumsum(tp.*volume)./(cumsum(volume).+1e-10) end function obv(close::Vector{Float64}, volume::Vector{Float64})::Vector{Float64} len = length(close); out = zeros(Float64, len); out[1] = volume[1] for i in 2:len d = close[i]-close[i-1] out[i] = out[i-1] + (d>0 ? volume[i] : d<0 ? -volume[i] : 0.0) end return out end function cmf(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, volume::Vector{Float64}, n::Int=20)::Vector{Float64} len = length(close); out = fill(NaN, len) hl = high.-low mfv = map((c,l,h,hl)->hl==0 ? 0.0 : (2c-l-h)/hl, close,low,high,hl).*volume for i in n:len sv = sum(volume[i-n+1:i]) out[i] = sv==0 ? 0.0 : sum(mfv[i-n+1:i])/sv end return out end # ── Utilities ───────────────────────────────────────── highest(x::Vector{Float64}, n::Int) = let len=length(x),out=fill(NaN,len); for i in n:len; out[i]=maximum(x[i-n+1:i]); end; out end lowest(x::Vector{Float64}, n::Int) = let len=length(x),out=fill(NaN,len); for i in n:len; out[i]=minimum(x[i-n+1:i]); end; out end function crossover(a::Vector{Float64}, b::Vector{Float64})::Vector{Bool} len=length(a); out=fill(false,len) for i in 2:len; out[i] = a[i]>b[i] && a[i-1]<=b[i-1]; end return out end function crossunder(a::Vector{Float64}, b::Vector{Float64})::Vector{Bool} len=length(a); out=fill(false,len) for i in 2:len; out[i] = a[i]=b[i-1]; end return out end end # module Indicators