Profit Percentage Tracker
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// (C) backslash-f
//
// Pre-requirements:
//  - The chart's timezone has to be set to "Exchange" (this is required to support the alert's logic).
//  - https://kodify.net/tradingview/platform/exchange-time-zone/
//
// This script (supports both long and short positions):
// - Tracks how much profit one could take based on:
//  - The given "entry date" and "entry price".
//  - These are interactive inputs:
//   - https://www.tradingview.com/blog/en/pine-scripts-are-now-interactive-27147
//
// - Displays the result in a label on top of the last candle.
//
// - Sends alerts based on the given "profit unit":
//  - That is: if "0.25" is given, then an alert is triggered every time the profit is raised by that number.
//  - For alerts to be displayed, a "script alert" must be configured beforehand:
//   - https://www.tradingview.com/support/solutions/43000597494-script-alerts/
//   - Choose "PPT" as condition and "Any alert() function call"
//
// - Can add helper lines such as:
//  - Target percentage lines
//  - Entry, target, stop and liquidation lines
//
// - Can format the body of the alert as JSON, so you can send it to your server and automate your trading
//  - Inspired by "Part Time Larry": https://youtu.be/gMRee2srpe8
//
// - Debug info is available and can be enabled / disabled.

//@version=5
indicator("Profit Percentage Tracker", shorttitle="Profits", overlay=true, explicit_plot_zorder=true)

// Input values.

// POSITION
targetSymbol                = input.string(title="Symbol", group="POSITION", defval="BTCUSDT", tooltip="The symbol for which the indicator is being configured. This allows for switching to other symbols in the same tab without showing wrong profit data.\n\nIf you would like to add multiple profit indicators for different symbols, just insert another profit indicator that targets the new symbol.\n\nNotice: the entry must match the chart's symbol exactly. That is, for example: \"BTCUSDT\" (instead of just \"BTC\").")
size                        = input.float(title="Size", group="POSITION", defval=0, minval=0, tooltip="The amount/quantity of the bought asset.")
isLong                      = input(title="Long", group="POSITION", defval=true, tooltip="Indicates the side of the position. Default is \"long\".\n\nUncheck to indicate a \"short\" side.\n\nThe side is taken into consideration when calculating profit.")
isKucoinSpot                = input(title="Spot (Kucoin)", group="POSITION", defval=false, tooltip="Indicates the type of the position (Kucoin only). Default is unchecked (\"Futures\").\n\nCheck to indicate a \"spot\" type.\n\nThe type is taken into consideration when calling Kucoin APIs.")

// PURCHASE
entryDate                   = input.time(timestamp("2022-08-12"), title="Entry date / time", group="PURCHASE", inline="entry", confirm=true, tooltip="The date that the asset was acquired.")
entryPrice                  = input.price(0, title="Entry price", inline="entry", group="PURCHASE", confirm=true, tooltip="The cost of the asset at the time of purchase.")
shouldShowCurrencyProfit    = input(title="Show currency profit", group="PURCHASE", defval=true, tooltip="When checked (default), displays the profit as currency, just below the profit percentage label.\nE.g.: \"135.79 USDT\".\n\nFor this to work, the \"size\" field must be greater than zero.\n\nDevelopment note: the currency is read from \"sysinfo.currency\".")

// ALERT
profitUnit                  = input(title="Percentage unit", defval=1.0, group="ALERT", tooltip="Every time that profit raises by this percentage unit *or more*, an alert is sent. The default is 1.5% (1.5). Remember to use dot -- 0.5, 0.75, 1.27, etc.\n\nExamples: the asset raises from 100 to 101 -- an alert is to be sent. When the same asset raises from 101 to 102 -- another alert is to be sent. So on and so forth.\n\nBe careful with low values. TradingView will stop an alert if it triggers too often.")
alertPrefix                 = input(title="Custom alert prefix", group="ALERT", defval="", tooltip="Add a custom prefix to the first line of the alert to indicate whatever you like. E.g.: \"[FUTURES]\". (A trailing white space will be added automatically.)")

// LINES
shouldShowLines             = input(title="Show lines", group="LINES", defval=true, tooltip="Adds a bunch of helper lines to the chart, such as target percentage lines and entry/target/stop/liquidation price lines.\n\nSome of the lines can be hidden by setting zero as their value.")
shouldShowStopLimitLines    = input(title="Show lines for current and next stop-limits", group="LINES", defval=false, tooltip="When checked, it displays:\n\n - A GRAY line that indicates where the NEXT stop-limit would be (based on the offset)\n\n - A YELLOW line that indicates where the CURRENT stop-limit is (based on the \"offset\" field + last alert)\n\nThe default is false. Notice that for this to work, the \"offset\" field under \"SERVER\" must be greater than zero. ")
maxTargetPercentage         = input.float(title="Maximum target (%)", group="LINES", defval=5, minval=0, step=1, tooltip="How much ideal/max profit is expected in percentage. The default is 8%.\n\nThe price shown in the target line is calculated based on this number.\n\nFor example: TARGET (127.58) (8%)")
numberOfPercentageLines     = input.int(title="Number of target lines", group="LINES", defval=4, minval=0, step=1, tooltip="Indicates how many target percentage lines should be added into the chart. The default is 3.\n\nThe lines are created based on the \"Percentage unit\".\n\nFor example: 1.5%, 3%, 4.5%, etc.")
customLinePercentage        = input.price(title="Custom percentage", group="LINES", defval=0.5, tooltip="Use this line to quickly check the entry price value based on the inserted percentage.\n\nFor example: CUSTOM (178.19) (0.25%).")
stopPercentage              = input.float(title="Stop loss (%)", group="LINES", defval=2, minval=0, step=1, tooltip="How much can you afford to lose in percentage. The default is 2%.\n\nThe price shown in the stop line is calculated based on this number.\n\nFor example: STOP (53.25) (2%).")
liquidationPrice            = input.price(title="Liquidation price", group="LINES", defval=0, tooltip="[FUTURES] In case the asset reaches the price indicated here, there will be a loss of 100%. The default is 0.\n\nThe price shown in the liquidation line is calculated based on this number.\n\nFor example: LIQ (22.17) (100%).")
labelLeadingSpace           = input.int(15, title="Label leading space", minval=0, group="LINES", tooltip="Moves the line labels to the right of the current bar by this unit.")

// SERVER
isWebhookPayload            = input(title="JSON payload", group="SERVER", defval=false, tooltip="When checked, the alert body is formatted as JSON.\n\nThis supports a webhook-triggered trading bot scenario in which a server receives said body/payload as input.\n\nIt also displays a line (AQUA color) to represent the \"Percentage unit trigger\" (see the field below).")
authorizationKey            = input.string(title="Authorization key", group="SERVER", defval="", tooltip="Basic auth key.")
exchangeName                = input.string(title="Exchange name", group="SERVER", defval="Kucoin", tooltip="Kucoin, Binance, etc.\n\nNote: If this field is empty, the exchange name is retrieved by \"syminfo.prefix\".")
profitPercentageTrigger     = input(title="Percentage unit trigger", group="SERVER", defval=1.0, tooltip="\"Equals or greater than\" logic. If this value is the same as the \"Percentage unit\" (default), then every time an alert is sent, the server will act upon it. However, sometimes we want alerts (e.g. at 2%), but we only want to act when it reaches say 5% (or more). In this scenario, \"Profit Unit\" would be 2 and \"Profit Unit Trigger\" would be 5.")
offset                      = input.string(title="Offset", group="SERVER", defval="0.5", tooltip="How far the target price of the stop order will be from the current price.\n\nFor example, suppose the profit percentage is 1.0% and the offset is 0.75%; the stop order to be placed will be 0.25% of the current price (1.0% - 0.75%).")
cancelStopOrders            = input(title="Cancel untriggered stop orders", group="SERVER", defval=true, tooltip="When checked, all the untriggered stop orders for the contract symbol will be cancelled before placing a new one. Default is \"true\".")

// DEVELOPER
shouldShowDebugInfo         = input(title="Show debug info", group="DEVELOPER", defval=true, tooltip="Check this box to see the values of the main variables on the chart, below bars. This is for debugging purposes only.")

// The current symbol name without exchange prefix. E.g.: "BTCUSDT".
var currentSymbol = syminfo.ticker

// The latest price of the symbol.
currentPrice = close

// The current profit, in percentage.
profitCurrent = 0.0

// The value for which the last alert was sent.
varip lastAlertAt = 0.0

// The value for which the next alert is to be sent.
varip nextAlertAt = profitUnit

// String array of metadata of the target symbol.
// E.g.: [exchangeName, contractSymbol, tickerSize]
metadata = array.new_string(0)

// For debugging purposes: will an alert be sent?
willAlert = false

////============================================================================
// This section supports server-side operations triggered by webhooks.
// Ignore it if you don't plan to use this script for trading automation.
////============================================================================

// Initializes an array of String metadata of the target symbol.
//
// The array contains the following information:
// - Exchange name (syminfo.prefix)
// - Contract name (symbol contract)
// - Minimum price increment (ticker size)
//
// Sources:
// - https://futures.kucoin.com/contract/detail
// Other exchanges/info are going to be added as needed.
symbol_metadata() =>

    // Exchange name.
    array.push(metadata, syminfo.prefix)

    ////========================================================================
    // BINANCE
    ////========================================================================
    if syminfo.prefix == "BINANCE"

        // LUNC
        if (targetSymbol == 'LUNCUSDT')
            array.push(metadata, "LUNCUSDT")
            array.push(metadata, "0.00000001")

        else
            array.push(metadata, "")
            array.push(metadata, "")

    ////========================================================================
    // KUCOIN
    ////========================================================================
    else if syminfo.prefix == "KUCOIN"

        // ADA
        if (targetSymbol == 'ADAUSDT')
            if isKucoinSpot
                array.push(metadata, "ADA-USDT")
            else
                array.push(metadata, "ADAUSDTM")
            array.push(metadata, "0.00001")

        // APT
        if (targetSymbol == 'APTUSDT')
            if isKucoinSpot
                array.push(metadata, "APT-USDT")
            else
                array.push(metadata, "APTUSDTM")
            array.push(metadata, "0.001")

        // AVAX
        if (targetSymbol == 'AVAXUSDT')
            if isKucoinSpot
                array.push(metadata, "AVAX-USDT")
            else
                array.push(metadata, "AVAXUSDTM")
            array.push(metadata, "0.01")

        // BTC
        if (targetSymbol == 'BTCUSDT')
            if isKucoinSpot
                array.push(metadata, "BTC-USDT")
            else
                array.push(metadata, "XBTUSDTM")
            array.push(metadata, "1")

        // BTC
        else if (targetSymbol == 'CAKEUSDT')
            if isKucoinSpot
                array.push(metadata, "CAKE-USDT")
            else
                array.push(metadata, "CAKEUSDTM")
            array.push(metadata, "0.01")

        // ETH
        else if (targetSymbol == 'ETHUSDT')
            if isKucoinSpot
                array.push(metadata, "ETH-USDT")
            else
                array.push(metadata, "ETHUSDTM")
            array.push(metadata, "0.05")

        // FTM
        else if (targetSymbol == 'FTMUSDT')
            if isKucoinSpot
                array.push(metadata, "FTM-USDT")
            else
                array.push(metadata, "FTMUSDTM")
            array.push(metadata, "0.0001")

        // GALA
        else if (targetSymbol == 'GALAUSDT')
            if isKucoinSpot
                array.push(metadata, "GALA-USDT")
            else
                array.push(metadata, "GALAUSDTM")
            array.push(metadata, "0.00001")

        // GAS
        else if (targetSymbol == 'GASUSDT')
            if isKucoinSpot
                array.push(metadata, "GAS-USDT")
            else
                array.push(metadata, "GASUSDTM")
            array.push(metadata, "0.01")

        // GMT
        else if (targetSymbol == 'GMTUSDT')
            if isKucoinSpot
                array.push(metadata, "GMT-USDT")
            else
                array.push(metadata, "GMTUSDTM")
            array.push(metadata, "0.0001")

        // LTC
        else if (targetSymbol == 'LTCUSDT')
            if isKucoinSpot
                array.push(metadata, "LTC-USDT")
            else
                array.push(metadata, "LTCUSDTM")
            array.push(metadata, "0.01")

        // LDO
        else if (targetSymbol == 'LDOUSDT')
            if isKucoinSpot
                array.push(metadata, "LDO-USDT")
            else
                array.push(metadata, "LDOUSDTM")
            array.push(metadata, "0.001")

        // LUNC
        else if (targetSymbol == 'LUNCUSDT')
            if isKucoinSpot
                array.push(metadata, "LUNC-USDT")
            else
                array.push(metadata, "LUNCUSDTM")
            array.push(metadata, "0.000000001")

        // MANA
        else if (targetSymbol == 'MANAUSDT')
            if isKucoinSpot
                array.push(metadata, "MANA-USDT")
            else
                array.push(metadata, "MANAUSDTM")
            array.push(metadata, "0.001")

        // NEAR
        else if (targetSymbol == 'NEARUSDT')
            if isKucoinSpot
                array.push(metadata, "NEAR-USDT")
            else
                array.push(metadata, "NEARUSDTM")
            array.push(metadata, "0.01")

        // PERP
        else if (targetSymbol == 'PERPUSDT')
            if isKucoinSpot
                array.push(metadata, "PERP-USDT")
            else
                array.push(metadata, "PERPUSDTM")
            array.push(metadata, "0.001")

        // OCEAN
        else if (targetSymbol == 'OCEANUSDT')
            if isKucoinSpot
                array.push(metadata, "OCEAN-USDT")
            else
                array.push(metadata, "OCEANUSDTM")
            array.push(metadata, "0.0001")

        // ONE
        else if (targetSymbol == 'ONEUSDT')
            if isKucoinSpot
                array.push(metadata, "ONE-USDT")
            else
                array.push(metadata, "ONEUSDTM")
            array.push(metadata, "0.00001")

        // STORJ
        else if (targetSymbol == 'STORJUSDT')
            if isKucoinSpot
                array.push(metadata, "STORJ-USDT")
            else
                array.push(metadata, "STORJUSDTM")
            array.push(metadata, "0.0001")

        // SOL
        else if (targetSymbol == 'SOLUSDT')
            if isKucoinSpot
                array.push(metadata, "SOL-USDT")
            else
                array.push(metadata, "SOLUSDTM")
            array.push(metadata, "0.001")

        // WAVES
        else if (targetSymbol == 'WAVESUSDT')
            if isKucoinSpot
                array.push(metadata, "WAVES-USDT")
            else
                array.push(metadata, "WAVESUSDTM")
            array.push(metadata, "0.001")

        // XMR
        else if (targetSymbol == 'XMRUSDT')
            if isKucoinSpot
                array.push(metadata, "XMR-USDT")
            else
                array.push(metadata, "XMRUSDTM")
            array.push(metadata, "0.01")

        else
            array.push(metadata, "")
            array.push(metadata, "")

    ////========================================================================
    // UNKNOWN
    ////========================================================================
    else
        // No available metadata for the current exchange. In this scenario,
        // populate the array with empty values, in order to avoid breaking up
        // the alert logic. (Pine would throw a "calculation error" and the
        // alert would be stopped -- so instead it's much better to gracefully
        // fail on the server side).
        array.push(metadata, "")
        array.push(metadata, "")
symbol_metadata()

////============================================================================
// End of server-side support.
////============================================================================

// Consider only:
// - The target symbol
// - Positive entry prices
// - Time *after* the purchase
if (targetSymbol == currentSymbol) and (entryPrice > 0) and (time >= entryDate)

    // Current profit percentage logic.
    // Long: profit is *positive* when price goes *up*
    // Short: profit is *positive* when price goes *down*
    profitCurrent := isLong ? ((currentPrice * 100) / entryPrice) : ((entryPrice * 100) / currentPrice)
    profitCurrent := profitCurrent - 100

    // Profit label creation.
    var profitLabel         = label.new(na, na, color=color.white, textcolor=color.black, style=label.style_label_down, yloc=yloc.abovebar)
    entryPriceString        = str.tostring(entryPrice, '#.########')
    currentPriceString      = str.tostring(currentPrice, '#.########')
    emoji                   = (profitCurrent >= 0) ? "💰" : "💸"
    profitString            = str.tostring(profitCurrent, '#.##') + "% " + emoji

    // Currency profit logic.
    currencyProfitJSON      = "" // Goes into the JSON payload
    currentPriceJSON        = "" // Goes into the JSON payload
    currencyProfitString    = ""
    if shouldShowCurrencyProfit and size > 0
        currencyPaidTotal = entryPrice * size
        currencyProfitTotal = currentPrice * size
        currencyProfit = isLong ? currencyProfitTotal - currencyPaidTotal : currencyPaidTotal - currencyProfitTotal
        currencyProfitJSON := str.tostring(currencyProfit, '#.##') + " " + syminfo.currency
        currentPriceJSON := currentPriceString + " " + syminfo.currency
        currencyProfitString := "\n↓\n" + currencyProfitJSON

    // Final string.
    profitPathString        = (entryPriceString + "\n↓\n" + currentPriceString + "\n↓\n" + profitString + currencyProfitString)

    // Profit label update.
    label.set_text(profitLabel, profitPathString)
    label.set_x(profitLabel, bar_index)
    label.set_y(profitLabel, currentPrice)

    // Debugging label creation.
    var debuggingLabel = label.new(na, na, color=color.new(color.blue, transp=100), textcolor=color.white, textalign=text.align_left, style=label.style_label_up, yloc=yloc.belowbar)
    if shouldShowDebugInfo
        label.set_x(debuggingLabel, bar_index)
        label.set_y(debuggingLabel, currentPrice)

    // Alert logic.
    if (profitCurrent > lastAlertAt) and (((profitCurrent - lastAlertAt) > profitUnit) or ((profitCurrent > profitPercentageTrigger) and (lastAlertAt < profitPercentageTrigger)))
        lastAlertAt := profitCurrent
        // int(...) -> e.g. if thelast alert was say 2.73 and profit unit is 2, the next alert will be at 4 (not 4.73)
        nextAlertAt := int(lastAlertAt) + profitUnit

        // Alert creation.
        willAlert := true
        alertPrefixString = alertPrefix + " "
        profitPercentageString = str.tostring(profitCurrent, '#.##')
        totalProfitString = "Total profit so far: " + profitPercentageString + "% 💰"
        friendlyMessageString = alertPrefixString + totalProfitString

        if isWebhookPayload
            // This is where the JSON body that can be send to a server gets created.
            exchangeNameString = str.length(exchangeName) > 0 ? exchangeName : array.get(metadata, 0)
            alert("{\"profit\":\"" + currencyProfitJSON + "\", \"profitPercentage\":\"" + profitPercentageString + "\", \"profitPercentageTrigger\":\"" + str.tostring(profitPercentageTrigger) + "\", \"offset\":\"" + offset + "\", \"currentPrice\":\"" + currentPriceJSON + "\", \"entryPrice\":\"" + str.tostring(entryPrice) + "\", \"exchange\":\"" + exchangeNameString + "\", \"isLong\":\"" + str.tostring(isLong) + "\", \"size\":\"" + str.tostring(size) + "\", \"contractSymbol\":\"" + array.get(metadata, 1) + "\", \"ticker\":\"" + targetSymbol + "\", \"tickerSize\":\"" + array.get(metadata, 2) + "\", \"cancelStopOrders\":\"" + str.tostring(cancelStopOrders) + "\", \"isKucoinSpot\":\"" + str.tostring(isKucoinSpot) + "\", \"authorizationKey\":\"" + authorizationKey + "\"}", alert.freq_all)
        else
            // Friendly/simplified alert for regular users.
            alert(friendlyMessageString, alert.freq_all)

    // Lines logic.
    if shouldShowLines

        // Target line logic.
        if maxTargetPercentage > 0
            targetPercentage = (maxTargetPercentage / 100)
            targetChange = (entryPrice * targetPercentage)
            targetPrice = isLong ? entryPrice + targetChange : entryPrice - targetChange
            targetLabelPercentageText = " (" + str.tostring(maxTargetPercentage, '#.##') + "%)"
            targetLabelText = "TARGET" + " (" + str.tostring(targetPrice, '#.########') + ")" + targetLabelPercentageText
            var targetLabel = label.new(x=last_bar_index + labelLeadingSpace, y=targetPrice, text=targetLabelText, textcolor=color.green, style=label.style_none)
            var targetLine = line.new(time, targetPrice, time_close, targetPrice, xloc=xloc.bar_time, extend=extend.both, color=color.green, width=1)

        // Target percentage lines logic.
        if numberOfPercentageLines > 0
            var previousTargetPercentage = 0.0 // This seems to be required to avoid adding the same label/line multiple times.
            for i = 1 to numberOfPercentageLines
                targetPercentage = (profitUnit / 100) * i // E.g.: 0.015
                if targetPercentage > previousTargetPercentage
                    previousTargetPercentage := targetPercentage
                    targetPrice = (entryPrice * targetPercentage)
                    yPosition = isLong ? entryPrice + targetPrice : entryPrice - targetPrice
                    targetPercentageLabelText = str.tostring(targetPercentage * 100, '#.##') + '%' + " (" + str.tostring(yPosition, '#.########') + ")" // E.g. "1.5% (41716.5)"
                    label.new(x=last_bar_index + labelLeadingSpace, y=yPosition, text=targetPercentageLabelText, textcolor=color.orange, style=label.style_none)
                    line.new(time, yPosition, time_close, yPosition, xloc=xloc.bar_time, extend=extend.both, color=color.orange, style=line.style_dashed, width=1)

        // Custom % line logic.
        if customLinePercentage != 0
            customPercentage = (customLinePercentage / 100)
            customPercentageChange = (entryPrice * customPercentage)
            customPercentagePrice = isLong ? entryPrice + customPercentageChange : entryPrice - customPercentageChange
            customPercentageText = str.tostring(customLinePercentage, '#.##') + "%"
            customPercentageLabelText = customPercentageText + " (" + str.tostring(customPercentagePrice, '#.########') + ")"
            var customPercentageLabel = label.new(x=last_bar_index + labelLeadingSpace, y=customPercentagePrice, text=customPercentageLabelText, textcolor=color.aqua, style=label.style_none)
            var customPercentageLine = line.new(time, customPercentagePrice, time_close, customPercentagePrice, xloc=xloc.bar_time, extend=extend.both, color=color.aqua, width=1)

        // Entry line logic.
        var entryLabel = label.new(x=last_bar_index + labelLeadingSpace, y=entryPrice, text="ENTRY" + " (" + str.tostring(entryPrice, '#.########') + ")", textcolor=color.blue, style=label.style_none)
        var entryLine = line.new(time, entryPrice, time_close, entryPrice, xloc=xloc.bar_time, extend=extend.both, color=color.blue, width=1)

        // Stop line logic.
        if stopPercentage > 0
            stop = (stopPercentage / 100)
            stopChange = (entryPrice * stop)
            stopPrice = isLong ? entryPrice - stopChange : entryPrice + stopChange
            stopLabelPercentageText = " (-" + str.tostring(stopPercentage, '#.##') + "%)"
            stopLabelText = "STOP" + " (" + str.tostring(stopPrice, '#.########') + ")" + stopLabelPercentageText
            var stopLabel = label.new(x=last_bar_index + labelLeadingSpace, y=stopPrice, text=stopLabelText, textcolor=color.red, style=label.style_none)
            var stopLine = line.new(time, stopPrice, time_close, stopPrice, xloc=xloc.bar_time, extend=extend.both, color=color.red, width=1)

        // Liquidation line logic.
        if liquidationPrice > 0
            liquidationLabelText = "LIQ" + " (" + str.tostring(liquidationPrice, '#.########') + ")" + " (-100%)"
            var liquidationLabel = label.new(x=last_bar_index + labelLeadingSpace, y=liquidationPrice, text=liquidationLabelText, textcolor=color.white, style=label.style_none)
            var liquidationLine = line.new(time, liquidationPrice, time_close, liquidationPrice, xloc=xloc.bar_time, extend=extend.both, color=color.white, width=1)

        // Current stop-limit line logic.
        if lastAlertAt > 0 and lastAlertAt >= profitPercentageTrigger
            currentStopLimitPercentage = (lastAlertAt - str.tonumber(offset)) / 100 // E.g.: 0.0075
            currentStopLimitPrice = (entryPrice * currentStopLimitPercentage)
            currentStopLimitYPosition = isLong ? entryPrice + currentStopLimitPrice : entryPrice - currentStopLimitPrice
            currentStopLimitPercentageLabelText = str.tostring(currentStopLimitPercentage * 100, '#.##') + '%' + " (" + str.tostring(currentStopLimitYPosition, '#.########') + ")" // E.g. "0.75% (31289.37)"
            // Create transparent label.
            var currentStopLimitLabel = label.new(x=last_bar_index + labelLeadingSpace, y=currentStopLimitYPosition, text=currentStopLimitPercentageLabelText, textcolor=color.new(color.yellow, transp=100), style=label.style_none)
            // Create transparent line.
            var currentStopLimitLine = line.new(time, currentStopLimitYPosition, time_close, currentStopLimitYPosition, xloc=xloc.bar_time, extend=extend.both, color=color.new(color.yellow, transp=100), style=line.style_dashed, width=1)
            if shouldShowStopLimitLines
                // Update/show label.
                label.set_text(currentStopLimitLabel, currentStopLimitPercentageLabelText)
                label.set_y(currentStopLimitLabel, currentStopLimitYPosition)
                label.set_textcolor(currentStopLimitLabel, color.new(color.yellow, transp=0))
                // Update/show line.
                line.set_y1(currentStopLimitLine, currentStopLimitYPosition)
                line.set_y2(currentStopLimitLine, currentStopLimitYPosition)
                line.set_color(currentStopLimitLine, color.new(color.yellow, transp=0))

        // Next stop-limit line logic.
        nextStopLimitRoot       = (lastAlertAt < profitPercentageTrigger) ? profitPercentageTrigger : nextAlertAt
        nextStopLimitPercentage = (nextStopLimitRoot - str.tonumber(offset)) / 100 // E.g.: 0.0075
        nextStopLimitPrice = (entryPrice * nextStopLimitPercentage)
        nextStopLimitYPosition = isLong ? entryPrice + nextStopLimitPrice : entryPrice - nextStopLimitPrice
        nextStopLimitPercentageLabelText = str.tostring(nextStopLimitPercentage * 100, '#.##') + '%' + " (" + str.tostring(nextStopLimitYPosition, '#.########') + ")" // E.g. "0.75% (31289.37)"
        // Create transparent label.
        var nextStopLimitLabel = label.new(x=last_bar_index + labelLeadingSpace, y=nextStopLimitYPosition, text=nextStopLimitPercentageLabelText, textcolor=color.new(color.gray, transp=100), style=label.style_none)
        // Create transparent line.
        var nextStopLimitLine = line.new(time, nextStopLimitYPosition, time_close, nextStopLimitYPosition, xloc=xloc.bar_time, extend=extend.both, color=color.new(color.gray, transp=100), style=line.style_dashed, width=1)
        if shouldShowStopLimitLines
            // Update/show label.
            label.set_text(nextStopLimitLabel, nextStopLimitPercentageLabelText)
            label.set_y(nextStopLimitLabel, nextStopLimitYPosition)
            label.set_textcolor(nextStopLimitLabel, color.new(color.gray, transp=0))
            // Update/show line.
            line.set_y1(nextStopLimitLine, nextStopLimitYPosition)
            line.set_y2(nextStopLimitLine, nextStopLimitYPosition)
            line.set_color(nextStopLimitLine, color.new(color.gray, transp=0))

        // Server-related-lines logic.
        if isWebhookPayload

            // Percentage-trigger line logic.
            percentageTriggerPercentage = (profitPercentageTrigger / 100) // E.g.: 0.015
            percentageTriggerTargetPrice = (entryPrice * percentageTriggerPercentage)
            percentageTriggerLineYPosition = isLong ? entryPrice + percentageTriggerTargetPrice : entryPrice - percentageTriggerTargetPrice
            percentageTriggerLabelText = str.tostring(percentageTriggerPercentage * 100, '#.##') + '%' + " (" + str.tostring(percentageTriggerLineYPosition, '#.########') + ")" // E.g. "1.5% (41716.5)"
            var percentageTriggerLineLabel = label.new(x=last_bar_index + labelLeadingSpace, y=percentageTriggerLineYPosition, text=percentageTriggerLabelText, textcolor=color.lime, style=label.style_none)
            var percentageTriggerLine = line.new(time, percentageTriggerLineYPosition, time_close, percentageTriggerLineYPosition, xloc=xloc.bar_time, extend=extend.both, color=color.lime, style=line.style_dashed, width=1)

    // Debugging logic.
    profitUnitString            = "Profit unit alert: " + str.tostring(profitUnit, '#.##')
    profitUnitTriggerString     = "Profit unit trigger: " + str.tostring(profitPercentageTrigger, '#.##')
    offsetString                = "Offset: " + offset

    lastAlertAtString           = "Last alert at: " + str.tostring(lastAlertAt, '#.##')
    currentStopLimit            = (lastAlertAt >= profitPercentageTrigger) ? (lastAlertAt - str.tonumber(offset)) : 0
    currentStopLimitString      = "Current stop-limit at: " + str.tostring(currentStopLimit, '#.##')

    nextAlertAtTriggerString    = (nextAlertAt > profitPercentageTrigger) and (lastAlertAt < profitPercentageTrigger) ? " (and " + str.tostring(profitPercentageTrigger, '#.##') + ")" : ""
    nextAlertAtString           = "Next alert at: " + str.tostring(nextAlertAt, '#.##') + nextAlertAtTriggerString
    nextStopLimitRoot           = (lastAlertAt < profitPercentageTrigger) ? profitPercentageTrigger : nextAlertAt
    nextStopLimit               = nextStopLimitRoot - str.tonumber(offset)
    nextStopLimitString         = "Next stop-limit at: " + str.tostring(nextStopLimit, '#.##')
    willAlertString             = "Will alert: " + str.tostring(willAlert)

    // Debugging logic - metadata.
    currentExchangeString       = "Current exchange: " + str.upper(exchangeName)
    metadataExchangeString      = str.length(array.get(metadata, 0)) > 0 ? array.get(metadata, 0) : "⚠️"
    isExchangeMatchingWarning   = str.upper(exchangeName) == metadataExchangeString ? "" : " ⚠️"
    metadataIsKucoinSpotString  = "Kucoin spot: " + str.upper(str.tostring(isKucoinSpot))
    metadataExchangeFinalString = "Metadata exchange: " + (str.length(array.get(metadata, 0)) > 0 ? array.get(metadata, 0) : "⚠️") + isExchangeMatchingWarning
    metadataContractString      = "Contract: " + (str.length(array.get(metadata, 1)) > 0 ? array.get(metadata, 1) : "⚠️")
    metadataTickerString        = "Ticker: " + (str.length(array.get(metadata, 2)) > 0 ? array.get(metadata, 2) : "⚠️")
    metadataString              = "\nMETADATA\n" + metadataIsKucoinSpotString + "\n" + currentExchangeString + "\n" + metadataExchangeFinalString + "\n" + metadataContractString + "\n" + metadataTickerString

    // Debugging logic - label.
    label.set_text(debuggingLabel, currentSymbol + "\n" + profitUnitString + "\n" + profitUnitTriggerString + "\n" + offsetString + "\n\n" + lastAlertAtString + "\n" + currentStopLimitString + "\n\n" + nextAlertAtString + "\n" + nextStopLimitString + "\n" + willAlertString + "\n" + metadataString)
    label.set_color(debuggingLabel, color=color.new(color.blue, transp=0))
    willAlert := false

// Adds the tracker on the chart.
plot(na, color=color.new(color=color.white, transp=100))