@tk · fractal rsi levels
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © gabrielrtakeda

//@version=5
indicator(title="@tk · fractal rsi levels", overlay=true)


//
// <import_statements>
//
import gabrielrtakeda/tk/1 as tk


//
// <constant_declarations>
//
MINUTE  = 60
HOUR    = 60 * MINUTE
DAY     = 24 * HOUR
WEEK    = 7 * DAY
MONTH   = 4 * WEEK

TRANSPARENT = color.rgb(0, 0, 0, 100)

string FRACTAL_RSI_LEVEL_COLOR_HINT = 'This color will be applied for all fractal RSI Levels. The data that will differ from each other is the line\'s label with timeframe `period` on each one.'
string LABELS_SHOW_PRICE_SCALE_HINT = 'Enable this option to see labels on price scale.'
string LABELS_SHOW_PRICE_RAY_HINT = 'Enable this option to see fractal RSI levels prices on fractal rays respectively.'
string FRACTAL_RAY_VISIBILITY_RULE_HINT = 'When you choose the `15m` option, the fractal ray will be visible only when the current timeframe was less than or equals to 15 minutes. Condition: `if tf != CURRENT_TIMEFRAME and tf <= CHOSEN_OPTION`.'

DEFAULT_TABLE_SIZE = size.small

//
// <inputs>
//
string GROUP_GENERAL                    = 'General'
string textSizeInput                    = input.string(title='Text Size', defval=DEFAULT_TABLE_SIZE, options=[size.tiny, size.small, size.normal, size.large], group=GROUP_GENERAL)

string GROUP_RSI_LEVELS                 = 'RSI Levels · Settings'
int preOversoldLevelInput               = input.int(33, 'Pre-oversold Level', minval=1, maxval=49, group=GROUP_RSI_LEVELS)
int preOverboughtLevelInput             = input.int(67, 'Pre-overbought Level', minval=51, maxval=100, group=GROUP_RSI_LEVELS)
bool showPreOverLevelsInput             = input.bool(true, 'Show "Pre-over" Levels.', tooltip='pre-oversold / pre-overbought', group=GROUP_RSI_LEVELS)

string GROUP_FRACTAL_SETTINGS           = 'Fractal Rays · Settings'
int fractalRsiLengthInput               = input.int(14, 'Length', minval=1, group=GROUP_FRACTAL_SETTINGS)
float fractalRsiSourceInput             = input.source(close, 'Source', group=GROUP_FRACTAL_SETTINGS)

string GROUP_FRACTAL_RAYS               = 'Fractal Rays · Style'
color fractalRsiRayColorInput           = input.color(color.rgb(187, 74, 207), 'Ray Color', tooltip=FRACTAL_RSI_LEVEL_COLOR_HINT, group=GROUP_FRACTAL_RAYS)
string fractalRsiRayStyleInput          = input.string(line.style_dotted, 'Ray Style', options=[line.style_solid, line.style_dashed, line.style_dotted], group=GROUP_FRACTAL_RAYS)
int fractalRsiRayLengthInput            = input.int(15, 'Ray Length', group=GROUP_FRACTAL_RAYS)

string GROUP_FRACTAL_OVERSOLD           = 'Fractal Rays · Oversold'
int fractalRsiOversoldLvlInput          = input.int(30, 'Oversold Level', minval=1, group=GROUP_FRACTAL_OVERSOLD)
string fractalRsiOversoldPrefixInput    = input.string(title='Oversold Prefix', defval='🚀', group=GROUP_FRACTAL_OVERSOLD)
string fractalRsiOversoldSuffixInput    = input.string(title='Oversold Suffix', defval='', group=GROUP_FRACTAL_OVERSOLD)

string GROUP_FRACTAL_OVERBOUGHT         = 'Fractal Rays · Overbought'
int fractalRsiOverboughtLvlInput        = input.int(70, 'Overbought Level', minval=1, group=GROUP_FRACTAL_OVERBOUGHT)
string fractalRsiOverboughtPrefixInput  = input.string(title='Overbought Prefix', defval='🐻', group=GROUP_FRACTAL_OVERBOUGHT)
string fractalRsiOverboughtSuffixInput  = input.string(title='Overbought Suffix', defval='', group=GROUP_FRACTAL_OVERBOUGHT)


string GROUP_FRACTAL_VISIBILITY_RULES   = 'Fractal Rays · Visiblity Rules'
string fractalRayVisRuleInput_1m        = input.string('15m',   '· for `1m` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_5m        = input.string('1h',    '· for `5m` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_15m       = input.string('4h',    '· for `15m` fractal',  options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_1h        = input.string('D',     '· for `1h` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_4h        = input.string('W',     '· for `4h` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_12h       = input.string('W',     '· for `12h` fractal',  options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_1D        = input.string('W',     '· for `1D` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)
string fractalRayVisRuleInput_1W        = input.string('W',     '· for `1W` fractal',   options=['hidden', '1m', '5m', '15m', '1h', '4h', '12h', 'D', 'W'], tooltip=FRACTAL_RAY_VISIBILITY_RULE_HINT, group=GROUP_FRACTAL_VISIBILITY_RULES)

string GROUP_LABELS                     = 'Labels'
bool showLabelsPriceScaleInput          = input.bool(false, 'Show Labels on Price Scale.', tooltip=LABELS_SHOW_PRICE_SCALE_HINT, group=GROUP_LABELS)
bool showPriceRayInput                  = input.bool(false, 'Show Price on Fractal Rays.', tooltip=LABELS_SHOW_PRICE_RAY_HINT, group=GROUP_LABELS)


//
// <function_declarations>
//

// @function Converts the fractal rays timeframe visibility rule label to timestamp int.
// @param string visibilityRule Fractal ray visibility rule label.
// @returns int Fractal ray visibility rule timestamp.
fn_fractalVisibilityRule(string visibilityRule) =>
    int ms = 1
    if visibilityRule == 'hidden'
        ms := 1
    else if visibilityRule == '1m'
        ms := 1 * MINUTE
    else if visibilityRule == '5m'
        ms := 5 * MINUTE
    else if visibilityRule == '15m'
        ms := 15 * MINUTE
    else if visibilityRule == '1h'
        ms := 1 * HOUR
    else if visibilityRule == '4h'
        ms := 4 * HOUR
    else if visibilityRule == '12h'
        ms := 12 * HOUR
    else if visibilityRule == 'D'
        ms := 1 * DAY
    else if visibilityRule == 'W'
        ms := 1 * WEEK
    ms

// @function Requests fractal data based on `period` for a given expression.
// @param string period Timeframe period for the desired fractal.
// @param mixed expression Security expression that will be applied for calculation.
// @returns mixed A result determined by expression.
fn_requestFractal(string period, expression) =>
    request.security(syminfo.tickerid, period, expression, gaps=barmerge.gaps_off)

// @function Plots ray after chart bars for the current time.
// @param float y Value in price scale for ray's vertical positioning.
// @param string label Text that will be displayed as label.
// @param color color The color of both, ray and text.
// @param int length The length of the ray in bars count. (default: 12)
// @returns void This function only plots the elements into the chart
fn_plotRay(float y, string label, color color, int length=12) =>
    lb = label.new(x=bar_index + length, y=y, text=label, color=color.rgb(0, 0, 0, 100), textcolor=color, style=label.style_label_left, size=textSizeInput)
    ln = line.new(x1=bar_index, y1=y, x2=label.get_x(lb) + 1, y2=y, style=fractalRsiRayStyleInput, color=color)
    label.delete(lb[1])
    line.delete(ln[1])

// @function Plots RSI Levels ray after chart bars for the current time.
// @param simple string period Timeframe period.
// @param simple int level Relative Strength Index level.
// @param color color The color of both, ray and label text.
// @returns void This function only plots the elements into the chart
fn_plotRsiLevelRay(simple string period, simple int level, color color) =>
    fractalRsiUpLvl = fn_requestFractal(period, tk.rsiUp())
    fractalRsiDownLvl = fn_requestFractal(period, tk.rsiDown())
    hasFractalRsiLevel = not na(fractalRsiUpLvl) and not na(fractalRsiDownLvl)
    rsiLevel = tk.rsiLevel(level, fractalRsiSourceInput, fractalRsiLengthInput, fractalRsiUpLvl, fractalRsiDownLvl)
    prefix = level < 50 ? fractalRsiOversoldPrefixInput : fractalRsiOverboughtPrefixInput
    suffix = level < 50 ? fractalRsiOversoldSuffixInput : fractalRsiOverboughtSuffixInput
    priceLabel = showPriceRayInput ? str.format(' · {0,number,' + str.tostring(syminfo.mintick) + '}', rsiLevel) : ''
    fn_plotRay(hasFractalRsiLevel ? rsiLevel : na, prefix + ' ' + tk.labelizeTimeFrame(period) + priceLabel + ' ' + suffix , color, length=fractalRsiRayLengthInput)

//
// <calculations>
//
tf = timeframe.in_seconds()

// upperband
LVL_80 = tk.rsiLevel(80)
LVL_78 = tk.rsiLevel(78)
LVL_76 = tk.rsiLevel(76)
LVL_74 = tk.rsiLevel(74)
LVL_72 = tk.rsiLevel(72)
LVL_70 = tk.rsiLevel(70)
PRE_OVERBOUGHT_LVL = tk.rsiLevel(preOverboughtLevelInput)

// lowerband
PRE_OVERSOLD_LVL = tk.rsiLevel(preOversoldLevelInput)
LVL_30 = tk.rsiLevel(30)
LVL_28 = tk.rsiLevel(28)
LVL_26 = tk.rsiLevel(26)
LVL_24 = tk.rsiLevel(24)
LVL_22 = tk.rsiLevel(22)
LVL_20 = tk.rsiLevel(20)


//
// <strategy_calls>
//


//
// <visuals>
//

// options
labelDisplay = showLabelsPriceScaleInput ? display.all : display.pane

// upperband
PLOT_80 = plot(LVL_80, title="Level 80", color=color.new(color.purple, 65), linewidth=2, display=labelDisplay)
PLOT_78 = plot(LVL_78, title="Level 78", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_76 = plot(LVL_76, title="Level 76", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_74 = plot(LVL_74, title="Level 74", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_72 = plot(LVL_72, title="Level 72", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_70 = plot(LVL_70, title="Level 70 · Overbought", color=color.new(color.purple, 20), linewidth=1, display=labelDisplay)
fill(PLOT_80, PLOT_70, title="Upperband Background", color=color.new(color.purple, 93))

// lowerband
PLOT_30 = plot(LVL_30, title="Level 30 · Oversold", color=color.new(color.purple, 20), linewidth=1, display=labelDisplay)
PLOT_28 = plot(LVL_28, title="Level 28", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_26 = plot(LVL_26, title="Level 26", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_24 = plot(LVL_24, title="Level 24", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_22 = plot(LVL_22, title="Level 22", color=color.new(color.purple, 75), linewidth=1, display=labelDisplay)
PLOT_20 = plot(LVL_20, title="Level 20", color=color.new(color.purple, 65), linewidth=2, display=labelDisplay)
fill(PLOT_30, PLOT_20, title="Lowerband Background", color=color.new(color.purple, 93))

// 'pre-over'
PRE_OVERBOUGHT_PLOT = plot(showPreOverLevelsInput ? PRE_OVERBOUGHT_LVL : na, title="Pre-overbought Level", color=color.new(color.white, 95), linewidth=1, display=display.pane)
PRE_OVERSOLD_PLOT   = plot(showPreOverLevelsInput ? PRE_OVERSOLD_LVL : na, title="Pre-oversold Level", color=color.new(color.white, 95), linewidth=1, display=display.pane)
fill(PRE_OVERBOUGHT_PLOT, PLOT_70, title="Pre-overbought Background", color=color.new(color.white, 98))
fill(PRE_OVERSOLD_PLOT, PLOT_30, title="Pre-oversold Background", color=color.new(color.white, 98))

//
// fractal rsi levels
//

bool visibilityRule_1m = tf != 1 * MINUTE and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_1m)
fn_plotRsiLevelRay('1', fractalRsiOversoldLvlInput, visibilityRule_1m ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('1', fractalRsiOverboughtLvlInput, visibilityRule_1m ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_5m = tf != 5 * MINUTE and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_5m)
fn_plotRsiLevelRay('5', fractalRsiOversoldLvlInput, visibilityRule_5m ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('5', fractalRsiOverboughtLvlInput, visibilityRule_5m ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_15m = tf != 15 * MINUTE and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_15m)
fn_plotRsiLevelRay('15', fractalRsiOversoldLvlInput, visibilityRule_15m ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('15', fractalRsiOverboughtLvlInput, visibilityRule_15m ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_1h = tf != 1 * HOUR and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_1h)
fn_plotRsiLevelRay('60', fractalRsiOversoldLvlInput, visibilityRule_1h ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('60', fractalRsiOverboughtLvlInput, visibilityRule_1h ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_4h = tf != 4 * HOUR and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_4h)
fn_plotRsiLevelRay('240', fractalRsiOversoldLvlInput, visibilityRule_4h ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('240', fractalRsiOverboughtLvlInput, visibilityRule_4h ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_12h = tf != 12 * HOUR and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_12h)
fn_plotRsiLevelRay('720', fractalRsiOversoldLvlInput, visibilityRule_12h ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('720', fractalRsiOverboughtLvlInput, visibilityRule_12h ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_1D = tf != 1 * DAY and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_1D)
fn_plotRsiLevelRay('D', fractalRsiOversoldLvlInput, visibilityRule_1D ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('D', fractalRsiOverboughtLvlInput, visibilityRule_1D ? fractalRsiRayColorInput : TRANSPARENT)

bool visibilityRule_1W = tf != 1 * WEEK and tf <= fn_fractalVisibilityRule(fractalRayVisRuleInput_1W)
fn_plotRsiLevelRay('W', fractalRsiOversoldLvlInput, visibilityRule_1W ? fractalRsiRayColorInput : TRANSPARENT)
fn_plotRsiLevelRay('W', fractalRsiOverboughtLvlInput, visibilityRule_1W ? fractalRsiRayColorInput : TRANSPARENT)


//
// HUD
//

// hudInstance = tk.hudInit()

// if tk.isHudReady()
//     tk.hud(hudInstance, array.from('debug', str.tostring(fn_fractalVisibilityRule(fractalRayVisRuleInput_1m))))


//
// <alerts>
//
