Liquidation_lines
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © djmad
// my_current_verion=158.0

//@version=5
library("Liquidationline",overlay = true)
/////////////////////////////////
// this libary helps you drawing standard liquidationlines
/////////////////////////////////

export type Liquidationline
    int     creationtime        = na
    int     stoptime            = na
    float   price               = na
    float   leverage            = na
    float   maintainance        = na
    bool    line_active         = na
    color   line_color          = na
    int     line_thickness      = na
    string  line_style          = na
    string  line_direction      = na
    bool    line_finished       = na
    bool    text_active         = na
    string  text_size           = na
    color   text_color          = na

export f_getbartime() =>
// this function delivers the time per bar in unixtime
    var int _timeperbar = na
    var int firstbartime = 0, var int lastbartime = 0
    var bool locked1 = false 
    var bool locked2 = false 
    var bool locked3 = false
    var int counter = 0
    var int difftime = 0

    if locked1 == false
        firstbartime := time
        counter := 0
        locked1 := true
    if counter == 100 and locked2 == false
        lastbartime := time
        locked2 := true
    if locked3 == false and locked2 == true
        difftime := lastbartime - firstbartime
        _timeperbar := difftime / counter
        locked3 := true
    if locked3 != true
        counter += 1
    _timeperbar

export f_calculateLeverage(float _leverage, float _maintainance, float _value, string _direction) =>
//f_calculateLeverage - calculates the leverage based on inputs _leverage, _maintainance, _value, and _direction
    maintainance_f = (100 - _maintainance)/10000

    if _direction == "short"
        (_leverage * _value)/(_leverage - 1 + (maintainance_f * _leverage))
    else if _direction == "long"
        (_leverage * _value)/(_leverage + 1 - (maintainance_f * _leverage))

export f_liqline_update(Liquidationline [] _Liqui_Line, bool _killonlowhigh, int _minlength = -1, float _timeperbar = 0.00000000001) =>
//This code updates the properties of Liquidationline objects stored in the _Liqui_Line array. 
//The properties being updated include stoptime, line_finished, and price. 
//The code first checks the line direction of each object and then updates the values accordingly.
// If the line direction is "long" and the line has not been finished and the low is less than the leverage 
//value calculated using f_calculateLeverage or if the close is less than the leverage value, then the line is 
//marked as finished and the stoptime is set to the current time. If the line direction is "short" and the 
//line has not been finished and the high is greater than the leverage value calculated using f_calculateLeverage 
//or if the close is greater than the leverage value, then the line is marked as finished and the stoptime is set to the current time.
    var string getA_line_direction = na
    var float getA_price = na
    var float getA_leverage = na
    var float getA_maintainance = na
    var bool getA_line_finished = na


    if array.size(_Liqui_Line) > 0
        for i = array.size(_Liqui_Line)-1 to 0
            getA     = array.get(_Liqui_Line, i)

            //capturing double gets
            getA_line_direction := getA.line_direction
            getA_price := getA.price
            getA_leverage := getA.leverage
            getA_maintainance := getA.maintainance
            getA_line_finished := getA.line_finished

            if getA_line_direction == "long" //checking if line is liquidated
                if getA.line_finished == false and 
                 (_killonlowhigh ? 
                 (low < f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)):
                 (close < f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)))

                    getA.line_finished := true
                    getA.stoptime := time
                    array.set(_Liqui_Line, i, getA)

                else if getA.line_finished == false //no: prolong the line one more bar
                    getA.stoptime := time
                    array.set(_Liqui_Line, i, getA)

            if getA_line_direction == "short" //checking if line is liquidated
                if getA.line_finished == false and 
                 (_killonlowhigh ? 
                 (high > f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)):
                 (close > f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)))

                    getA.line_finished := true
                    getA.stoptime := time
                    array.set(_Liqui_Line, i, getA)
                else if getA.line_finished == false //no: prolong the line one more bar
                    getA.stoptime := time
                    array.set(_Liqui_Line, i, getA)

            //Check if killed line is to short for drawing, when yes remove it from the array
            //this does repaint, but it will give bigger relevant history
            if _minlength != -1      //dont waste time on gets if not relevant
                if getA_line_finished == true and
                 ((getA.stoptime - getA.creationtime) / _timeperbar)  <= _minlength
                    array.remove(_Liqui_Line, i)
  
    _Liqui_Line
    

export f_liqline_draw(Liquidationline [] _Liqui_Line, bool _priceorliq) =>
//The function f_drawfield takes an array of Liquidationline objects as input and uses the elements 
//in the array to draw lines on a chart. The line properties such as color, style, and thickness are 
//taken from the Liquidationline object and the line start and end points are calculated based on the 
//creationtime, stoptime, price, leverage, and maintainance values in the Liquidationline object. 
//The f_calculateLeverage function is called to help in calculating the line endpoints.
    var string getA_line_direction = na
    var float getA_price = na
    var float getA_leverage = na
    var float getA_maintainance = na

    if (barstate.isrealtime or barstate.islast) and array.size(_Liqui_Line) > 0
        if array.size(_Liqui_Line) > 0
            for i = 0 to array.size(_Liqui_Line)-1
                getA     = array.get(_Liqui_Line, i)

                getA_line_direction := getA.line_direction
                getA_price := getA.price
                getA_leverage := getA.leverage
                getA_maintainance := getA.maintainance

                line.new(
                 x1             =   getA.creationtime               , 
                 y1             =   _priceorliq ? getA_price : f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)                     ,
                 x2             =   getA.stoptime                   ,
                 y2             =   _priceorliq ? getA_price : f_calculateLeverage(getA_leverage, getA_maintainance, getA_price, getA_line_direction)                     ,  
                 xloc           =   xloc.bar_time                   , 
                 extend         =   extend.none                     , 
                 color          =   getA.line_color                 , 
                 style          =   getA.line_style                 , 
                 width          =   getA.line_thickness             
                 )

export f_liqline_add(Liquidationline [] _Liqui_Line, Liquidationline linetoadd, int _limit = 250) =>
//This code is defining a function f_liqline_add which takes in parameters dir (direction), col (color), thickness, and 
//_Liqui_Line (an array of Liquidationline). The function creates a new instance of Liquidationline class with properties 
//such as creation time, stop time, price, leverage, and line color, thickness, style, direction. 
//The new instance is then added to the beginning of the _Liqui_Line array. 
//If the size of the _Liqui_Line array is greater than the limit, the last element of the array is popped.
//limit sets the maximum lines in the array, no linedelete as they will pop on maximum themself
    array.unshift(_Liqui_Line, linetoadd)
    if array.size(_Liqui_Line) > _limit
        array.pop(_Liqui_Line)
    _Liqui_Line


////////////////// Indicator implementation sample
////////////////// Indicator implementation sample
////////////////// Indicator implementation sample
float maintainance      =   input.float(50, "Exchange margin maintainance %", minval=0, maxval=100 ,group='leverage',step = 1)
float f_leverage        =   input.float(25, "leverage", minval=0, maxval=100 ,group='leverage',step = 1)
bool priceorliq         =   input.bool(false, "Position or Liq", group='leverage')

col_long                =   input(color.rgb(76, 175, 79, 50))
col_short               =   input(color.rgb(255, 82, 82, 50))
int thinkness           =   input.int(1,"Thickness of lines", minval = -1)
bool killonlowhigh      =   input.bool(true, "Kill on LOW/HIGH", tooltip = "how is a relevant liquidation handled, by high/low or by the close value")
int maximumlines        =   input.int(25,"Maximum ammount of lines", tooltip = "if u convert this script to a indicator set max_line_count to 500 and this variable here to 250")
int minimumlength       =   input.int(10,"Minimum Length of liquidated lines", minval = -1, tooltip = "set this parameter to disable the small line cleanup, as longer the minimum length as more historic lines will remain")

float factor            =   input.float(1.03,"minimum shrink/raise of the close for new line draw",step = 0.01, minval = 1.0, group='Indicator settings')
string mtf_lvl          =   input.timeframe("",   title="Custom TF",group='Indicator settings')
bool Show_L1            =   input.bool(false, "Show L1 Bollingers",group='Indicator settings')
bool Show_L2            =   input.bool(true, "Show L2 Bollingers",group='Indicator settings')
bool Show_L3            =   input.bool(true, "Show L3 Bollingers",group='Indicator settings')


var bool LOCK = false
var float lastprice = na
var Liquidationline[] Liqui_Line_L    = array.new <Liquidationline>()
var Liquidationline[] Liqui_Line_S    = array.new <Liquidationline>()
var float timeperbar = 0.0000001

set_l = Liquidationline.new(
 creationtime=          time, 
 stoptime =             time,
 price=                 close, 
 leverage=              f_leverage,
 maintainance=          maintainance,
 line_active=           true,
 line_color=            color.rgb(33, 191, 67), 
 line_thickness=        1,
 line_style=            line.style_solid,
 line_direction=        "long",
 line_finished=         false,
 text_active=           true,
 text_size=             size.normal,
 text_color=            color.white
 )

set_s = Liquidationline.new(
 creationtime=          time, 
 stoptime =             time,
 price=                 close, 
 leverage=              f_leverage,
 maintainance=          maintainance,
 line_active=           true,
 line_color=            color.rgb(191, 33, 33), 
 line_thickness=        1,
 line_style=            line.style_solid,
 line_direction=        "short",
 line_finished=         false,
 text_active=           true,
 text_size=             size.normal,
 text_color=            color.white
 )

//run the timeperbar only as long till relevant data is aviable ( 100 bars to get the value, incl. 100 bars reserve)
if bar_index < 200 
    timeperbar := f_getbartime()

//this blocks recurring repositions at the same privelevel
if close > lastprice * factor or close < lastprice / factor 
    LOCK := false



[a1,b1,c1] = request.security(syminfo.tickerid,mtf_lvl,ta.bb(close,20,1), barmerge.gaps_off, barmerge.lookahead_off)
[a2,b2,c2] = request.security(syminfo.tickerid,mtf_lvl,ta.bb(close,20,2), barmerge.gaps_off, barmerge.lookahead_off)
[a3,b3,c3] = request.security(syminfo.tickerid,mtf_lvl,ta.bb(close,20,3), barmerge.gaps_off, barmerge.lookahead_off)

//using bollingerbands to create some signals, you can enter here whatever you want signal like rsi, macdcross,....
if ((Show_L1? ta.crossunder(close,c1) or ta.crossover(close,a1) :false) or
 (Show_L2? ta.crossunder(close,c2) or ta.crossover(close,a2) :false) or
 (Show_L3? ta.crossunder(close,c3) or ta.crossover(close,a3) :false)) and LOCK == false
    LOCK := true
    lastprice := close
    f_liqline_add(_Liqui_Line = Liqui_Line_L, linetoadd = set_l,_limit = maximumlines)
    f_liqline_add(_Liqui_Line = Liqui_Line_S, linetoadd = set_s,_limit = maximumlines)

//drawing
f_liqline_draw(_Liqui_Line = Liqui_Line_L, _priceorliq = priceorliq)
f_liqline_draw(_Liqui_Line = Liqui_Line_S, _priceorliq = priceorliq)
//prolonging lines, liquidations and cleaning up the short lines smaller minimumlength
f_liqline_update(_Liqui_Line = Liqui_Line_L, _killonlowhigh = killonlowhigh, _minlength = minimumlength, _timeperbar = timeperbar)
f_liqline_update(_Liqui_Line = Liqui_Line_S, _killonlowhigh = killonlowhigh, _minlength = minimumlength, _timeperbar = timeperbar)
