Repeated Median Regression with Interactive Range Selection
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tbiktag
//
//
// The script calculates linear regression between two points which can be interactively
// selected on the chart. It uses a robust regression algorithm known as repeated median regression. 
//
//
// @version=5
indicator("Repeated Median Regression", overlay=true, max_bars_back = 5000)
//
// Get the functions from the LinearRegressionLibrary
import tbiktag/LinearRegressionLibrary/1 as linreg
//
// --- constants ---
int    DEAFAULT_TIME1 = timestamp("2021-09")
int    DEAFAULT_TIME2 = timestamp("2021-10")
//
//
// --- inputs ---
float  src            = input(title = "Source", defval = close)
int    startTimeInput = input.time(title = "T1", defval = DEAFAULT_TIME1, confirm = true)
int    endTimeInput   = input.time(title = "T2", defval = DEAFAULT_TIME2, confirm = true)
bool   showInterval   = input.bool(title = "Show Channel with Width Factor = ", defval = true, inline = "lineInt",group = "Options")
float  mult           = input.float(title = "", defval = 2, minval = 0.1, inline = "lineInt",group = "Options", tooltip = "Calculates and plots the prediction interval of the regression defined as: \n Central line ± Mean Absolute Error (MAE) times Width")
bool   showResult     = input.bool(title = "Show Infobox", defval = true, group = "Options", tooltip = 'The box in the lower left corner shows the estimated slope and mean absolute error (MAE) of the regression.')
bool   extendln       = input(title = "Extend Line to the Right", defval = false,group = "Options")
bool   showBorder     = input.bool(title = "Show Interval Borders", defval = true, group = "Options", tooltip = 'Marks the beginning and end of the interval with vertical lines.')
color  colUp          = input.color( title = "Color Scheme", defval = #71BC68, inline = "linecol",group = "Appearence")
color  colDw          = input.color( title = "", defval = #F29388, inline = "linecol",group = "Appearence")
int    lw             = input(title = "Line Width", defval = 2,group = "Appearence")
//
//
// --- functions ---
getBarIndexForTime(t) =>
    // Obtain the bar index from the timestamp. The function is adopted from 
    // the script published by TradingView:
    // https://www.tradingview.com/script/SkmMrMe0-CAGR-Custom-Range/
    // Input: 
    //    t  :: timestamp
    // Outup ::  integer, bar index
    var int barindex_t = na
    if time[1] <= t and time >= t and na(barindex_t)
        barindex_t := bar_index
    barindex_t
//
// 
// --- calculations ---
// initialize time interval
int    startTime = math.min(startTimeInput,endTimeInput)
int    endTime   = math.max(startTimeInput,endTimeInput)
int    len       = getBarIndexForTime(endTime) - getBarIndexForTime(startTime)
if len < 1
    runtime.error("There must be minimum one bar between two points.")
//
// apply regression
[mSlope, mIntercept] = linreg.RepeatedMedian(src, len, getBarIndexForTime(endTime))
float  startY        = mIntercept
float  endY          = mIntercept+mSlope*(len-1)
float  mae           = linreg.metricMAE(src,len, getBarIndexForTime(endTime),mSlope,mIntercept)
//
//
// --- output ---
// plot line(s)
slopeColor = mSlope>0?colUp:colDw
mainLine = line.new(startTime,startY,endTime,endY,xloc=xloc.bar_time,color=slopeColor,style=line.style_solid,width=lw,extend = extendln?extend.right:extend.none)
line.delete(mainLine[1])
if showInterval
    highLine = line.new(startTime,startY+mae*mult,endTime,endY+mae*mult,xloc=xloc.bar_time,color=slopeColor,style=line.style_dashed,width=int(lw/2),extend = extendln?extend.right:extend.none)
    lowLine  = line.new(startTime,startY-mae*mult,endTime,endY-mae*mult,xloc=xloc.bar_time,color=slopeColor,style=line.style_dashed,width=int(lw/2),extend = extendln?extend.right:extend.none)
    line.delete(highLine[1])
    line.delete(lowLine[1])
if showBorder
    borderL  = line.new(startTime,startY+mae*mult,startTime,startY-mae*mult,xloc=xloc.bar_time,color=color.blue,style=line.style_solid,width=1)
    borderR  = line.new(endTime,endY+mae*mult,endTime,endY-mae*mult,xloc=xloc.bar_time,color=color.blue,style=line.style_solid,width=1)
    line.delete(borderL[1])
    line.delete(borderR[1])
//
// display infobox
if barstate.islast and showResult
    var infobox = table.new(position = position.bottom_left, columns = 1, rows = 3, bgcolor = slopeColor)
    table.cell(table_id = infobox, column = 0, row = 0, text = "Regression results",text_size = size.normal)
    table.cell(table_id = infobox, column = 0, row = 1, text = str.format("Slope: {0,number,#.#}",mSlope),text_size = size.normal, text_halign = text.align_left)
    table.cell(table_id = infobox, column = 0, row = 2, text = str.format("MAE: {0,number,#.#}",mae),text_size = size.normal, text_halign = text.align_left)
//
