Files

128 lines
4.4 KiB
Plaintext

// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Connors RSI (CRSI)", "CRSI", overlay=false)
//@function Calculates RSI using Wilder's smoothing with warmup compensation
//@param source Series to calculate RSI from
//@param period RSI lookback period
//@returns RSI value (0-100) with valid output from bar 1
rsi_calc(series float source, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
float alpha = 1.0 / period
float beta = 1.0 - alpha
var float prevVal = na
var float avgGain = 0.0
var float avgLoss = 0.0
var float e = 1.0
var bool warmup = true
float result = 50.0
if not na(source)
if na(prevVal)
prevVal := source
else
float change = source - prevVal
float gain = change > 0.0 ? change : 0.0
float loss = change < 0.0 ? -change : 0.0
prevVal := source
avgGain := alpha * gain + beta * avgGain
avgLoss := alpha * loss + beta * avgLoss
if warmup
e *= beta
float c = e > 1e-10 ? 1.0 / (1.0 - e) : 1.0
float aG = avgGain * c
float aL = avgLoss * c
float total = aG + aL
result := total != 0.0 ? 100.0 * aG / total : 50.0
if e <= 1e-10
warmup := false
else
float total = avgGain + avgLoss
result := total != 0.0 ? 100.0 * avgGain / total : 50.0
result
//@function Calculates Connors RSI as average of price RSI, streak RSI, and percent rank
//@param source Series to calculate from
//@param rsiPeriod Period for price RSI component
//@param streakPeriod Period for streak RSI component
//@param rankPeriod Lookback period for percent rank of ROC
//@returns Connors RSI value (0-100)
crsi(series float source, simple int rsiPeriod, simple int streakPeriod, simple int rankPeriod) =>
if rsiPeriod <= 0 or streakPeriod <= 0 or rankPeriod <= 0
runtime.error("All periods must be greater than 0")
// Component 1: RSI of price
float priceRsi = rsi_calc(source, rsiPeriod)
// Component 2: Streak calculation + RSI of streak
var float prevClose = na
var float streak = 0.0
if not na(source)
if na(prevClose)
streak := 0.0
else if source > prevClose
streak := streak >= 0.0 ? streak + 1.0 : 1.0
else if source < prevClose
streak := streak <= 0.0 ? streak - 1.0 : -1.0
else
streak := 0.0
prevClose := source
float streakRsi = rsi_calc(streak, streakPeriod)
// Component 3: Percent rank of 1-bar ROC
var float prevSrc = na
var array<float> rocBuf = array.new_float(rankPeriod, na)
var int rocHead = 0
var int rocCount = 0
float roc = 0.0
if not na(source) and not na(prevSrc) and prevSrc != 0.0
roc := (source - prevSrc) / prevSrc * 100.0
if not na(source)
prevSrc := source
// Count how many HISTORICAL ROC values are strictly < current ROC
// BEFORE storing current roc (Connors/Alvarez: "percentage of values the current return is greater than")
int lessCount = 0
for i = 0 to rocCount - 1
float val = array.get(rocBuf, i)
if not na(val) and val < roc
lessCount += 1
float pctRank = rocCount > 0 ? (float(lessCount) / float(rocCount)) * 100.0 : 50.0
// Store current ROC after rank scan
if na(array.get(rocBuf, rocHead))
rocCount := math.min(rocCount + 1, rankPeriod)
array.set(rocBuf, rocHead, roc)
rocHead := (rocHead + 1) % rankPeriod
// Connors RSI = average of three components
float result = (priceRsi + streakRsi + pctRank) / 3.0
math.max(0.0, math.min(100.0, result))
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_rsiPeriod = input.int(3, "RSI Period", minval=1, maxval=500)
i_streakPeriod = input.int(2, "Streak RSI Period", minval=1, maxval=500)
i_rankPeriod = input.int(100, "Percent Rank Period", minval=1, maxval=1000)
// Calculation
crsi_value = crsi(i_source, i_rsiPeriod, i_streakPeriod, i_rankPeriod)
// Plot
plot(crsi_value, "CRSI", color.new(color.yellow, 0), 2)
hline(70, "Overbought", color=color.gray, linestyle=hline.style_dotted)
hline(30, "Oversold", color=color.gray, linestyle=hline.style_dotted)