Files

83 lines
3.1 KiB
Plaintext

// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Derivative Oscillator (DOSC)", "DOSC", overlay=false, precision=4)
//@function Calculates the Derivative Oscillator: double-smoothed RSI minus its SMA signal line
//@param source Series to calculate from
//@param rsiPeriod RSI lookback period
//@param ema1Period First EMA smoothing period applied to RSI
//@param ema2Period Second EMA smoothing period (double smoothing)
//@param sigPeriod SMA signal line period applied to double-smoothed RSI
//@returns DOSC value (histogram: double-smoothed RSI minus signal)
//@optimized O(1) per bar after warmup for all EMA/SMA stages
dosc(series float source, simple int rsiPeriod, simple int ema1Period, simple int ema2Period, simple int sigPeriod) =>
if rsiPeriod <= 0 or ema1Period <= 0 or ema2Period <= 0 or sigPeriod <= 0
runtime.error("All periods must be greater than 0")
// --- Stage 1: RSI via Wilder's smoothing ---
float change_up = math.max(source - nz(source[1]), 0.0)
float change_down = math.max(nz(source[1]) - source, 0.0)
var float avgGain = 0.0
var float avgLoss = 0.0
float rsiAlpha = 1.0 / rsiPeriod
if bar_index < rsiPeriod
avgGain := change_up
avgLoss := change_down
else
avgGain := nz(avgGain[1]) * (1.0 - rsiAlpha) + change_up * rsiAlpha
avgLoss := nz(avgLoss[1]) * (1.0 - rsiAlpha) + change_down * rsiAlpha
float rsiVal = avgLoss == 0.0 ? 100.0 : 100.0 - (100.0 / (1.0 + avgGain / avgLoss))
// --- Stage 2: EMA1 of RSI ---
var float ema1 = na
float alpha1 = 2.0 / (ema1Period + 1.0)
ema1 := na(ema1[1]) ? rsiVal : nz(ema1[1]) * (1.0 - alpha1) + rsiVal * alpha1
// --- Stage 3: EMA2 of EMA1 (double smoothing) ---
var float ema2 = na
float alpha2 = 2.0 / (ema2Period + 1.0)
ema2 := na(ema2[1]) ? ema1 : nz(ema2[1]) * (1.0 - alpha2) + ema1 * alpha2
// --- Stage 4: SMA signal line of EMA2 ---
var array<float> sigBuf = array.new_float(sigPeriod, na)
var int sigHead = 0
var int sigCount = 0
var float sigSum = 0.0
float oldest = array.get(sigBuf, sigHead)
if not na(oldest)
sigSum -= oldest
sigSum += ema2
else
sigCount += 1
sigSum += ema2
array.set(sigBuf, sigHead, ema2)
sigHead := (sigHead + 1) % sigPeriod
float signal = sigCount > 0 ? sigSum / sigCount : 0.0
// DOSC = double-smoothed RSI minus signal
float result = ema2 - signal
result
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_rsiPeriod = input.int(14, "RSI Period", minval=1, maxval=500)
i_ema1 = input.int(5, "EMA1 Period", minval=1, maxval=500, tooltip="First EMA smoothing of RSI")
i_ema2 = input.int(3, "EMA2 Period", minval=1, maxval=500, tooltip="Second EMA smoothing (double smooth)")
i_sigPeriod = input.int(9, "Signal Period", minval=1, maxval=500, tooltip="SMA signal line period")
// Calculation
dosc_value = dosc(i_source, i_rsiPeriod, i_ema1, i_ema2, i_sigPeriod)
// Plot
plot(dosc_value, "DOSC", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)