Assassin's Grid
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Good-Vibe

//@version=5
indicator(title = "Assassin's Grid", shorttitle = "AAGG💥", overlay = true, max_labels_count = 500)

// 1.0 INPUTS

// 1.1 Grid parameters
WOption                     = input.string('Geometric (% Sell)', 'Width Type',                      ['Arithmetic (Price)', 'Geometric (% Sell)', 'Geometric (% Fall)'],                 group = "GRID")
Width                       = input.float(6.5,                   'Width Parameter',                 minval = 0, step = 0.5,                                                             group = "GRID")
ppPeriod                    = input.timeframe('W',               'Pivot Point Resolution',                                                                                              group = "GRID")
MARes                       = input.timeframe('240',             'EMA Resolution',                                                                                                      group = "GRID")
MALength                    = input.int(200,                     'MA Length',                       minval = 1,                                                                         group = "GRID")

// 1.2 Buy  
BuyType                     = input.string('Cash / n Buys +',    'Buy Type',                        ['Contracts', 'Cash', '% Cash', 'Cash / n Buys', 'Cash / n Buys +'],                group = "BUY")
BuyQ                        = input.float(10.0,                  'Contracts / Cash / % Cash',       minval = 0,                                                                         group = "BUY")
NBuysUp                     = input.int(6,                       'N Buys over MA',                  minval = 1, maxval = 30,                                                            group = "BUY")
NBuysDown                   = input.int(6,                       'N Buys under MA (Max.)',          minval = 1, maxval = 30,                                                            group = "BUY")
LastXtrades                 = input.int(2,                       'Buy all in last Trades',          minval = 0, maxval = 10,                                                            group = "BUY")

// 1.3 Sel  
SellType                    = input.string('Position / n Sells', 'Sell Type',                       ['Contracts', 'Cash', '% Position', 'Position / n Sells', 'Position / n Sells +'],  group = "SELL")
SellQ                       = input.float(5.0,                   'Contracts / Cash / % Position',   minval = 0,                                                                         group = "SELL")
NSellsUp                    = input.int(21,                      'N Sells over MA (Max.)',          minval = 1, maxval = 30,                                                            group = "SELL")
NSellsDown                  = input.int(3,                       'N Sells under MA',                minval = 1, maxval = 30,                                                            group = "SELL")
LossAllowed                 = input.string('Last buy',           'Loss Allowed',                    ['Never', 'Last buy', 'Always'],                                                    group = "SELL")

// 1.4 Trading  
TradingType                 = input.string('Spot',               'Trading Type',                    ['Spot', 'Margin'],                                                                 group = "TRADING")
InitialCapital              = input(10000.0,                     'Initial Capital',                                                                                                     group = "TRADING")
InitialContracts            = input.float(10.0,                  '% Initial Capital 1st Trade',     minval = 1, maxval = 100,                                                           group = "TRADING") / 100
CommissionValue             = input.float(0.1,                   '% Comission Value',               minval = 0, maxval = 100, step = 0.1,                                               group = "TRADING") / 100
MarginRate                  = input.float(10.0,                  '% Margin Rate',                   minval = 0, maxval = 100, step = 0.5,                                               group = "TRADING") / 100
OrdersType                  = input.string('Limit',              'Orders Type on Alerts',           ['Limit', 'Market'],                                                                group = "TRADING")
StartDate                   = timestamp(                         '01 Jan 1990 01:00 +000')
testPeriodStart             = input.time(StartDate,              'Start of Trading',                                                                                                    group = "TRADING")
LabelSizeInput              = input.string('Tiny',               'Labels Size',                     ['Auto', 'Tiny', 'Small', 'Normal', 'Large', 'Huge'],                               group = "PLOTTING")
TableSizeInput              = input.string('Tiny',               'Table Size',                      ['Auto', 'Tiny', 'Small', 'Normal', 'Large', 'Huge'],                               group = "PLOTTING")
ShowGrid                    = input(true,                        'Level Grid',                                                                                                          group = "PLOTTING")
ShowPanel                   = input(true,                        'Information Panel',                                                                                                   group = "PLOTTING")
ShowMarginCall              = input(false,                       'Margin Call Price',                                                                                                   group = "PLOTTING")
Account                     = input.string("OKEX",               'Input Account')

// 2.0 VARIABLES

// 2.1 Grid levels on buys
var float _ldown            = na
bool _pb                    = false
bool _buy                   = false

// 2.2 Grid levels on sells
var float _lup              = na
bool _ps                    = false
bool _sell                  = false

// 2.3 First Buy
CloseFirstBar               = ta.valuewhen(bar_index == 0, open, 0)
TimeFirstBar                = ta.valuewhen(bar_index == 0, time, 0)
CloseStart                  = ta.valuewhen(time <= testPeriodStart, open, 0)
FirstClose                  = testPeriodStart > TimeFirstBar ? CloseStart : CloseFirstBar
TimeFirstClose              = testPeriodStart > TimeFirstBar ? testPeriodStart : TimeFirstBar

// 2.4 Buy and Sell prices 
var float FinalBuyPrice     = na
var float FinalSellPrice    = na
var float FinalOpenPrice    = na
var float BuyLimitPrice     = na
var float SellLimitPrice    = na

// 2.5 Number of trades
var int nBuys               = na
nBuys                       := nz(nBuys[1])
var int nSells              = na
nSells                      := nz(nSells[1])
var int NBuys               = NBuysDown
var int NSells              = NSellsDown

// 2.6 Quantities
var float BuyQuantity       = 0
var float BuyAmount         = 0
var float SellQuantity      = 0
var float SellAmount        = 0
var float Commission        = 0
var float Gains             = 0
var float Losses            = 0

// 2.7 Position calculation
var float PositionCashS     = 0
var float PositionCash      = 0
var float PositionSizeS     = 0
var float PositionSize      = 0
var int BarIndex            = 0

// 2.8 Average Price Calculation
var float AvgPrice0         = 0
var float AvgPrice          = 0
var float hl2Bar            = 0

// 2.9 Backtest information
var float Balance           = InitialCapital
var float Equity            = 0
var float RealizedPnL       = 0
var float PRealizedPnL      = 0
var float Floating          = 0
var float PFloating         = 0
var float URealizedPnL      = 0
var float PURealizedPnL     = 0
var float Cash              = InitialCapital
var float Margin            = 0
var float BuyAndHold        = 0
var float PBuyAndHold       = 0
var float CLeverage         = 0
var float MarginCallPrice   = 0
var bool  MarginCall        = false
var float ProfitFactor      = 0
var int TradingTime         = 0

// 2.10 Fibonacci Pivots level calculation
var float PP                = open

// 2.11 Information panel
label labelBalance          = na

// 2.12 Analyzing when the period changes
bool PeriodChange           = false

// 2.13 Grid with arrays
aGrid                       = array.new_float(30)
aDown                       = array.new_float(30)
aUp                         = array.new_float(30)
aPb                         = array.new_bool(30)
aBuy                        = array.new_bool(30)
aPs                         = array.new_bool(30)
aSell                       = array.new_bool(30)

// 2.14 Labels size
fTextSize(_SizeInput)=>
    if _SizeInput == 'Auto'
        size.auto
    else if _SizeInput == 'Tiny'
        size.tiny
    else if _SizeInput == 'Small'
        size.small
    else if _SizeInput == 'Normal'
        size.normal
    else if _SizeInput == 'Normal'
        size.normal
    else if _SizeInput == 'Large'
        size.large
    else if _SizeInput == 'Huge'
        size.huge

// 2.15 Variable reference
var float MaxFinalOpenPrice = open

// 2.16 Value of the MA
var float sMAValue          = na

// 3.0 GRID

// 3.1 Function to calculate the Width
fWidth(_Width) =>

    // 3.1.1 If price is constant
    if WOption == 'Arithmetic (Price)'
        math.round_to_mintick(_Width)

    // 3.1.2 If price is the Max % of the next Sell
    else if WOption == 'Geometric (% Sell)'
        if na(FinalOpenPrice)
            math.round_to_mintick((PP * (_Width / 100)))
        else
            math.round_to_mintick(MaxFinalOpenPrice * (_Width / 100))
    
    // 3.1.3 If price is a part of the % of the maximum fall
    else if WOption == 'Geometric (% Fall)'
        if na(FinalOpenPrice)
            math.round_to_mintick((PP * (_Width / 100)))
        else
            math.round_to_mintick(MaxFinalOpenPrice / NBuysDown * (_Width / 100))
        
// 3.2 Grid Calculation
fGrid(_Origin, _GridWidth, _Index) =>
    for _i = 1 to _Index
        array.insert(aGrid, _i, _Origin + (_GridWidth * (_i-1)))
        array.get(aGrid, _Index)

// 3.3.1  Origin from Rounded Pivot Points or last Sell
fDownOrigin(_GridWidth) =>

    if FinalSellPrice[1] <= PP
        if nSells < NSells
            if na(FinalBuyPrice[1])
                (math.floor(FinalSellPrice / _GridWidth) * _GridWidth) - _GridWidth
            else
                FinalBuyPrice[1]
                
        else if nSells == NSells
            (math.floor(PP / _GridWidth) * _GridWidth)
    
    else
        if na(FinalBuyPrice[1])
            math.floor(PP / _GridWidth) * _GridWidth
        else
            FinalBuyPrice[1]
        
// 3.3.2 Origin for sells from Rounded Position Price
fUpOrigin(_GridWidth) =>
    
    if na(FinalSellPrice[1])
        if LossAllowed == 'Never'
            math.ceil(math.max(AvgPrice0, AvgPrice, hl2Bar) / _GridWidth) * _GridWidth
            
        else if LossAllowed == 'Last buy'
            if nBuys == NBuys
                math.ceil(FinalBuyPrice / _GridWidth) * _GridWidth
            else
                math.ceil(math.max(AvgPrice0, AvgPrice, hl2Bar) / _GridWidth) * _GridWidth
                
        else if LossAllowed == 'Always'
            math.ceil(FinalBuyPrice / _GridWidth) * _GridWidth
                
    else
        FinalSellPrice[1]
    
// 4.0 FUNCTIONS

// 4.1 Function to sum factorial
fSum(_Num)=>
    (math.pow(_Num, 2) + _Num) / 2

// 4.2.1 Function when "Cash / n Buys" or "Position / n Sells"
fCaPo_N(_N, _n) =>
    1 / (_N - nz(_n))

// 4.2.2 Function when "Cash / n Buys +" or "Position / n Sells +"
fCaPo_Nplus(_OnSells, _N, _n) =>
    if TradingType == 'Spot' or _OnSells == 1
        (nz(_n)+1) / (fSum(_N) - fSum(nz(_n)))
    else
        (nz(_n)+1) / fSum(_N)
        
// 4.3 One of the correct ways to use security
f_security(_sym, _res, _src, _rep) => 
    request.security(_sym, _res, _src[not _rep and barstate.isrealtime ? 1 : 0])[_rep or barstate.isrealtime ? 0 : 1]

// 4.3.1 Pivot points
PP           := f_security(syminfo.tickerid, ppPeriod, hlc3, false)

// 4.3.2 Moving Average
MA          = ta.ema(close, MALength)
sMA         = f_security(syminfo.tickerid, MARes, MA, false)

// 4.4 Analyzing when the period changes
PeriodChange := ta.change(time(ppPeriod)) != 0

// 4.5.1 On Bullish trend, less Number of Buys and more amounts per trade;
// on Bearish more Number of Buys and less amounts per trade
// Max. number of buys
NBuys := if (BuyType == "Cash / n Buys" or BuyType == "Cash / n Buys +")
    if BuyLimitPrice >= sMAValue[1]
        NBuysUp
    else
        NBuysDown
else
    NBuysDown

// 4.5.2 On Bullish trend, more Number of Sells and less amounts per trade;
// on Bearish less Number of Sells and more amounts per trade
// Max. number of sells
NSells := if (SellType == "Position / n Sells" or SellType == "Position / n Sells +")
    if SellLimitPrice < sMAValue[1]
        NSellsDown
    else
        NSellsUp
else
    NSellsUp
    
// 5.0 ALERTS

// 5.1 Triggering alerts more than once in the same bar
fBuySell30Alerts(_nBuySell, _BuySellMessage) =>
    if _nBuySell == 0
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 1
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 2
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 3
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 4
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 5
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 6
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 7
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 8
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 9
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 10
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 11
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 12
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 13
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 14
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 15
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 16
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 17
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 18
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 19
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 20
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 21
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 22
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 23
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 24
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 25
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 26
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 27
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 28
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 29
        alert(_BuySellMessage,  alert.freq_once_per_bar)
    if _nBuySell == 30
        alert(_BuySellMessage,  alert.freq_once_per_bar)

// 5.2.1 Alert Function with syntax to buy
fAlertsOnBuys(_Account, _CancelBuy, _FinalBuyPrice, _nBuys, _nSells, _BuyLimitPrice, _SellLimitPrice) =>
    
    // Previous Messages
    var string _PrevBuyMessage          = na
    var string _PrevCancelSellMessage   = na
    var string _PrevSellMessage         = na
    var string _PrevCancelBuyMessage    = na
    var string _BuyMessage              = na
    var string _SellMessage             = na
    var string _BuyLimitOnSellMessage   = na
    var string _SellLimitOnBuyMessage   = na
    
    // _Symbol
    var string _Symbol                  = na
    _Symbol                             := " | a=" + _Account + " s=" + syminfo.basecurrency + "-" + syminfo.currency

    // Previous Messages
    _PrevCancelBuyMessage := _Symbol + " c=order b=buy | delay=1"
    if _CancelBuy == 1    
        _PrevBuyMessage := "BUY " + str.tostring(nz(_nBuys)) + " @ " + str.tostring(_FinalBuyPrice, '#.#####') + _PrevCancelBuyMessage + _Symbol + " b=buy q="
    else
        _PrevBuyMessage := "BUY " + str.tostring(nz(_nBuys)) + " @ " + str.tostring(_FinalBuyPrice, '#.#####') + _Symbol + " b=buy q="
        
    if nz(_nBuys) > 0
        _PrevCancelSellMessage := _Symbol + " c=order b=sell | delay=1"
    
    // Sell limit orders on buys
    if      SellType == "Contracts" and nz(_nBuys) > 0
        _SellLimitOnBuyMessage := _Symbol + " b=sell q=" + str.tostring(SellQ)                                      + " u=contracts t=limit fp="  + str.tostring(_SellLimitPrice)
    else if SellType == "Currency" and nz(_nBuys) > 0                        
        _SellLimitOnBuyMessage := _Symbol + " b=sell q=" + str.tostring(SellQ)                                      + " u=currency t=limit fp="   + str.tostring(_SellLimitPrice)
    else if SellType == "% Position" and nz(_nBuys) > 0                            
        _SellLimitOnBuyMessage := _Symbol + " b=sell q=" + str.tostring(SellQ)                                      + "% t=limit fp="             + str.tostring(_SellLimitPrice)
    else if SellType == "Position / n Sells" and nz(_nBuys) > 0     
        _SellLimitOnBuyMessage := _Symbol + " b=sell q=" + str.tostring(fCaPo_N(NSells, nz(_nSells)) * 100)         + "% t=limit fp="             + str.tostring(_SellLimitPrice)
    else if SellType == "Position / n Sells +" and nz(_nBuys) > 0
        _SellLimitOnBuyMessage := _Symbol + " b=sell q=" + str.tostring(fCaPo_Nplus(1, NSells, nz(_nSells)) * 100)  + "% t=limit fp="             + str.tostring(_SellLimitPrice)
        
    // Buy alerts according to inputs
    if      BuyType == "Contracts"
        if OrdersType == 'Limit'
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + " u=contracts t=limit fp="  + str.tostring(_BuyLimitPrice) + _PrevCancelSellMessage 
                                                                                                                                                                                 + _SellLimitOnBuyMessage
        else if OrdersType == 'Market' and nz(_nBuys) > 0
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + " u=contracts t=market"
        fBuySell30Alerts(nz(_nBuys), _BuyMessage)
                                  
    else if BuyType == "Cash"
        if OrdersType == 'Limit'
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + " u=currency t=limit fp="   + str.tostring(_BuyLimitPrice) + _PrevCancelSellMessage 
                                                                                                                                                                                 + _SellLimitOnBuyMessage
        else if OrdersType == 'Market' and nz(_nBuys) > 0
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + " u=currency t=market"
        fBuySell30Alerts(nz(_nBuys), _BuyMessage)
        
    else if BuyType == "% Cash"
        if OrdersType == 'Limit'
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + "% t=limit fp="             + str.tostring(_BuyLimitPrice) + _PrevCancelSellMessage 
                                                                                                                                                                                 + _SellLimitOnBuyMessage
        else if OrdersType == 'Market' and nz(_nBuys) > 0
            _BuyMessage := _PrevBuyMessage               + str.tostring(BuyQ)                                       + "% t=market"
        fBuySell30Alerts(nz(_nBuys), _BuyMessage)
    
    else if BuyType == "Cash / n Buys"
        if OrdersType == 'Limit'
            _BuyMessage := _PrevBuyMessage               + str.tostring(fCaPo_N(NBuys, nz(_nBuys)) * 100)           + "% t=limit fp="             + str.tostring(_BuyLimitPrice) + _PrevCancelSellMessage 
                                                                                                                                                                                 + _SellLimitOnBuyMessage
        else if OrdersType == 'Market' and nz(_nBuys) > 0
            _BuyMessage := _PrevBuyMessage               + str.tostring(fCaPo_N(NBuys+1, nz(_nBuys)) * 100)         + "% t=market"
        fBuySell30Alerts(nz(_nBuys), _BuyMessage)
    
    else if BuyType == "Cash / n Buys +"
        if OrdersType == 'Limit'
            _BuyMessage := _PrevBuyMessage               + str.tostring(fCaPo_Nplus(0, NBuys, nz(_nBuys)) * 100)    + "% t=limit fp="             + str.tostring(_BuyLimitPrice) + _PrevCancelSellMessage 
                                                                                                                                                                                 + _SellLimitOnBuyMessage
        else if OrdersType == 'Market' and nz(_nBuys) > 0
            _BuyMessage := _PrevBuyMessage               + str.tostring(fCaPo_Nplus(0, NBuys, nz(_nBuys)-1) * 100)  + "% t=market"
        fBuySell30Alerts(nz(_nBuys), _BuyMessage)

// 5.2.2 Alert Function with syntax to sell
fAlertsOnSells(_Account, _FinalSellPrice, _nBuys, _nSells, _BuyLimitPrice, _SellLimitPrice) =>
    
    // Previous Messages
    var string _PrevBuyMessage          = na
    var string _PrevCancelSellMessage   = na
    var string _PrevSellMessage         = na
    var string _PrevCancelBuyMessage    = na
    var string _BuyMessage              = na
    var string _SellMessage             = na
    var string _BuyLimitOnSellMessage   = na
    var string _SellLimitOnBuyMessage   = na 
    
    // Symbol    
    var string _Symbol                  = na
    _Symbol                             := " | a=" + _Account + " s=" + syminfo.basecurrency + "-" + syminfo.currency
    
    // Previous Messages
    _PrevSellMessage := "SELL " + str.tostring(nz(_nSells)) + " @ " + str.tostring(_FinalSellPrice, '#.#####') + _Symbol + " b=sell q="
    _PrevCancelBuyMessage := _Symbol + " c=order b=buy | delay=1"
    
    // Buy limit orders on sells
    if      BuyType == "Contracts"
        _BuyLimitOnSellMessage := _Symbol + " b=buy q=" + str.tostring(BuyQ)                                        + " u=contracts t=limit fp="  + str.tostring(_BuyLimitPrice)
    else if BuyType == "Currency"                    
        _BuyLimitOnSellMessage := _Symbol + " b=buy q=" + str.tostring(BuyQ)                                        + " u=currency t=limit fp="   + str.tostring(_BuyLimitPrice)
    else if BuyType == "% Cash"                  
        _BuyLimitOnSellMessage := _Symbol + " b=buy q=" + str.tostring(BuyQ)                                        + "% t=limit fp="             + str.tostring(_BuyLimitPrice)
    else if BuyType == "Cash / n Buys" 
        _BuyLimitOnSellMessage := _Symbol + " b=buy q=" + str.tostring(fCaPo_N(NBuys, nz(_nBuys)) * 100)            + "% t=limit fp="             + str.tostring(_BuyLimitPrice)
    else if BuyType == "Cash / n Buys +" 
        _BuyLimitOnSellMessage := _Symbol + " b=buy q=" + str.tostring(fCaPo_Nplus(0, NBuys, nz(_nBuys)) * 100)     + "% t=limit fp="             + str.tostring(_BuyLimitPrice)
     
    // Sell alerts according to input 
    if SellType == "Contracts" 
        if OrdersType == 'Limit' 
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + " u=contracts t=limit fp="  + str.tostring(_SellLimitPrice) + _PrevCancelBuyMessage 
                                                                                                                                                                                  + _BuyLimitOnSellMessage
        else if OrdersType == 'Market' and nz(_nSells) > 0
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + " u=contracts t=market"
        fBuySell30Alerts(nz(_nSells), _SellMessage)
                                 
    else if SellType == "Cash"
        if OrdersType == 'Limit'
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + " u=currency t=limit fp="   + str.tostring(_SellLimitPrice) + _PrevCancelBuyMessage 
                                                                                                                                                                                  + _BuyLimitOnSellMessage
        else if OrdersType == 'Market' and nz(_nSells) > 0
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + " u=currency t=market"
        fBuySell30Alerts(nz(_nSells), _SellMessage)
    
    else if SellType == "% Position"
        if OrdersType == 'Limit'
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + "% t=limit fp="             + str.tostring(_SellLimitPrice) + _PrevCancelBuyMessage 
                                                                                                                                                                                  + _BuyLimitOnSellMessage
        else if OrdersType == 'Market' and nz(_nSells) > 0
            _SellMessage := _PrevSellMessage            + str.tostring(SellQ)                                       + "% t=market"
        fBuySell30Alerts(nz(_nSells), _SellMessage)

    else if SellType == "Position / n Sells"
        if OrdersType == 'Limit'
            _SellMessage := _PrevSellMessage            + str.tostring(fCaPo_N(NSells, nz(_nSells)) * 100)          + "% t=limit fp="             + str.tostring(_SellLimitPrice) + _PrevCancelBuyMessage 
                                                                                                                                                                                  + _BuyLimitOnSellMessage
        else if OrdersType == 'Market' and nz(_nSells) > 0
            _SellMessage := _PrevSellMessage            + str.tostring(fCaPo_N(NSells+1, nz(_nSells)) * 100)        + "% t=market"
        fBuySell30Alerts(nz(_nSells), _SellMessage)

    else if SellType == "Position / n Sells +"
        if OrdersType == 'Limit'
            _SellMessage := _PrevSellMessage            + str.tostring(fCaPo_Nplus(1, NSells, nz(_nSells)) * 100)   + "% t=limit fp="             + str.tostring(_SellLimitPrice) + _PrevCancelBuyMessage 
                                                                                                                                                                                  + _BuyLimitOnSellMessage
        else if OrdersType == 'Market' and nz(_nSells) > 0
            _SellMessage := _PrevSellMessage            + str.tostring(fCaPo_Nplus(1, NSells, nz(_nSells)-1) * 100) + "% t=market"
        fBuySell30Alerts(nz(_nSells), _SellMessage)
                
// 6.0 TRADING

// 6.1. Start of trading
if time >= TimeFirstClose

    // 6.2. Final Trade Price, Average Price & Backtest
    for _i = 1 to 30
    
        // 6.2.1.1 Grid on Buys
        array.insert(aDown, _i, fGrid(fDownOrigin(fWidth(Width)), fWidth(-Width), _i))
        
        // 6.2.1.2 Crossing between price and levels of grid
        array.insert(aPb, _i, (((ta.cross(low, array.get(aDown, _i)) or ta.cross(high, array.get(aDown, _i))) and high >= array.get(aDown, _i)) or (open <= array.get(aDown, _i))) and low <= array.get(aDown, _i))
        
        // 6.2.1.3 Buy only when price is better
        array.insert(aBuy, _i, (na(FinalOpenPrice) or (NSells == nSells and array.get(aPb, _i)) or (array.get(aPb, _i) and array.get(aDown, _i) <= (FinalOpenPrice - fWidth(Width)))) and nBuys <= NBuys-1)
        
        // 6.2.2.1 Grid on Sells
        array.insert(aUp, _i, fGrid(fUpOrigin(fWidth(Width)), fWidth(Width), _i))
    
        // 6.2.2.2 Crossing between price and levels of grid
        array.insert(aPs, _i, (((ta.cross(low, array.get(aUp, _i)) or ta.cross(high, array.get(aUp, _i))) and low <= array.get(aUp, _i)) or (open >= array.get(aUp, _i))) and high >= array.get(aUp, _i))
    
        // 6.2.2.3 Sell only with profit
        array.insert(aSell, _i, ((na(FinalOpenPrice) and array.get(aPs, _i)) or (array.get(aPs, _i) and array.get(aUp, _i) >= (FinalOpenPrice + fWidth(Width)))) and nSells <= NSells-1)
        
        // 6.2.3 Financial Data
        RealizedPnL         := Balance - InitialCapital
        PRealizedPnL        := (RealizedPnL / InitialCapital) * 100
        Floating            := ((close - AvgPrice) / AvgPrice) * PositionSize * AvgPrice
        PFloating           := (Floating / Balance) * 100
        URealizedPnL        := RealizedPnL + Floating
        PURealizedPnL       := (URealizedPnL / InitialCapital) * 100
        Equity              := Balance + Floating
        Margin              := TradingType == 'Spot' ? 0 : (PositionSize * AvgPrice * MarginRate)
        Cash                := TradingType == 'Spot' ? math.max(0, Balance - (PositionSize * AvgPrice)) : math.max(0, Balance - Margin)
        BuyAndHold          := ((close  - FirstClose) / FirstClose) * InitialCapital
        PBuyAndHold         := (BuyAndHold / InitialCapital) * 100
        CLeverage           := (PositionSize * AvgPrice) / Balance
        MarginCallPrice     := TradingType == 'Spot' ? 0 :  AvgPrice - ((Balance - (Margin * 0.8)) / PositionSize)
        MarginCall          := (ta.valuewhen(MarginCallPrice >= low, time , 0) <= timenow)
        ProfitFactor        := Gains / Losses
        TradingTime         := timenow - TimeFirstClose
        
        // 6.2.4.1 Quantities to buy according to inputs
        if      BuyType == "Contracts"
            if na(FinalOpenPrice)
                BuyQuantity := ((Cash * InitialContracts) / FirstClose)
            else
                BuyQuantity := math.min((Cash / AvgPrice), BuyQ)
                   
        else if BuyType == "Cash"
            if na(FinalOpenPrice)
                BuyQuantity := (Cash * InitialContracts)
            else
                BuyQuantity := math.min(Cash, BuyQ)
                    
        else if BuyType == "% Cash"
            if na(FinalOpenPrice)
                BuyQuantity := (Cash * InitialContracts)
            else
                if nBuys >= NBuys - LastXtrades
                    BuyQ := (1 / (NBuys - nz(nBuys))) * 100
                
                BuyQuantity := math.min(Cash, (BuyQ / 100) * Cash)
        
        else if BuyType == "Cash / n Buys"
            if na(FinalOpenPrice)
                BuyQuantity := (Cash * InitialContracts)
            else
                BuyQuantity := math.min(Cash, fCaPo_N(NBuys, nBuys) * Cash)
                                           
        else if BuyType == "Cash / n Buys +"
            if na(FinalOpenPrice)
                BuyQuantity := (Cash * InitialContracts)
            else
                BuyQuantity := math.min(Cash, fCaPo_Nplus(0, NBuys, nBuys) * Cash)
            
        // 6.2.4.2 Quantities to sell according to inputs
        if      SellType == "Contracts"
            SellQuantity    := math.min(PositionSize, SellQ)
            
        else if SellType == "Cash"
            SellQuantity    := math.min((PositionSize * AvgPrice), SellQ)
            
        else if SellType == "% Position"
            SellQuantity    := math.min(PositionSize, (SellQ / 100) * PositionSize)
            
        else if SellType == "Position / n Sells"
            SellQuantity    := math.min(PositionSize, fCaPo_N(NSells, nSells) * PositionSize)
        
        else if SellType == "Position / n Sells +"
            SellQuantity    := math.min(PositionSize, fCaPo_Nplus(1, NSells, nSells) * PositionSize)
            
        // 6.2.5.1 First buy limit order from every change of the period
        if (PP != PP[1]) and nz(nBuys) == 0 and not nz(nSells) == 0 and not na(nBuys) and not na(fDownOrigin(fWidth(Width)))
            
            // Value of the MA
            sMAValue := sMA
            
            // Buy price of the limit order
            if (math.floor(PP / fWidth(Width)) * fWidth(Width)) <= (FinalOpenPrice - fWidth(Width))
                BuyLimitPrice := (math.floor(PP / fWidth(Width)) * fWidth(Width))
            else
                BuyLimitPrice := (math.floor(PP / fWidth(Width)) * fWidth(Width)) - fWidth(Width)
            
            // Alert Function with syntax to buy
            if OrdersType == 'Limit'
                fAlertsOnBuys(Account, 1, close, nBuys, nSells, BuyLimitPrice, SellLimitPrice)
                
        // 6.2.5.2 Buying at better Price
        if array.get(aBuy, _i) and BuyQuantity > 0
            
            // Value of the MA
            sMAValue := sMA
            
            // Price of buy orders and resetting sales
            FinalBuyPrice   := time == TimeFirstClose ? FirstClose : math.min(open, array.get(aDown, _i))
            FinalSellPrice  := na
            FinalOpenPrice  := FinalBuyPrice
            
            // Number of buys and resetting sales
            nBuys           += 1
            nSells          := na
            
            // Redefining buy quantity
            if BuyType == "Contracts"
                BuyAmount   := BuyQuantity
            else
                BuyAmount   := (BuyQuantity / FinalBuyPrice)
            
            // Calculating the priority and secondary price average
            PositionCashS   += FinalBuyPrice * BuyAmount
            PositionCash    += FinalBuyPrice * BuyAmount
            PositionSizeS   += BuyAmount
            PositionSize    += BuyAmount
            AvgPrice0       := PositionCashS / PositionSizeS
            AvgPrice        := PositionCash / PositionSize
            
            // Calculating net profit
            Balance         -= (BuyAmount * FinalBuyPrice * CommissionValue)
            
            // Comissions losses
            Losses          += (BuyAmount * FinalBuyPrice * CommissionValue)
            
            // Fees paid
            Commission      += (BuyAmount * FinalBuyPrice * CommissionValue)
            
            // Avoiding overlap
            BarIndex        := bar_index
            hl2Bar          := hl2
            
            // Variable reference
            MaxFinalOpenPrice := math.max(FinalBuyPrice, nz(MaxFinalOpenPrice))
            
            // Buy & Sell price of the limit orders
            if nBuys <= NBuys-1
                BuyLimitPrice := FinalBuyPrice - fWidth(Width)
            else
                BuyLimitPrice := na

            if fUpOrigin(fWidth(Width)) >= (FinalBuyPrice + fWidth(Width))
                SellLimitPrice := fUpOrigin(fWidth(Width))
            else
                SellLimitPrice := fUpOrigin(fWidth(Width)) + fWidth(Width)
                
            // Alerts with syntax to buy
            fAlertsOnBuys(Account, 0, FinalBuyPrice, nBuys, nSells, BuyLimitPrice, SellLimitPrice)
                
            // Buy shapes
            string BuyText = str.tostring(BuyAmount,'#.####') + "\n" + str.tostring(((BuyAmount * FinalBuyPrice) / Cash) * 100, '#.##') + "%"
            c_BuyGrad = color.from_gradient(((NBuys - nBuys) / NBuys) * 100, 1, 100, color.lime, color.blue)
            label.new(bar_index, FinalBuyPrice, BuyText, textcolor = #9598a1, color = c_BuyGrad, style = label.style_diamond, size = fTextSize(LabelSizeInput))

        // 6.2.5.3 Selling at better Price    
        else if array.get(aSell, _i) and SellQuantity > 0 and BarIndex != bar_index
            
            // Value of the MA
            sMAValue := sMA
            
            // Price of sale orders and resetting buys
            FinalBuyPrice   := na
            FinalSellPrice  := math.max(open, array.get(aUp, _i))
            FinalOpenPrice  := FinalSellPrice
            
            // Number of sales and resetting buys
            nBuys           := na
            nSells          += 1
            
            // Redefining sell quantity
            if SellType == "Cash"
                SellAmount  := SellQuantity / FinalSellPrice
            else
                SellAmount  := SellQuantity
                
            // Calculating the priority and resetting secondary price average
            PositionCashS   := 0
            PositionCash    -= AvgPrice * SellAmount
            PositionSizeS   := 0
            PositionSize    -= SellAmount
            
            // Calculating net profit
            Balance          += ((((FinalSellPrice - AvgPrice) / AvgPrice) * SellAmount) * FinalSellPrice)
            Balance          -= (SellAmount * FinalSellPrice * CommissionValue)
            
            // Gains and Losses
            if FinalSellPrice >= AvgPrice
                Gains       += ((((FinalSellPrice - AvgPrice) / AvgPrice) * SellAmount) * FinalSellPrice)
            else
                Losses      -= ((((FinalSellPrice - AvgPrice) / AvgPrice) * SellAmount) * FinalSellPrice)
            
            // Comission losses    
            Losses          += (SellAmount * FinalSellPrice * CommissionValue)
            
            // Fees paid
            Commission      += (SellAmount * FinalSellPrice * CommissionValue)
            
            // Variable reference
            MaxFinalOpenPrice := math.max(FinalSellPrice, nz(MaxFinalOpenPrice))
            
            // Buy & Sell price of the limit orders
            if FinalSellPrice <= PP
                BuyLimitPrice := fDownOrigin(fWidth(Width)) - fWidth(Width)
            else
                if (math.floor(PP / fWidth(Width)) * fWidth(Width)) <= (FinalOpenPrice - fWidth(Width))
                    BuyLimitPrice := (math.floor(PP / fWidth(Width)) * fWidth(Width))
                else
                    BuyLimitPrice := (math.floor(PP / fWidth(Width)) * fWidth(Width)) - fWidth(Width)
                    
            if nSells <= NSells-1
                SellLimitPrice := FinalSellPrice + fWidth(Width)
            else
                SellLimitPrice := na
            
            // Alerts with syntax to sell    
            fAlertsOnSells(Account, FinalSellPrice, nBuys, nSells, BuyLimitPrice, SellLimitPrice)
                
            // Sell shapes
            string SellText = str.tostring(SellAmount,'#.####') + "\n" + str.tostring((SellAmount / (PositionSize + SellAmount)) * 100, '#.##') + "%"
            c_SellGrad = color.from_gradient(((NSells - nSells) / NSells) * 100, 1, 100, color.yellow, color.red)
            label.new(bar_index, FinalSellPrice, SellText, textcolor = #9598a1, color = c_SellGrad, style = label.style_diamond, size = fTextSize(LabelSizeInput))
      
// 7.0 PLOTTING

// 7.1 Price of the limit orders
bool LastBar = (time >= timenow - (timeframe.multiplier * 1000 * 60))
plotshape(OrdersType == 'Limit' and LastBar ? BuyLimitPrice  : na, "Buy limit",  shape.cross, location.absolute, color.new(color.blue, 30), size = size.small)
plotshape(OrdersType == 'Limit' and LastBar ? SellLimitPrice : na, "Sell limit", shape.cross, location.absolute, color.new(color.red,  30), size = size.small)
plotshape(ShowGrid                          ? BuyLimitPrice  : na, "Buy level",  shape.cross, location.absolute, color.new(color.blue, 30), size = size.auto)
plotshape(ShowGrid                          ? SellLimitPrice : na, "Sell level", shape.cross, location.absolute, color.new(color.red,  30), size = size.auto)

// 7.2 Table
var InfoPanel = table.new(position = position.bottom_left, columns = 2, rows = 12, border_width = 1)
ftable(_table_id, _column, _row, _text, _bgcolor) => 
    table.cell(_table_id, _column, _row, _text, 0, 0, color.black, text.align_left, text.align_center, fTextSize(TableSizeInput), _bgcolor)

tfString(int timeInMs) =>
    // @function    Produces a string corresponding to the input time in days, hours, and minutes.
    // @param       (series int) A time value in milliseconds to be converted to a string variable. 
    // @returns     (string) A string variable reflecting the amount of time from the input time.
    float s  = timeInMs / 1000
    float m  = s / 60
    float h  = m / 60
    float d  = h / 24
    float mo = d / 30.416
    int tm   = math.floor(m % 60)
    int th   = math.floor(h % 24)
    int td   = math.floor(d % 30.416)
    int tmo  = math.floor(mo % 12)
    int ys   = math.floor(d / 365)
    
    string result = 
      switch
        d == 30 and th == 10 and tm == 30 => "1M"
        d == 7  and th == 0  and tm == 0  => "1W"
        =>
            string yStr  = ys  ? str.tostring(ys)  + "Y "  : ""
            string moStr = tmo ? str.tostring(tmo) + "M "  : ""
            string dStr  = td  ? str.tostring(td)  + "D "  : ""
            string hStr  = th  ? str.tostring(th)  + "H "  : ""
            string mStr  = tm  ? str.tostring(tm)  + "min" : ""
            yStr + moStr + dStr + hStr + mStr
            
if ShowPanel and barstate.islast
    ftable(InfoPanel, 0, 0,     'Equity: '                                                                              , #9598a1)
    ftable(InfoPanel, 0, 1,     'Position: '                                                                            , #9598a1)
    ftable(InfoPanel, 0, 2,     'Cash: '                                                                                , #9598a1)
    ftable(InfoPanel, 0, 3,     'Margin: '                                                                              , #9598a1)
    ftable(InfoPanel, 0, 4,     'Current Leverage: '                                                                    , #9598a1)
    ftable(InfoPanel, 0, 5,     'Commission: '                                                                          , #9598a1)
    ftable(InfoPanel, 0, 6,     'Floating: '                                                                            , #9598a1)
    ftable(InfoPanel, 0, 7,     'Realized PnL: '                                                                        , #9598a1)
    ftable(InfoPanel, 0, 8,     'Unrealized PnL: '                                                                      , #9598a1)
    ftable(InfoPanel, 0, 9,     'Buy n Hold: '                                                                          , #9598a1)
    ftable(InfoPanel, 0, 10,    'Profit Factor: '                                                                       , #9598a1)
    ftable(InfoPanel, 0, 11,    'Time of Trading: '                                                                     , #9598a1)
    ftable(InfoPanel, 1, 0,     MarginCall ? 'Margin Call️' : str.tostring(Equity, '#.####') + ' ' + syminfo.currency    , MarginCall ? color.red : #9598a1)
    ftable(InfoPanel, 1, 1,     str.tostring(PositionSize, '#.####') + ' ' + syminfo.basecurrency                       , #9598a1)
    ftable(InfoPanel, 1, 2,     str.tostring(Cash, '#.####') + ' ' + syminfo.currency                                   , #9598a1)
    ftable(InfoPanel, 1, 3,     str.tostring(Margin, '#.####') + ' ' + syminfo.currency                                 , #9598a1)
    ftable(InfoPanel, 1, 4,     TradingType == 'Spot' ? 'Spot' : str.tostring(CLeverage, '#.##') + 'x'                  , #9598a1)
    ftable(InfoPanel, 1, 5,     str.tostring(CommissionValue * 100, '#.##') + ' %'                                      , #9598a1)
    ftable(InfoPanel, 1, 6,     str.tostring(PFloating, '#.##') + ' %'                                                  , PFloating     >= 0 ? color.green : color.red)
    ftable(InfoPanel, 1, 7,     str.tostring(PRealizedPnL, '#.##') + ' %'                                               , PRealizedPnL  >= 0 ? color.green : color.red)
    ftable(InfoPanel, 1, 8,     str.tostring(PURealizedPnL, '#.##') + ' %'                                              , PURealizedPnL >= 0 ? color.green : color.red)
    ftable(InfoPanel, 1, 9,     str.tostring(PBuyAndHold, '#.##') + ' %'                                                , PBuyAndHold   >= 0 ? color.green : color.red)
    ftable(InfoPanel, 1, 10,    str.tostring(ProfitFactor, '#.##')                                                      , ProfitFactor  >= 1 ? color.green : color.red)
    ftable(InfoPanel, 1, 11,    tfString(TradingTime)                                                                   , #9598a1)
    
// 7.3 Plotting pivot points
plot(PP, title = "PP", style = plot.style_circles, color = color.silver, linewidth = 1)

// 7.4 Plotting the average price
plotshape(barstate.isrealtime and ta.change(AvgPrice0)    != 0 and (nz(nBuys) > 1) ? AvgPrice0      : na, "AvgPricesr",   shape.diamond, location.absolute, color.new(color.fuchsia, 40), size = size.tiny)
plotshape(barstate.isrealtime and ta.change(AvgPrice)     != 0                     ? AvgPrice       : na, "AvgPricepr",   shape.diamond, location.absolute, color.new(color.yellow,  10), size = size.tiny)
plotshape(                        ta.change(AvgPrice0[1]) != 0 and (nz(nBuys) > 1) ? AvgPrice0[1]   : na, "AvgPrices",    shape.diamond, location.absolute, color.new(color.fuchsia, 10), size = size.tiny)
plotshape(                        ta.change(AvgPrice[1])  != 0                     ? AvgPrice[1]    : na, "AvgPricep",    shape.diamond, location.absolute, color.new(color.yellow,  40), size = size.tiny)
plotshape(TimeFirstClose == time                                                   ? FirstClose     : na, "FirstClose",   shape.diamond, location.absolute, color.new(color.yellow,  40), size = size.tiny)

// 7.5 Plotting the moving average
plot((BuyType == "Cash / n Buys" or BuyType == "Cash / n Buys +") and NBuysUp != NBuysDown ? sMA : na, title = "Moving Average Buys", color = color.new(color.blue, 50), linewidth = 2)
plot((SellType == "Position / n Sells" or SellType == "Position / n Sells +") and NSellsUp != NSellsDown ? sMA : na, title = "Moving Average Sells", color = color.new(color.red, 50), linewidth = 2)

// 7.6 Plotting the liquidation price
plot(ShowMarginCall and MarginCallPrice > 0 ? MarginCallPrice : na, "Liquidation Price", MarginCall ? color.new(color.red, 30) : color.new(color.lime, 30), 2)
barcolor(MarginCall ? color.red : na)

// 8.0 NOTE

// Certain Risks of Live Algorithmic Trading:
// 1. Backtesting Cannot Assure Actual Results.
// 2. The relevant market might fail or behave unexpectedly.
// 3. Your broker may experience failures in its infrastructure, fail to execute your orders in a correct or timely fashion or reject your orders.
// 4. The system you use for generating trading orders, communicating those orders to your broker, and receiving queries and trading results from your broker may fail.
// 5. Time lag at various point in live trading might cause unexpected behavior.
// 6. The systems of third parties in addition to those of the provider from which we obtain various services, your broker, and the applicable securities market may fail or malfunction.