Untitled
unknown
plain_text
a year ago
18 kB
228
Indexable
// © Julien_Eche
// Copyright (c) 2023-present, Julien Eche
// Adaptive Trend Finder script may be freely distributed under the terms of the GPL-3.0 license.
// Created in December 2023 by Julien Eche
//@version=6
indicator('Adaptive Trend Finder (log)', shorttitle = 'Adaptive Trend Finder', overlay = true, max_bars_back = 1200)
confidence(pearsonR) =>
switch
pearsonR < 0.2 => 'Extremely Weak'
pearsonR < 0.3 => 'Very Weak'
pearsonR < 0.4 => 'Weak'
pearsonR < 0.5 => 'Mostly Weak'
pearsonR < 0.6 => 'Somewhat Weak'
pearsonR < 0.7 => 'Moderately Weak'
pearsonR < 0.8 => 'Moderate'
pearsonR < 0.9 => 'Moderately Strong'
pearsonR < 0.92 => 'Mostly Strong'
pearsonR < 0.94 => 'Strong'
pearsonR < 0.96 => 'Very Strong'
pearsonR < 0.98 => 'Exceptionally Strong'
=> 'Ultra Strong'
getTablePosition(string pos) =>
switch pos
'Bottom Right' => position.bottom_right
'Bottom Center' => position.bottom_center
'Bottom Left' => position.bottom_left
'Top Right' => position.top_right
'Top Left' => position.top_left
'Top Center' => position.top_center
'Middle Right' => position.middle_right
=> position.middle_left // "Middle Left" - default
// Calculate deviations for given length
calcDev(float source, int length) =>
float logSource = math.log(source)
var int period_1 = length - 1
if barstate.islast
float sumX = 0.0
float sumXX = 0.0
float sumYX = 0.0
float sumY = 0.0
for int i = 1 to length by 1
float lSrc = logSource[i - 1]
sumX := sumX + i
sumXX := sumXX + i * i
sumYX := sumYX + i * lSrc
sumY := sumY + lSrc
sumY
float slope = nz((length * sumYX - sumX * sumY) / (length * sumXX - sumX * sumX))
float average = sumY / length
float intercept = average - slope * sumX / length + slope
float sumDev = 0.0
float sumDxx = 0.0
float sumDyy = 0.0
float sumDyx = 0.0
float regres = intercept + slope * period_1 * 0.5
float sumSlp = intercept
for int i = 0 to period_1 by 1
float lSrc = logSource[i]
float dxt = lSrc - average
float dyt = sumSlp - regres
lSrc := lSrc - sumSlp
sumSlp := sumSlp + slope
sumDxx := sumDxx + dxt * dxt
sumDyy := sumDyy + dyt * dyt
sumDyx := sumDyx + dxt * dyt
sumDev := sumDev + lSrc * lSrc
sumDev
float unStdDev = math.sqrt(sumDev / period_1) // unbiased
float divisor = sumDxx * sumDyy
float pearsonR = nz(sumDyx / math.sqrt(divisor))
[unStdDev, pearsonR, slope, intercept]
else
[na, na, na, na]
string t1 = 'In Long-Term Channel mode, if the channel is not visible, scroll back on the chart for additional historical data. To view both Short-Term and Long-Term channels simultaneously, load this indicator twice on your chart.'
string t2 = 'Displays the length of the period automatically selected by the indicator that shows the strongest trend. This period is determined by identifying the highest correlation between price movements and trend direction.'
string t3 = 'Pearson\'s R is a statistical measure that evaluates the linear relationship between price movements and trend projection. A value closer to 1 indicates a strong positive correlation, increasing confidence in the trend direction based on historical data.'
string t4 = 'Displays the annualized return (CAGR) of the trend over the auto-selected period. This feature is available only for daily (D) and weekly (W) timeframes, providing insight into the expected yearly growth rate if the trend continues.'
sourceInput = input.source(close, title = 'Source')
string group0 = 'CHANNEL SETTINGS'
bool periodMode = input.bool(false, 'Use Long-Term Channel', group = group0, tooltip = t1)
float devMultiplier = input.float(2.0, 'Deviation Multiplier:', group = group0, step = 0.1)
color colorInput = input.color(color.gray, '', group = group0, inline = group0)
string lineStyle1 = input.string('Solid', '', group = group0, inline = group0, options = ['Solid', 'Dotted', 'Dashed'])
string extendStyle = input.string('Extend Right', '', group = group0, inline = group0, options = ['Extend Right', 'Extend Both', 'Extend None', 'Extend Left'])
int fillTransparency = input.int(93, 'Fill Transp:', group = group0, inline = 'mid', minval = 0, maxval = 100, step = 1)
int channelTransparency = input.int(40, 'Line Transp:', group = group0, inline = 'mid', minval = 0, maxval = 100, step = 1)
string group1 = 'MIDLINE SETTINGS'
color colorInputMidline = input.color(color.blue, '', group = group1, inline = group1)
int transpInput = input.int(100, 'Transp:', group = group1, inline = group1, minval = 0, maxval = 100, step = 10)
int lineWidth = input.int(1, 'Line Width:', group = group1, inline = group1)
string midLineStyle = input.string('Dashed', '', group = group1, inline = group1, options = ['Dotted', 'Solid', 'Dashed'])
string group2 = 'TABLE SETTINGS'
bool showAutoSelectedPeriod = input(true, 'Show Auto-Selected Period', group = group2, tooltip = t2)
bool showTrendStrength = input(true, 'Show Trend Strength', group = group2, inline = 'secondLine')
bool showPearsonInput = input.bool(false, 'Show Pearson\'s R', group = group2, inline = 'secondLine', tooltip = t3)
bool showTrendAnnualizedReturn = input(true, 'Show Trend Annualized Return', group = group2, tooltip = t4)
string tablePositionInput = input.string('Bottom Right', 'Table Position', options = ['Bottom Right', 'Bottom Left', 'Middle Right', 'Middle Left', 'Top Right', 'Top Left', 'Top Center', 'Bottom Center'], group = group2, inline = 'fourthLine')
string textSizeInput = input.string('Normal', 'Text Size', options = ['Normal', 'Large', 'Small'], group = group2, inline = 'fourthLine')
// Helper function to get the multiplier based on timeframe
get_tf_multiplier() =>
var float multiplier = 1.0
if syminfo.type == 'crypto'
if timeframe.isdaily
multiplier := 365 // ~365 trading days per year
multiplier
else if timeframe.isweekly
multiplier := 52 // 52 weeks per year
multiplier
multiplier
else // Default for stocks and other asset types
if timeframe.isdaily
multiplier := 252 // ~252 trading days per year
multiplier
else if timeframe.isweekly
multiplier := 52 // 52 weeks per year
multiplier
multiplier
// Helper function to check if the timeframe is daily or weekly
is_valid_timeframe() =>
timeframe.isdaily or timeframe.isweekly
var string EXTEND_STYLE = switch extendStyle
'Extend Right' => extend.right
'Extend Both' => extend.both
'Extend None' => extend.none
=> extend.left
// Length Inputs
var array<int> Periods = periodMode ? array.from(na, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1150, 1200) : array.from(na, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)
// Calculate deviations, correlation, slope, and intercepts for different lengths
[stdDev01, pearsonR01, slope01, intercept01] = calcDev(sourceInput, Periods.get(1))
[stdDev02, pearsonR02, slope02, intercept02] = calcDev(sourceInput, Periods.get(2))
[stdDev03, pearsonR03, slope03, intercept03] = calcDev(sourceInput, Periods.get(3))
[stdDev04, pearsonR04, slope04, intercept04] = calcDev(sourceInput, Periods.get(4))
[stdDev05, pearsonR05, slope05, intercept05] = calcDev(sourceInput, Periods.get(5))
[stdDev06, pearsonR06, slope06, intercept06] = calcDev(sourceInput, Periods.get(6))
[stdDev07, pearsonR07, slope07, intercept07] = calcDev(sourceInput, Periods.get(7))
[stdDev08, pearsonR08, slope08, intercept08] = calcDev(sourceInput, Periods.get(8))
[stdDev09, pearsonR09, slope09, intercept09] = calcDev(sourceInput, Periods.get(9))
[stdDev10, pearsonR10, slope10, intercept10] = calcDev(sourceInput, Periods.get(10))
[stdDev11, pearsonR11, slope11, intercept11] = calcDev(sourceInput, Periods.get(11))
[stdDev12, pearsonR12, slope12, intercept12] = calcDev(sourceInput, Periods.get(12))
[stdDev13, pearsonR13, slope13, intercept13] = calcDev(sourceInput, Periods.get(13))
[stdDev14, pearsonR14, slope14, intercept14] = calcDev(sourceInput, Periods.get(14))
[stdDev15, pearsonR15, slope15, intercept15] = calcDev(sourceInput, Periods.get(15))
[stdDev16, pearsonR16, slope16, intercept16] = calcDev(sourceInput, Periods.get(16))
[stdDev17, pearsonR17, slope17, intercept17] = calcDev(sourceInput, Periods.get(17))
[stdDev18, pearsonR18, slope18, intercept18] = calcDev(sourceInput, Periods.get(18))
[stdDev19, pearsonR19, slope19, intercept19] = calcDev(sourceInput, Periods.get(19))
if barstate.islast
// Find the highest Pearson's R
float highestPearsonR = math.max(pearsonR01, pearsonR02, pearsonR03, pearsonR04, pearsonR05, pearsonR06, pearsonR07, pearsonR08, pearsonR09, pearsonR10, pearsonR11, pearsonR12, pearsonR13, pearsonR14, pearsonR15, pearsonR16, pearsonR17, pearsonR18, pearsonR19)
// Determine selected length, slope, intercept, and deviations
int detectedPeriod = na
float detectedSlope = na
float detectedIntrcpt = na
float detectedStdDev = na
switch highestPearsonR
pearsonR01 =>
detectedPeriod := Periods.get(1)
detectedSlope := slope01
detectedIntrcpt := intercept01
detectedStdDev := stdDev01
detectedStdDev
pearsonR02 =>
detectedPeriod := Periods.get(2)
detectedSlope := slope02
detectedIntrcpt := intercept02
detectedStdDev := stdDev02
detectedStdDev
pearsonR03 =>
detectedPeriod := Periods.get(3)
detectedSlope := slope03
detectedIntrcpt := intercept03
detectedStdDev := stdDev03
detectedStdDev
pearsonR04 =>
detectedPeriod := Periods.get(4)
detectedSlope := slope04
detectedIntrcpt := intercept04
detectedStdDev := stdDev04
detectedStdDev
pearsonR05 =>
detectedPeriod := Periods.get(5)
detectedSlope := slope05
detectedIntrcpt := intercept05
detectedStdDev := stdDev05
detectedStdDev
pearsonR06 =>
detectedPeriod := Periods.get(6)
detectedSlope := slope06
detectedIntrcpt := intercept06
detectedStdDev := stdDev06
detectedStdDev
pearsonR07 =>
detectedPeriod := Periods.get(7)
detectedSlope := slope07
detectedIntrcpt := intercept07
detectedStdDev := stdDev07
detectedStdDev
pearsonR08 =>
detectedPeriod := Periods.get(8)
detectedSlope := slope08
detectedIntrcpt := intercept08
detectedStdDev := stdDev08
detectedStdDev
pearsonR09 =>
detectedPeriod := Periods.get(9)
detectedSlope := slope09
detectedIntrcpt := intercept09
detectedStdDev := stdDev09
detectedStdDev
pearsonR10 =>
detectedPeriod := Periods.get(10)
detectedSlope := slope10
detectedIntrcpt := intercept10
detectedStdDev := stdDev10
detectedStdDev
pearsonR11 =>
detectedPeriod := Periods.get(11)
detectedSlope := slope11
detectedIntrcpt := intercept11
detectedStdDev := stdDev11
detectedStdDev
pearsonR12 =>
detectedPeriod := Periods.get(12)
detectedSlope := slope12
detectedIntrcpt := intercept12
detectedStdDev := stdDev12
detectedStdDev
pearsonR13 =>
detectedPeriod := Periods.get(13)
detectedSlope := slope13
detectedIntrcpt := intercept13
detectedStdDev := stdDev13
detectedStdDev
pearsonR14 =>
detectedPeriod := Periods.get(14)
detectedSlope := slope14
detectedIntrcpt := intercept14
detectedStdDev := stdDev14
detectedStdDev
pearsonR15 =>
detectedPeriod := Periods.get(15)
detectedSlope := slope15
detectedIntrcpt := intercept15
detectedStdDev := stdDev15
detectedStdDev
pearsonR16 =>
detectedPeriod := Periods.get(16)
detectedSlope := slope16
detectedIntrcpt := intercept16
detectedStdDev := stdDev16
detectedStdDev
pearsonR17 =>
detectedPeriod := Periods.get(17)
detectedSlope := slope17
detectedIntrcpt := intercept17
detectedStdDev := stdDev17
detectedStdDev
pearsonR18 =>
detectedPeriod := Periods.get(18)
detectedSlope := slope18
detectedIntrcpt := intercept18
detectedStdDev := stdDev18
detectedStdDev
=> // pearsonR19
detectedPeriod := Periods.get(19)
detectedSlope := slope19
detectedIntrcpt := intercept19
detectedStdDev := stdDev19
detectedStdDev
var line upperLine = na
var linefill upperFill = na
var line baseLine = na
var line lowerLine = na
var linefill lowerFill = na
// Calculate start and end price based on detected slope and intercept
float startPrice = math.exp(detectedIntrcpt + detectedSlope * (detectedPeriod - 1))
float endPrice = math.exp(detectedIntrcpt)
int startAtBar = bar_index - detectedPeriod + 1
var color ChannelColor = color.new(colorInput, channelTransparency)
if na(baseLine)
baseLine := line.new(startAtBar, startPrice, bar_index, endPrice, width = lineWidth, extend = EXTEND_STYLE, color = color.new(colorInputMidline, transpInput), style = midLineStyle == 'Dotted' ? line.style_dotted : midLineStyle == 'Dashed' ? line.style_dashed : line.style_solid)
baseLine
else
line.set_xy1(baseLine, startAtBar, startPrice)
line.set_xy2(baseLine, bar_index, endPrice)
float upperStartPrice = startPrice * math.exp(devMultiplier * detectedStdDev)
float upperEndPrice = endPrice * math.exp(devMultiplier * detectedStdDev)
if na(upperLine)
upperLine := line.new(startAtBar, upperStartPrice, bar_index, upperEndPrice, width = 1, extend = EXTEND_STYLE, color = ChannelColor, style = lineStyle1 == 'Dotted' ? line.style_dotted : lineStyle1 == 'Dashed' ? line.style_dashed : line.style_solid)
upperLine
else
line.set_xy1(upperLine, startAtBar, upperStartPrice)
line.set_xy2(upperLine, bar_index, upperEndPrice)
line.set_color(upperLine, colorInput)
float lowerStartPrice = startPrice / math.exp(devMultiplier * detectedStdDev)
float lowerEndPrice = endPrice / math.exp(devMultiplier * detectedStdDev)
if na(lowerLine)
lowerLine := line.new(startAtBar, lowerStartPrice, bar_index, lowerEndPrice, width = 1, extend = EXTEND_STYLE, color = ChannelColor, style = lineStyle1 == 'Dotted' ? line.style_dotted : lineStyle1 == 'Dashed' ? line.style_dashed : line.style_solid)
lowerLine
else
line.set_xy1(lowerLine, startAtBar, lowerStartPrice)
line.set_xy2(lowerLine, bar_index, lowerEndPrice)
line.set_color(lowerLine, colorInput)
if na(upperFill)
upperFill := linefill.new(upperLine, baseLine, color = color.new(colorInput, fillTransparency))
upperFill
if na(lowerFill)
lowerFill := linefill.new(baseLine, lowerLine, color = color.new(colorInput, fillTransparency))
lowerFill
var table t = na
if periodMode
t := table.new(position.bottom_center, 2, 3)
t
else
t := table.new(getTablePosition(tablePositionInput), 2, 3)
t
string text1 = periodMode ? 'Auto-Selected Period (Long Term): ' + str.tostring(detectedPeriod) : 'Auto-Selected Period: ' + str.tostring(detectedPeriod)
var colorInputLight = color.new(colorInput, 0)
// Display or hide the "Auto-Selected Period" cell
if showAutoSelectedPeriod
table.cell(t, 0, 0, text1, text_color = colorInputLight, text_size = textSizeInput == 'Large' ? size.large : textSizeInput == 'Small' ? size.small : size.normal)
// Display or hide the "Trend Strength" or "Pearson's R" cell
if showTrendStrength
if showPearsonInput
table.cell(t, 0, 1, 'Pearson\'s R: ' + str.tostring(detectedSlope > 0.0 ? -highestPearsonR : highestPearsonR, '#.###'), text_color = colorInput, text_size = textSizeInput == 'Large' ? size.large : textSizeInput == 'Small' ? size.small : size.normal)
else
table.cell(t, 0, 1, 'Trend Strength: ' + confidence(highestPearsonR), text_color = colorInput, text_size = textSizeInput == 'Large' ? size.large : textSizeInput == 'Small' ? size.small : size.normal)
// Calculate CAGR
float cagr = na
if not na(detectedPeriod) and bar_index >= detectedPeriod and is_valid_timeframe()
float num_of_periods = detectedPeriod
float multiplier = get_tf_multiplier()
float startClosePrice = close[detectedPeriod - 1]
cagr := math.pow(close / startClosePrice, multiplier / num_of_periods) - 1
cagr
// Display or hide the "Trend Annualized Return" cell
if showTrendAnnualizedReturn and is_valid_timeframe()
table.cell(t, 0, 2, 'Trend Annualized Return: ' + (not na(cagr) ? str.tostring(cagr * 100, '#.#') + '%' : 'N/A'), text_color = colorInput, text_size = textSizeInput == 'Large' ? size.large : textSizeInput == 'Small' ? size.small : size.normal)
Editor is loading...
Leave a Comment