mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-07 13:37:44 +00:00
86fe32a682
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat> Co-authored-by: Warp <agent@warp.dev>
128 lines
4.3 KiB
Plaintext
128 lines
4.3 KiB
Plaintext
// The MIT License (MIT)
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("KDJ", "KDJ", overlay=false)
|
|
|
|
//@function Calculates KDJ (K, D, J) lines - enhanced Stochastic Oscillator
|
|
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/oscillators/kdj.md
|
|
//@param high Series of high prices
|
|
//@param low Series of low prices
|
|
//@param close Series of close prices
|
|
//@param length Lookback period for highest/lowest calculation
|
|
//@param signal Smoothing period for K and D lines
|
|
//@returns Tuple [K line, D line, J line]
|
|
//@optimized Uses Wilder's RMA smoothing and deque min/max for highest/lowest, O(n) amortized
|
|
kdj(series float high, series float low, series float close, simple int length, simple int signal) =>
|
|
if length <= 0 or signal <= 0
|
|
runtime.error("Length and signal must be greater than 0")
|
|
if length > 500
|
|
runtime.error("Length exceeds maximum of 500")
|
|
|
|
float alpha = 1.0 / signal
|
|
float beta = 1.0 - alpha
|
|
var bool warmupK = true
|
|
var bool warmupD = true
|
|
var float eK = 1.0
|
|
var float eD = 1.0
|
|
var float k = 0.0
|
|
var float d = 0.0
|
|
var float resultK = 50.0
|
|
var float resultD = 50.0
|
|
|
|
var int lastLength = 0
|
|
var array<int> maxDeque = array.new_int(0)
|
|
var array<int> minDeque = array.new_int(0)
|
|
var array<float> highBuffer = array.new_float(0)
|
|
var array<float> lowBuffer = array.new_float(0)
|
|
|
|
if length != lastLength
|
|
lastLength := length
|
|
maxDeque := array.new_int(0)
|
|
minDeque := array.new_int(0)
|
|
highBuffer := array.new_float(length, na)
|
|
lowBuffer := array.new_float(length, na)
|
|
warmupK := true
|
|
warmupD := true
|
|
eK := 1.0
|
|
eD := 1.0
|
|
k := 0.0
|
|
d := 0.0
|
|
resultK := 50.0
|
|
resultD := 50.0
|
|
|
|
if not na(high) and not na(low) and not na(close)
|
|
int currentBar = bar_index
|
|
int slot = currentBar % length
|
|
|
|
array.set(highBuffer, slot, high)
|
|
array.set(lowBuffer, slot, low)
|
|
|
|
while array.size(maxDeque) > 0 and array.get(maxDeque, 0) <= currentBar - length
|
|
array.shift(maxDeque)
|
|
|
|
while array.size(minDeque) > 0 and array.get(minDeque, 0) <= currentBar - length
|
|
array.shift(minDeque)
|
|
|
|
while array.size(maxDeque) > 0 and array.get(highBuffer, array.get(maxDeque, array.size(maxDeque) - 1) % length) <= high
|
|
array.pop(maxDeque)
|
|
|
|
while array.size(minDeque) > 0 and array.get(lowBuffer, array.get(minDeque, array.size(minDeque) - 1) % length) >= low
|
|
array.pop(minDeque)
|
|
|
|
array.push(maxDeque, currentBar)
|
|
array.push(minDeque, currentBar)
|
|
|
|
float highest = high
|
|
float lowest = low
|
|
if array.size(maxDeque) > 0
|
|
highest := array.get(highBuffer, array.get(maxDeque, 0) % length)
|
|
if array.size(minDeque) > 0
|
|
lowest := array.get(lowBuffer, array.get(minDeque, 0) % length)
|
|
|
|
float price_range = highest - lowest
|
|
|
|
float rsv = price_range > 0 ? 100.0 * (close - lowest) / price_range : 50.0
|
|
|
|
k := alpha * rsv + beta * k
|
|
d := alpha * k + beta * d
|
|
|
|
if warmupK
|
|
eK *= beta
|
|
float cK = 1.0 / (1.0 - eK)
|
|
resultK := math.max(0.0, math.min(100.0, cK * k))
|
|
warmupK := eK > 1e-10
|
|
else
|
|
resultK := math.max(0.0, math.min(100.0, k))
|
|
|
|
if warmupD
|
|
eD *= beta
|
|
float cD = 1.0 / (1.0 - eD)
|
|
resultD := math.max(0.0, math.min(100.0, cD * d))
|
|
warmupD := eD > 1e-10
|
|
else
|
|
resultD := math.max(0.0, math.min(100.0, d))
|
|
|
|
float j = 3.0 * resultK - 2.0 * resultD
|
|
|
|
[resultK, resultD, j]
|
|
|
|
// ---------- Main loop ----------
|
|
|
|
// Inputs
|
|
i_length = input.int(9, "Length", minval=1, maxval=500)
|
|
i_signal = input.int(3, "Signal", minval=1, maxval=50)
|
|
|
|
// Calculation
|
|
[k, d, j] = kdj(high, low, close, i_length, i_signal)
|
|
|
|
// Plot
|
|
plot(k, "K", color=color.blue, linewidth=2)
|
|
plot(d, "D", color=color.red, linewidth=2)
|
|
plot(j, "J", color=color.yellow, linewidth=2)
|
|
|
|
hline(80, "Overbought K", color=color.red, linestyle=hline.style_dotted)
|
|
hline(70, "Overbought D", color=color.orange, linestyle=hline.style_dotted)
|
|
hline(50, "Midline", color=color.gray, linestyle=hline.style_solid)
|
|
hline(30, "Oversold D", color=color.lime, linestyle=hline.style_dotted)
|
|
hline(20, "Oversold K", color=color.green, linestyle=hline.style_dotted)
|