Ichimoku Cloud Distance
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © NgUTech

//@version=4
study(title="Ichimoku Cloud Distance", shorttitle="ΔCloud")

cloudSettings = input('Doubled Crypto',options=['Standard','Crypto','Doubled Crypto'],title="Cloud Settings")
distanceMode = input('Hybrid',options=['Distance to Center','Distance to Edge','Hybrid'],title="Distance Mode")
hybridModeScaleMethod = input('Timeframe-dependent',options=['Simple','Timeframe-dependent'],title="Hybrid Mode Scale Method")
hybridModeScaleFactor = input(title="Hybrid Mode Scale Factor", type=input.float, defval=1, minval=1, step=1)
thicknessScaleMethod = input('Timeframe-dependent',options=['Simple','Timeframe-dependent'],title="Thickness Scale Method")
thicknessScaleFactor = input(title="Thickness Scale Factor", type=input.float, defval=1, minval=1, step=1)
hybridIntervalColor = input(title="Hybrid Cloud (-1, 1) Interval Color", type=input.color, defval=color.new(color.white, 90))
bullishCloudColor = input(title="Bullish Cloud Color", type=input.color, defval=color.rgb(67, 160, 71, 90))
bearishCloudColor = input(title="Bearish Cloud Color", type=input.color, defval=color.rgb(244, 67, 54, 90))
displayTkCrosses = input(title="Display TK Crosses", type=input.bool, defval=false)
fillBackground = input(title="Fill Background", type=input.bool, defval=false)

conversionPeriods = cloudSettings == 'Standard' ? 9 : cloudSettings == 'Crypto' ? 10 : 20
basePeriods = cloudSettings == 'Standard' ? 26 : cloudSettings == 'Crypto' ? 30 : 60
laggingSpan2Periods = cloudSettings == 'Standard' ? 52 : cloudSettings == 'Crypto' ? 60 : 120
displacement = cloudSettings == 'Standard' ? 26 : cloudSettings == 'Crypto' ? 30 : 30

donchian(len) => avg(lowest(len), highest(len))

conversionLine = donchian(conversionPeriods)
baseLine = donchian(basePeriods)
leadLine1 = avg(conversionLine, baseLine)
leadLine2 = donchian(laggingSpan2Periods)

offset = displacement - 1

bullishCloud = leadLine1[offset] > leadLine2[offset]
bearishCloud = not bullishCloud

crossover2(series1, series2) => series1[1] < series2[1] and series1 > series2 or crossover(series1, series2)

baseLineBearishCross = crossover2(baseLine, conversionLine)
baseLineBullishCross = crossover2(conversionLine, baseLine)

center = (leadLine1[offset] + leadLine2[offset])/2

timeframe = timeframe.ismonthly ? 86400*30*timeframe.multiplier : (timeframe.isweekly ? 86400*7*timeframe.multiplier : (timeframe.isdaily ? 86400*timeframe.multiplier : (timeframe.isminutes ? 60*timeframe.multiplier : timeframe.multiplier)))

scale(val) => (val-1)*(hybridModeScaleMethod == 'Timeframe-dependent' ? 11929.5*pow(timeframe, -0.6470469) : 1)*hybridModeScaleFactor + 1

getDistance(val, offset) =>
    priceBelowBearishCloud = bearishCloud and val < leadLine1[offset]
    priceAboveBearishCloud = bearishCloud and val > leadLine2[offset]
    priceBelowBullishCloud = bullishCloud and val < leadLine2[offset]
    priceAboveBullishCloud = bullishCloud and val > leadLine1[offset]
    
    if nz(leadLine1[offset]) == 0 or nz(leadLine2[offset]) == 0
        distance = 0
    else if distanceMode == 'Distance to Center'
        distance = val > center ? val/center - 1 : -center/val + 1
    else if distanceMode == 'Hybrid'
        distance = priceBelowBearishCloud ? -scale(leadLine1[offset]/val) : (priceAboveBearishCloud ? scale(val/leadLine2[offset]) : (priceBelowBullishCloud ? -scale(leadLine2[offset]/val) : priceAboveBullishCloud ? scale(val/leadLine1[offset]) : (bullishCloud ? (val > center ? 1/(leadLine1[offset]-center)*(val-center) : -1/(center-leadLine2[offset])*(center-val)) : (val < center ? -1/(center-leadLine1[offset])*(center-val) : 1/(leadLine2[offset]-center)*(val-center)))))
    else
        distance = priceBelowBearishCloud ? -leadLine1[offset]/val + 1 : (priceAboveBearishCloud ? val/leadLine2[offset] - 1 : (priceBelowBullishCloud ? -leadLine2[offset]/val + 1 : priceAboveBullishCloud ? val/leadLine1[offset] - 1 : 0))

cloudDistance = getDistance(close, offset)
conversionLineDistance = getDistance(conversionLine, offset)
baseLineDistance = getDistance(baseLine, offset)
laggingDistance = getDistance(close, offset*2)

if barstate.islastconfirmedhistory and distanceMode == 'Hybrid'
    box.new(left=bar_index, top=1, right=bar_index, bottom=-1, extend=extend.both, xloc=xloc.bar_index, border_width=0, bgcolor=hybridIntervalColor)
hline(0, "Zero", color=#787B86)
zero = plot(0, offset=offset, color=color.rgb(0, 0, 0, 100), title="Zero", display=display.none)

cloudThickness = abs(leadLine1 - leadLine2)/center
thicknessScale = thicknessScaleMethod == 'Timeframe-dependent' ? (distanceMode == 'Hybrid' ? 37114.9335600234*pow(timeframe, -0.787826324127987)-0.00000188481158058423*timeframe+25.3717700469544 : 0.08382861154789754*pow(timeframe, 0.3390020697495657)) : 1

cloudThicknessPlot = plot(cloudThickness*thicknessScale*thicknessScaleFactor, offset=offset, color=color.rgb(0, 0, 0, 100), title="Cloud (Kumo) Thickness Outline")
fill(zero, cloudThicknessPlot, color=leadLine1 > leadLine2 ? bullishCloudColor : bearishCloudColor, title="Cloud (Kumo) Thickness Background")

plot(laggingDistance, offset=-offset, color=color.rgb(67, 160, 71, 70), title="Lagging (Chikou) Span Distance", display=display.none)
plot(conversionLineDistance, color=color.rgb(41, 98, 255), title="Conversion (Tenkan-Sen) Line Distance")
plot(baseLineDistance, color=color.rgb(183, 28, 28), title="Base (Kijun-Sen) Line Distance")
plot(cloudDistance, color=color.orange, linewidth=2, title="Price Distance")

bgcolor(displayTkCrosses and baseLineBearishCross ? color.rgb(255, 0, 0) : (displayTkCrosses and baseLineBullishCross ? color.rgb(0, 255, 0) : (fillBackground ? (bullishCloud ? bullishCloudColor : bearishCloudColor) : color.rgb(0 , 0, 0, 100))))
