peacefulIndicators
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © peacefulLizard50262

//@version=5

// @description A custom library of technical indicators for trading analysis, including MACD with Dynamic Length, Stochastic RSI with ATR Stop Loss, Bollinger Bands with RSI Divergence, and more.
library("peacefulIndicators")
import lastguru/DominantCycle/2 as d

ema(float source = close, float length = 9)=>
    alpha = 3 / (length + 1)
    var float smoothed = na
    smoothed := alpha * source + (1 - alpha) * nz(smoothed[1])

// @function Moving Average Convergence Divergence with Dynamic Length
// @param src Series to use
// @param shortLen Shorter moving average length
// @param longLen Longer moving average length
// @param signalLen Signal line length
// @param dynLow Lower bound for the dynamic length
// @param dynHigh Upper bound for the dynamic length
// @returns tuple of MACD line and Signal line
// Computes MACD using lengths adapted based on the dominant cycle period
export macdDynamicLength(float src, int shortLen, int longLen, int signalLen, int dynLow, int dynHigh) =>
    // Get dominant cycle period using Pearson Autocorrelation
    dominantCycle = d.paPeriod(src, dynLow, dynHigh)

    // Calculate adapted lengths based on the dominant cycle period
    shortAdaptedLen = int(math.max(2, math.min(shortLen * dominantCycle / math.avg(dynHigh, dynLow), shortLen)))
    longAdaptedLen = int(math.max(2, math.min(longLen * dominantCycle / math.avg(dynHigh, dynLow), longLen)))
    signalAdaptedLen = int(math.max(2, math.min(signalLen * dominantCycle / math.avg(dynHigh, dynLow), signalLen)))

    // Calculate MACD line and Signal line using adapted lengths
    shortMa = ema(src, shortAdaptedLen)
    longMa = ema(src, longAdaptedLen)
    macdLine = shortMa - longMa
    signalLine = ta.sma(macdLine, signalAdaptedLen)

    [macdLine, signalLine]

// @function RSI Divergence Detection
// @param src Series to use
// @param rsiLen Length for RSI calculation
// @param divThreshold Divergence threshold for RSI
// @param linRegLength Length for linear regression calculation
// @returns tuple of RSI Divergence (positive, negative)
// Computes RSI Divergence detection that identifies bullish (positive) and bearish (negative) divergences
export rsiDivergence(float src, int rsiLen, float divThreshold, int linRegLength) =>
    source = src - ta.linreg(src, linRegLength, 0)
    // Calculate RSI
    rsiLine = ta.rsi(source, rsiLen)

    // Detect RSI Divergence
    priceDelta = (source - source[2])/2
    rsiDelta = (rsiLine - rsiLine[2])/2
    positiveDivergence = priceDelta < 0 and rsiDelta > divThreshold
    negativeDivergence = priceDelta > 0 and rsiDelta < -divThreshold

    [positiveDivergence, negativeDivergence]

// @function Trend Reversal Detection (TRD)
// @param src Series to use
// @param rocLength Length for Rate of Change calculation
// @param maLength Length for Moving Average calculation
// @param maType Type of Moving Average to use (default: "sma")
// @returns A tuple containing trend reversal direction and the reversal point
// Detects trend reversals using the Rate of Change (ROC) and Moving Averages.
export trendReversalDetection(series float src, int rocLength, int maLength, string maType) =>
    // Calculate Rate of Change (ROC)
    roc = (src - src[rocLength]) / src[rocLength]
    ema = ema(src, maLength)
    wma = ta.wma(src, maLength)
    sma = ta.sma(src, maLength)
    // Calculate Moving Average
    ma = maType == "ema" ? ema :
         maType == "wma" ? wma :
         sma

    // Detect trend reversals
    reversalDirection = 0
    var reversalPoint = float(na)
    
    crossover  = ta.crossover(roc, 0) and src > ma
    crossunder = ta.crossunder(roc, 0) and src < ma

    if crossover
        reversalDirection := 1
        reversalPoint := src
    else if crossunder
        reversalDirection := -1
        reversalPoint := src

    [reversalDirection, nz(reversalPoint, nz(reversalPoint[1]))]

// @function Volume Flow Oscillator
// @param src Series to use
// @param vol Volume series to use
// @param length Period for the calculation
// @returns Custom Oscillator value
// Computes the custom oscillator based on price movement strength and volume
export volume_flow_oscillator(float src, int length) =>
    price_delta = src - src[1]
    volume_flow = price_delta * volume
    positive_volume_flow = math.sum(volume_flow > 0 ? volume_flow : 0, length)
    negative_volume_flow = math.sum(volume_flow < 0 ? volume_flow : 0, length)
    vfo = 100 * (positive_volume_flow + negative_volume_flow) / (positive_volume_flow - negative_volume_flow)
    vfo

// @function Weighted Volatility Oscillator
// @param src Series to use
// @param vol Volume series to use
// @param length Period for the calculation
// @returns Custom Oscillator value
// Computes the custom oscillator based on price volatility and volume
export weighted_volatility_oscillator(float src, int length) =>
    price_change = math.abs(src - src[1])
    weighted_volatility = price_change * volume
    wvo = math.sum(weighted_volatility, length) / math.sum(volume, length)
    wvo

// @function Relative Volume Oscillator
// @param vol_volume_series Volume series to use
// @param length Period for the calculation
// @returns Custom Oscillator value
// Computes the custom oscillator based on relative volume
export rvo(int length) =>
    avg_volume = ta.sma(volume, length)
    relative_volume = volume / avg_volume
    rvo_value = (relative_volume - 1) * 100
    rvo_value

// @function Adaptive Channel Breakout
// @param price_series Price series to use
// @param ma_length Period for the moving average calculation
// @param vol_length Period for the volatility calculation
// @param multiplier Multiplier for the volatility
// @returns Tuple containing the ACB upper and lower values and the trend direction (1 for uptrend, -1 for downtrend)
export acb(float price_series, int ma_length, int vol_length, float multiplier) =>
    ma = ta.sma(price_series, ma_length)
    volatility = ta.stdev(price_series, vol_length)
    upper = ma + (multiplier * volatility)
    lower = ma - (multiplier * volatility)
    var bool trend = na
    crossover = ta.crossover(price_series, upper)
    crossunder = ta.crossunder(price_series, lower)
    trend := crossover ? true : crossunder ? false : nz(trend[1], 1)
    [upper, lower, trend]
