POA
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © dokang

//@version=5

// @description This library is a client script for making a webhook signal formatted string to PoABOT server.
library("POA", overlay = true)

is_stock() =>
    var stocks = array.from("KRX", "NASDAQ", "ARCA", "AMEX", "NYSE")
    array.includes(stocks, syminfo.prefix)

is_korea_stock() =>
    syminfo.prefix == "KRX"

is_us_stock() =>
    var stocks = array.from("NASDAQ", "ARCA", "AMEX", "NYSE")
    array.includes(stocks, syminfo.prefix)

is_crypto() =>
    (syminfo.type == "crypto") 
 
is_futures() => 
    code = switch syminfo.prefix
        "BINANCE"   => ".P"
        "BYBIT"     => ".P"
        "BITGET"    => ".P"
        "UPBIT"     => "PERP"
        "OKX"       => ".P"
        =>".P"
    is_crypto() and str.endswith(syminfo.ticker, code)

is_spot() =>
    is_crypto() and (not is_futures())

get_exchange_and_base_and_quote() =>
    is_stock = is_stock()
    base =  is_stock ? syminfo.ticker : syminfo.basecurrency
    quote = is_stock ? syminfo.currency : str.replace(syminfo.ticker, syminfo.basecurrency, "", 0)
    [syminfo.prefix, base, quote]

get_side_and_qty_and_price_and_orderName() =>
    ["{{strategy.order.action}}", "{{strategy.order.contracts}}", "{{strategy.order.price}}", "{{strategy.order.comment}}"]

// @function            Create a buy order message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for buy based on your wallet balance.
// @param kis_number    (int)    [Optional] The number of koreainvestment account.
// @returns             (string) A string containing the formatted webhook message.
buy_order(string password, float percent=na, int kis_number = 1) =>
    [exchange, str_base, str_quote] = get_exchange_and_base_and_quote()
    [str_side, str_qty, str_price, order_name] = get_side_and_qty_and_price_and_orderName()
    order_qty = str.tonumber(str_qty)
    if not na(percent)
        str_qty := na
    json =  '{'
             + str.format(
             ' 
                 "password":"{0}",
                 "exchange":"{1}",
                 "base":"{2}",
                 "quote":"{3}",
                 "side":"{4}",
                 "amount":"{5}",
                 "price":"{6}",
                 "percent":"{7}",
                 "order_name":"{8}",
                 "kis_number": "{9}"
             ', password, exchange, str_base, str_quote, str_side, str_qty, str_price, percent, order_name, kis_number) 
             +'}'
    json

// @function            Create a sell order message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for sell based on your wallet balance.
// @param kis_number    (int)    [Optional] The number of koreainvestment account. Default 1
// @returns             (string) A string containing the formatted webhook message.
sell_order(string password, float percent = na, int kis_number = 1) =>
    [exchange, str_base, str_quote] = get_exchange_and_base_and_quote()
    [str_side, str_qty, str_price, order_name] = get_side_and_qty_and_price_and_orderName()
    
    if not na(percent)
        str_qty := na

    json =  '{'
             + str.format(
             ' 
                 "password":"{0}",
                 "exchange":"{1}",
                 "base":"{2}",
                 "quote":"{3}",
                 "side":"{4}",
                 "price":"{5}",
                 "amount":"{6}",
                 "percent": "{7}",
                 "order_name":"{8}",
                 "kis_number":"{9}"
                 
             ', password, exchange, str_base,  str_quote, str_side, str_price, str_qty, percent, order_name, kis_number) 
             +'}'
    json

// @function            Create a entry order message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for entry based on your wallet balance.
// @param leverage      (int)    [Optional] The leverage of entry. If not set, your levereage doesn't change.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @returns             (string) A json formatted string for webhook message.
entry_order(string password, float percent = na, int leverage = na, string margin_mode = na) =>
    [exchange, base, quote] = get_exchange_and_base_and_quote()
    [side, str_qty, str_price, order_name] = get_side_and_qty_and_price_and_orderName()
	str_leverage = str.tostring(leverage)

    if not na(percent)
        str_qty := na

    json =  '{'
             + str.format(
             ' 
                 "password":"{0}",
                 "exchange":"{1}",
                 "base":"{2}",
                 "quote":"{3}",
                 "side":"{4}",
                 "amount":"{5}",
                 "price":"{6}",
                 "percent":"{7}",
                 "leverage": "{8}",
                 "margin_mode": "{9}",
                 "order_name":"{10}"
                 
             ', password, exchange, base, quote, "entry/"+side, str_qty, str_price, percent, str_leverage, margin_mode, order_name) 
             +'}'
    json


// @function            Create a close order message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for close based on your wallet balance.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @returns             (string) A json formatted string for webhook message.
close_order(string password, float percent = na, string margin_mode = na) =>
    [exchange, base, quote] = get_exchange_and_base_and_quote()
    [side, str_qty, str_price, order_name] = get_side_and_qty_and_price_and_orderName()
    
    if not na(percent)
        str_qty := na
        
    json = '{'
             + str.format(
             '   
                 "password":"{0}",
                 "exchange":"{1}",
                 "base":"{2}",
                 "quote":"{3}",
                 "side":"{4}",
                 "price":"{5}",
                 "amount":"{6}",
                 "percent":"{7}",
                 "margin_mode":"{8}",
                 "order_name":"{9}"
                
             ', password, exchange, base, quote, "close/"+side, str_price, str_qty, percent, margin_mode,order_name) 
             +'}'
    json

///////////////////////// EXPORT //////////////////////////////////////

// @function            Create a entry message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for entry based on your wallet balance.
// @param leverage      (int)    [Optional] The leverage of entry. If not set, your levereage doesn't change.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number    (int)    [Optional] The number of koreainvestment account. Default 1
// @returns             (string) A json formatted string for webhook message.
export entry_message(string password, float percent = na, int leverage = na, string margin_mode = na, int kis_number=1) => 
    is_buy = is_stock() or is_spot()
    is_buy  ? buy_order(password, percent, kis_number) : entry_order(password, percent, leverage, margin_mode)

// @function            Create a order message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for entry based on your wallet balance.
// @param leverage      (int)    [Optional] The leverage of entry. If not set, your levereage doesn't change.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number    (int)    [Optional] The number of koreainvestment account. Default 1
// @returns             (string) A json formatted string for webhook message.
export order_message(string password, float percent = na, int leverage = na, string margin_mode = na, int kis_number=1) => 
    is_buy = is_stock() or is_spot()
    is_buy  ? buy_order(password, percent, kis_number) : entry_order(password, percent, leverage, margin_mode)

// @function            Create a close message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for close based on your wallet balance.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number    (int)    [Optional] The number of koreainvestment account. Default 1
// @returns             (string) A json formatted string for webhook message.
export close_message(string password, float percent = na, string margin_mode = na, int kis_number=1) =>
    is_sell = is_stock() or is_spot()
    is_sell ? sell_order(password, percent, kis_number) : close_order(password, percent, margin_mode)

// @function            Create a exit message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param percent       (float)  [Optional] The percent for exit based on your wallet balance.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number    (int)    [Optional] The number of koreainvestment account. Default 1
// @returns             (string) A json formatted string for webhook message.
export exit_message(string password, float percent = na, string margin_mode = na, int kis_number=1) =>
    close_message(password, percent, margin_mode, kis_number)

// @function            Create a manual message for POABOT 
// @param password      (string) [Required] The password of your bot.
// @param exchange      (string) [Required] The exchange 
// @param base          (string) [Required] The base 
// @param quote         (string) [Required] The quote of order message 
// @param side          (string) [Required] The side of order messsage
// @param qty           (float)  [Optional] The qty of order message 
// @param price         (float)  [Optional] The price of order message 
// @param percent       (float)  [Optional] The percent for order based on your wallet balance.
// @param leverage      (int)    [Optional] The leverage of entry. If not set, your levereage doesn't change.
// @param margin_mode   (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number    (int)    [Optional] The number of koreainvestment account.
// @param order_name    (string) [Optional] The name of order message 
// @returns             (string) A json formatted string for webhook message.
export manual_message( string password, 
                       string exchange, 
                       string base, 
                       string quote, 
                       string side, 
                       float qty = na, 
                       float price = na, 
                       float percent = na, 
                       int leverage = na,
                       string margin_mode = na,
                       int kis_number=1, 
                       string order_name="Order") =>
	str_leverage = na(leverage) ? na : str.tostring(leverage)
    str_qty = str.tostring(qty)
    str_price = str.tostring(price)

    if not na(percent)
        str_qty := na

    json =  '{'
             + str.format(
             ' 
                 "password":"{0}",
                 "exchange":"{1}",
                 "base":"{2}",
                 "quote":"{3}",
                 "side":"{4}",
                 "amount":"{5}",
                 "price":"{6}",
                 "percent":"{7}",
                 "leverage": "{8}",
                 "margin_mode": "{9}",
                 "order_name":"{10}",
                 "kis_number": "{11}"
                 
             ', password, exchange, base, quote, side, str_qty, str_price, percent, str_leverage, margin_mode, order_name, kis_number) 
             +'}'
    json


// @function                Create a trade start line
// @param start_time        (int)   [Required] The start of time.
// @param end_time          (int)   [Required] The end of time.
// @param hide_trade_line   (bool)  [Optional] if true, hide trade line. Default false.
// @returns                 (bool)  Get bool for trade based on time range. 
export in_trade(int start_time, int end_time, bool hide_trade_line = false) =>
    allowedToTrade = (time>=start_time) and (time<=end_time)
    if barstate.islastconfirmedhistory and not hide_trade_line
        var myLine = line(na)
        line.delete(myLine)
        myLine := line.new(start_time, low, start_time, high, xloc=xloc.bar_time, color = color.rgb(255, 153, 0, 50), width = 3, extend = extend.both, style = line.style_dashed)
    allowedToTrade

// @function            Get exchange specific real qty
// @param qty           (float) [Optional] qty
// @param precision     (float) [Optional] precision
// @param leverage      (int)   [Optional] leverage
// @param contract_size (float) [Optional] contract_size
// @returns             (float) exchange specific qty. 
export real_qty(float qty = na, float precision = na, int leverage = na, float contract_size = na, string default_qty_type = na, float default_qty_value = na) =>
    _initial_qty = float(na)
    if na(qty)
        if default_qty_type == strategy.percent_of_equity
            _initial_qty := strategy.equity/close * default_qty_value * 0.01
        else if default_qty_type == strategy.cash
            _initial_qty := default_qty_value * 1.0 / close
        else if default_qty_type == strategy.fixed
            _initial_qty := default_qty_value
        else
            _initial_qty := strategy.equity/close
    else
        _initial_qty := qty
    // _initial_qty = na(qty) ? strategy.equity/close : qty
    initial_qty = float(na)
    if not is_futures()
        initial_qty := _initial_qty
    else
        initial_qty := na(leverage) ? _initial_qty : leverage * _initial_qty

    exchange = syminfo.prefix
    
    _precision = float(na)
    if na(precision)
        _precision := if exchange == "BINANCE"
            switch syminfo.basecurrency
                "BTC" => 0.001
                "ETH" => 0.001
                "BNB" => 0.01
                "XRP" => 0.1
                "DOT" => 0.1
                "LTC" => 0.001
                "LINK" => 0.01
                "ATOM" => 0.01
                "XMR" => 0.001
                "ETC" => 0.01
                "BCH" => 0.001
                "TRB" => 0.1
                
                => close >= 10000 ? 0.001 : close >= 1000 ? 0.01 : close >= 30 ? 0.1 : 1
        else if exchange == "BYBIT"
            switch syminfo.basecurrency
                "BTC" => 0.001
                "ETH" => 0.01
                "BNB" => 0.01
                "SOL" => 0.1
                "DOT" => 0.1
                "LTC" => 0.1
                "SHIB" => 10
                "AVAX" => 0.1
                "LINK" => 0.1
                "ATOM" => 0.1
                "UNI" => 0.1
                "XMR" => 0.01
                "ETC" => 0.1
                "ICP" => 0.1
                "BCH" => 0.01
                => close >= 10000 ? 0.001 : close >= 1000 ? 0.01 : close >= 30 ? 0.1 : 1
        else if exchange == "BITGET"
            switch syminfo.basecurrency
                "BTC" => 0.001
                "ETH" => 0.01
                "BNB" => 0.01
                "LTC" => 0.1
                "SHIB" => 10000
                "AVAX" => 0.1
                "XMR" => 0.01
                "ICP" => 0.01
                "BCH" => 0.01
                => close >= 10000 ? 0.001 : close >= 1000 ? 0.01 : close >= 30 ? 0.1 : 1
        else if exchange == "OKX"
            na
        else if exchange == "UPBIT"
            1e-08
        else
            if syminfo.type == "crypto" and syminfo.currency != "KRW"
                close >= 10000 ? 0.001 : close >= 1000 ? 0.01 : close >= 100 ? 0.1 : 1
    else
        _precision := precision <= 0 ? 1 : math.pow(10, -precision)


    _contract_size = na(contract_size) ? float(na) : contract_size
    if na(_contract_size)
        _contract_size := if exchange == "OKX"
            switch syminfo.basecurrency
                "BTC" => 0.01
                "ETH" => 0.1
                "LTC" => 1
                "XRP" => 100
                "BCH" => 0.1
                "SOL" => 1
                "FIL" => 0.1
                "PEPE" => 25
                "DOGE" => 1000
                "APT" => 1
    
    initial_qty := if na(_contract_size)
        initial_qty
    else
        math.floor(initial_qty / _contract_size) * _contract_size 
    
    if na(_precision) 
        initial_qty
    else
        decimal = math.abs(math.round(math.log10(_precision)))
        factor = math.pow(10, decimal)
        result = int(initial_qty * factor) / factor
        result == 0 ? initial_qty : result

export type bot
    string  password
    int     start_time 
    int     end_time
    int     leverage = 1
    float   initial_capital = na
    string  default_qty_type = strategy.percent_of_equity
    float   default_qty_value = 100
    string  margin_mode
    float   contract_size
    int     kis_number
    float   entry_percent
    float   close_percent
    float   exit_percent
    table   log_table
    float   fixed_qty = na
    float   fixed_cash = na
    bool    real = true
    bool    auto_alert_message = true
    bool    hide_trade_line = false

// @function                    Set bot object. 
// @param password              (string) [Optional] password for poabot.
// @param start_time            (int)    [Optional] start_time timestamp.
// @param end_time              (int)    [Optional] end_time timestamp.
// @param leverage              (int)    [Optional] leverage.
// @param margin_mode           (string) [Optional] The margin mode for trade(only for OKX). "cross" or "isolated"
// @param kis_number            (int)    [Optional] kis_number for poabot.
// @param entry_percent         (float)  [Optional] entry_percent for poabot.
// @param close_percent         (float)  [Optional] close_percent for poabot.
// @param exit_percent          (float)  [Optional] exit_percent for poabot.
// @param fixed_qty             (float)  [Optional] fixed qty.
// @param fixed_cash            (float)  [Optional] fixed cash.
// @param real                  (bool)   [Optional] convert qty for exchange specific.
// @param auto_alert_message    (bool)   [Optional] convert alert_message for exchange specific.
// @param hide_trade_line       (bool)   [Optional] if true, Hide trade line. Default false.
// @returns                     (void)
export method set(bot this, string password = na, int start_time = na, int end_time = na, int leverage = na, float initial_capital = na,string default_qty_type = na, float default_qty_value = na , string margin_mode = na, float contract_size = na, int kis_number = na, float entry_percent = na, float close_percent = na, float exit_percent = na, float fixed_qty = na, float fixed_cash = na,bool real = na, bool auto_alert_message = na, bool hide_trade_line = false) =>
    if not na(password)
        this.password := password
    if not na(start_time)
        this.start_time := start_time
    if not na(end_time)
        this.end_time := end_time
    if not na(leverage)
        this.leverage := leverage
    if not na(initial_capital)
        this.initial_capital := initial_capital
    if not na(default_qty_type)
        this.default_qty_type := default_qty_type
    if not na(default_qty_value)
        this.default_qty_value := default_qty_value
    if not na(margin_mode)
        this.margin_mode := margin_mode
    if not na(contract_size)
        this.contract_size := contract_size
    if not na(kis_number)
        this.kis_number := kis_number
    if not na(entry_percent)
        this.entry_percent := entry_percent
    if not na(close_percent)
        this.close_percent := close_percent
    if not na(exit_percent)
        this.exit_percent := exit_percent
    if not na(fixed_qty)
        this.fixed_qty := fixed_qty
    if not na(fixed_cash)
        this.fixed_cash := fixed_cash
    if not na(real)
        this.real := real
    if not na(auto_alert_message)
        this.auto_alert_message := auto_alert_message
    if not na(hide_trade_line)
        this.hide_trade_line := hide_trade_line

var cell_text_color = color.white

// @function            Print message using log table. 
// @param password      (string) [Required] message.
// @returns             (void)
export method print(bot this, string message) =>    
    if na(this.log_table)
        this.log_table := table.new(position.bottom_center, 2, 2, color.red)
        table.cell(this.log_table, 0, 0, "", text_color = cell_text_color)
    table.cell_set_text(this.log_table, 0, 0, message)

// @function            start trade using start_time and end_time
// @returns             (void)
export method start_trade(bot this) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    in_trade(this.start_time, this.end_time, this.hide_trade_line)


// @function            It is a command to enter market position. If an order with the same ID is already pending, it is possible to modify the order. If there is no order with the specified ID, a new order is placed. To deactivate an entry order, the command strategy.cancel or strategy.cancel_all should be used. In comparison to the function strategy.order, the function strategy.entry is affected by pyramiding and it can reverse market position correctly. If both 'limit' and 'stop' parameters are 'NaN', the order type is market order.
// @param id            (string) [Required] A required parameter. The order identifier. It is possible to cancel or modify an order by referencing its identifier.
// @param direction     (string) [Required] A required parameter. Market position direction: 'strategy.long' is for long, 'strategy.short' is for short.
// @param qty           (float)  [Optional] An optional parameter. Number of contracts/shares/lots/units to trade. The default value is 'NaN'.
// @param limit         (float)  [Optional] An optional parameter. Limit price of the order. If it is specified, the order type is either 'limit', or 'stop-limit'. 'NaN' should be specified for any other order type.
// @param stop          (float)  [Optional] An optional parameter. Stop price of the order. If it is specified, the order type is either 'stop', or 'stop-limit'. 'NaN' should be specified for any other order type.
// @param oca_name      (string) [Optional] An optional parameter. Name of the OCA group the order belongs to. If the order should not belong to any particular OCA group, there should be an empty string.
// @param oca_type      (string) [Optional] An optional parameter. Type of the OCA group. The allowed values are: "strategy.oca.none" - the order should not belong to any particular OCA group; "strategy.oca.cancel" - the order should belong to an OCA group, where as soon as an order is filled, all other orders of the same group are cancelled; "strategy.oca.reduce" - the order should belong to an OCA group, where if X number of contracts of an order is filled, number of contracts for each other order of the same OCA group is decreased by X.
// @param comment       (string) [Optional] An optional parameter. Additional notes on the order.
// @param alert_message (string) [Optional] An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @param when          (bool)   [Optional] An optional parmeter. Condition, deprecated.   
// @returns             (void)
export method entry(
                     bot this, 
                     string id, 
                     string direction, 
                     float qty = na, 
                     float limit = na, 
                     float stop = na, 
                     string oca_name = na, 
                     string oca_type = na, 
                     string comment = na, 
                     string alert_message = na,
                     bool when = na
                     ) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        var directions = array.from("strategy.long", "strategy.short")
        if not array.includes(directions, str.lower(direction))
            this.print('Only "strategy.long" and "strategy.short" directions are permitted.')
        else if this.real and strategy.equity < 0
            this.print('Equity is lower than 0')
        else
            _direction = direction == "strategy.long" ? strategy.long :  direction == "strategy.short" ? strategy.short : na
            final_qty = float(na)
            initial_qty = if not na(this.fixed_cash)
                this.fixed_cash / close
            else if not na(this.fixed_qty)
                this.fixed_qty
            else
                qty
            final_qty := this.real ? real_qty(initial_qty, leverage = this.leverage, contract_size = this.contract_size, default_qty_type = this.default_qty_type, default_qty_value = this.default_qty_value) : initial_qty
        
            if this.real and final_qty <= 0
                this.print(str.format("Insufficient capital for entry : {0}", strategy.equity))
            
            if na(oca_type)
                strategy.entry(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, comment = comment, alert_message = this.auto_alert_message ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.none" 
                strategy.entry(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.none, comment = comment, alert_message = this.auto_alert_message ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) :  na(alert_message) ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.cancel"
                strategy.entry(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.cancel, comment = comment, alert_message = this.auto_alert_message ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.reduce"
                strategy.entry(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.reduce, comment = comment, alert_message = this.auto_alert_message ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? entry_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)


// @function            It is a command to place order. If an order with the same ID is already pending, it is possible to modify the order. If there is no order with the specified ID, a new order is placed. To deactivate order, the command strategy.cancel or strategy.cancel_all should be used. In comparison to the function strategy.entry, the function strategy.order is not affected by pyramiding. If both 'limit' and 'stop' parameters are 'NaN', the order type is market order.
// @param id            (string) [Required] A required parameter. The order identifier. It is possible to cancel or modify an order by referencing its identifier.
// @param direction     (string) [Required] A required parameter. Market position direction: 'strategy.long' is for long, 'strategy.short' is for short.
// @param qty           (float)  [Optional] An optional parameter. Number of contracts/shares/lots/units to trade. The default value is 'NaN'.
// @param limit         (float)  [Optional] An optional parameter. Limit price of the order. If it is specified, the order type is either 'limit', or 'stop-limit'. 'NaN' should be specified for any other order type.
// @param stop          (float)  [Optional] An optional parameter. Stop price of the order. If it is specified, the order type is either 'stop', or 'stop-limit'. 'NaN' should be specified for any other order type.
// @param oca_name      (string) [Optional] An optional parameter. Name of the OCA group the order belongs to. If the order should not belong to any particular OCA group, there should be an empty string.
// @param oca_type      (string) [Optional] An optional parameter. Type of the OCA group. The allowed values are: "strategy.oca.none" - the order should not belong to any particular OCA group; "strategy.oca.cancel" - the order should belong to an OCA group, where as soon as an order is filled, all other orders of the same group are cancelled; "strategy.oca.reduce" - the order should belong to an OCA group, where if X number of contracts of an order is filled, number of contracts for each other order of the same OCA group is decreased by X.
// @param comment       (string) [Optional] An optional parameter. Additional notes on the order.
// @param alert_message (string) [Optional] An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @param when          (bool)   [Optional] An optional parmeter. Condition, deprecated.   
// @returns             (void)
export method order( bot this, 
                     string id, 
                     string direction, 
                     float qty = na, 
                     float limit = na, 
                     float stop = na, 
                     string oca_name = na, 
                     string oca_type = na, 
                     string comment = na, 
                     string alert_message = na,
                     bool when = na
                     ) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")

    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        var directions = array.from("strategy.long", "strategy.short")
        if not array.includes(directions, str.lower(direction))
            this.print('Only "strategy.long" and "strategy.short" directions are permitted.')
        else if this.real and strategy.equity < 0
            this.print('Equity is lower than 0')
        else
            _direction = direction == "strategy.long" ? strategy.long :  direction == "strategy.short" ? strategy.short : na
            final_qty = float(na)
            initial_qty = if not na(this.fixed_cash)
                this.fixed_cash / close
            else if not na(this.fixed_qty)
                this.fixed_qty
            else
                qty
            final_qty := this.real ? real_qty(initial_qty, leverage = this.leverage, contract_size = this.contract_size, default_qty_type = this.default_qty_type, default_qty_value = this.default_qty_value) : initial_qty
        
            if this.real and final_qty <= 0
                this.print(str.format("Insufficient capital for entry : {0}", strategy.equity))
            

            if na(oca_type)
                strategy.order(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, comment = comment, alert_message = this.auto_alert_message ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.none" 
                strategy.order(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.none, comment = comment, alert_message = this.auto_alert_message ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) :  na(alert_message) ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.cancel"
                strategy.order(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.cancel, comment = comment, alert_message = this.auto_alert_message ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)
            else if oca_type == "strategy.oca.reduce"
                strategy.order(id=id, direction=_direction, qty=final_qty, limit=limit, stop=stop, oca_name=oca_name, oca_type = strategy.oca.reduce, comment = comment, alert_message = this.auto_alert_message ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : na(alert_message) ? order_message(this.password, this.entry_percent, leverage = this.leverage, margin_mode = this.margin_mode, kis_number=this.kis_number) : alert_message)        

// @function            Exits the current market position, making it flat.
// @param comment       (string) [Optional] An optional parameter. Additional notes on the order.
// @param alert_message (string) [Optional] An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @param immediately   (bool)   [Optional] An optional parameter. If true, the closing order will be executed on the tick where it has been placed, ignoring the strategy parameters that restrict the order execution to the open of the next bar. The default is false.
// @param when          (bool)   [Optional] An optional parmeter. Condition, deprecated.    
// @returns             (void)
export method close_all(bot this, string comment = na, string alert_message = na, bool immediately = false, bool when = na) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        this.set()
        strategy.close_all(comment, alert_message, immediately)

// @function            It is a command to cancel/deactivate pending orders by referencing their names, which were generated by the functions: strategy.order, strategy.entry and strategy.exit.
// @param id            (string) [Optional] A required parameter. The order identifier. It is possible to cancel an order by referencing its identifier.
// @param when          (bool)   [Optional] An optional parmeter. Condition, deprecated.    
// @returns             (void)
export method cancel(bot this, string id, bool when = na) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        this.set()
        strategy.cancel(id)

// @function            It is a command to cancel/deactivate all pending orders, which were generated by the functions: strategy.order, strategy.entry and strategy.exit.
// @param when          (bool)  [Optional] An optional parmeter. Condition, deprecated.    
// @returns             (void)
export method cancel_all(bot this, bool when = na) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        this.set()
        strategy.cancel_all()

// @function            It is a command to exit from the entry with the specified ID. If there were multiple entry orders with the same ID, all of them are exited at once. If there are no open entries with the specified ID by the moment the command is triggered, the command will not come into effect. The command uses market order. Every entry is closed by a separate market order.
// @param id            (string) [Required] A required parameter. The order identifier. It is possible to close an order by referencing its identifier.
// @param comment       (string) [Optional] An optional parameter. Additional notes on the order.
// @param qty           (float)  [Optional] An optional parameter. Number of contracts/shares/lots/units to exit a trade with. The default value is 'NaN'.
// @param qty_percent   (float)  [Optional] Defines the percentage (0-100) of the position to close. Its priority is lower than that of the 'qty' parameter. Optional. The default is 100.
// @param alert_message (string) [Optional] An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @param immediately   (bool)   [Optional] An optional parameter. If true, the closing order will be executed on the tick where it has been placed, ignoring the strategy parameters that restrict the order execution to the open of the next bar. The default is false.
// @param when          (bool)   [Optional] An optional parmeter. Condition, deprecated.    
// @returns             (void)
export method close(
                     bot this, 
                     string id, 
                     string comment=na, 
                     float qty=na, 
                     float qty_percent=100, 
                     string alert_message = na, 
                     bool immediately = false,
                     bool when = na
                     ) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        strategy.close(id=id, comment=comment, qty=qty, qty_percent = qty_percent, alert_message = this.auto_alert_message ? close_message(this.password, this.close_percent, this.margin_mode, this.kis_number) : na(alert_message) ? close_message(this.password, this.close_percent, this.margin_mode, this.kis_number) : alert_message, immediately = immediately)

// @function        Converts ticks to a price offset from the supplied price or the average entry price.
// @param ticks     (float) Ticks to convert to a price.
// @param from      (float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns         (float) A price level that has a distance from the entry price equal to the specified number of ticks.
export ticks_to_price(series float ticks, series float from = strategy.position_avg_price) =>
    float offset = ticks * syminfo.mintick 
    strategy.position_size != 0 ? from + offset : float(na)

// @function                It is a command to exit either a specific entry, or whole market position. If an order with the same ID is already pending, it is possible to modify the order. If an entry order was not filled, but an exit order is generated, the exit order will wait till entry order is filled and then the exit order is placed. To deactivate an exit order, the command strategy.cancel or strategy.cancel_all should be used. If the function strategy.exit is called once, it exits a position only once. If you want to exit multiple times, the command strategy.exit should be called multiple times. If you use a stop loss and a trailing stop, their order type is 'stop', so only one of them is placed (the one that is supposed to be filled first). If all the following parameters 'profit', 'limit', 'loss', 'stop', 'trail_points', 'trail_offset' are 'NaN', the command will fail. To use market order to exit, the command strategy.close or strategy.close_all should be used.
// @param id                (string) [Required] A required parameter. The order identifier. It is possible to cancel or modify an order by referencing its identifier.
// @param from_entry        (string) [Optional] An optional parameter. The identifier of a specific entry order to exit from it. To exit all entries an empty string should be used. The default values is empty string.
// @param qty               (float)  [Optional] An optional parameter. Number of contracts/shares/lots/units to exit a trade with. The default value is 'NaN'.
// @param qty_percent       (float)  [Optional] Defines the percentage of (0-100) the position to close. Its priority is lower than that of the 'qty' parameter. Optional. The default is 100.
// @param profit            (float)  [Optional] An optional parameter. Profit target (specified in ticks). If it is specified, a limit order is placed to exit market position when the specified amount of profit (in ticks) is reached. The default value is 'NaN'.
// @param limit             (float)  [Optional] An optional parameter. Profit target (requires a specific price). If it is specified, a limit order is placed to exit market position at the specified price (or better). Priority of the parameter 'limit' is higher than priority of the parameter 'profit' ('limit' is used instead of 'profit', if its value is not 'NaN'). The default value is 'NaN'.
// @param loss              (float)  [Optional] An optional parameter. Stop loss (specified in ticks). If it is specified, a stop order is placed to exit market position when the specified amount of loss (in ticks) is reached. The default value is 'NaN'.
// @param stop              (float)  [Optional] An optional parameter. Stop loss (requires a specific price). If it is specified, a stop order is placed to exit market position at the specified price (or worse). Priority of the parameter 'stop' is higher than priority of the parameter 'loss' ('stop' is used instead of 'loss', if its value is not 'NaN'). The default value is 'NaN'.
// @param trail_price       (float)  [Optional] An optional parameter. Trailing stop activation level (requires a specific price). If it is specified, a trailing stop order will be placed when the specified price level is reached. The offset (in ticks) to determine initial price of the trailing stop order is specified in the 'trail_offset' parameter: X ticks lower than activation level to exit long position; X ticks higher than activation level to exit short position. The default value is 'NaN'.
// @param trail_points      (float)  [Optional] An optional parameter. Trailing stop activation level (profit specified in ticks). If it is specified, a trailing stop order will be placed when the calculated price level (specified amount of profit) is reached. The offset (in ticks) to determine initial price of the trailing stop order is specified in the 'trail_offset' parameter: X ticks lower than activation level to exit long position; X ticks higher than activation level to exit short position. The default value is 'NaN'.
// @param trail_offset      (float)  [Optional] An optional parameter. Trailing stop price (specified in ticks). The offset in ticks to determine initial price of the trailing stop order: X ticks lower than 'trail_price' or 'trail_points' to exit long position; X ticks higher than 'trail_price' or 'trail_points' to exit short position. The default value is 'NaN'.
// @param oca_name          (string) [Optional] An optional parameter. Name of the OCA group (oca_type = strategy.oca.reduce) the profit target, the stop loss / the trailing stop orders belong to. If the name is not specified, it will be generated automatically.
// @param comment           (string) [Optional] Additional notes on the order. If specified, displays near the order marker on the chart. Optional. The default is na.
// @param comment_profit    (string) [Optional] Additional notes on the order if the exit was triggered by crossing `profit` or `limit` specifically. If specified, supercedes the `comment` parameter and displays near the order marker on the chart. Optional. The default is na.
// @param comment_loss      (string) [Optional] Additional notes on the order if the exit was triggered by crossing `stop` or `loss` specifically. If specified, supercedes the `comment` parameter and displays near the order marker on the chart. Optional. The default is na.
// @param comment_trailing  (string) [Optional] Additional notes on the order if the exit was triggered by crossing `trail_offset` specifically. If specified, supercedes the `comment` parameter and displays near the order marker on the chart. Optional. The default is na.
// @param alert_message     (string) [Optional] Text that will replace the '{{strategy.order.alert_message}}' placeholder when one is used in the "Message" field of the "Create Alert" dialog. Optional. The default is na.
// @param alert_profit      (string) [Optional] Text that will replace the '{{strategy.order.alert_message}}' placeholder when one is used in the "Message" field of the "Create Alert" dialog. Only replaces the text if the exit was triggered by crossing `profit` or `limit` specifically. Optional. The default is na.
// @param alert_loss        (string) [Optional] Text that will replace the '{{strategy.order.alert_message}}' placeholder when one is used in the "Message" field of the "Create Alert" dialog. Only replaces the text if the exit was triggered by crossing `stop` or `loss` specifically. Optional. The default is na.
// @param alert_trailing    (string) [Optional] Text that will replace the '{{strategy.order.alert_message}}' placeholder when one is used in the "Message" field of the "Create Alert" dialog. Only replaces the text if the exit was triggered by crossing `trail_offset` specifically. Optional. The default is na.
// @param when              (bool)   [Optional] An optional parmeter. Condition, deprecated.
// @returns                 (void)
export method exit(  bot this, 
                     string id, 
                     string from_entry = na, 
                     float qty = na, 
                     float qty_percent = 100, 
                     float profit = na, 
                     float limit = na, 
                     float loss = na, 
                     float stop = na, 
                     float trail_price = na, 
                     float trail_points = na, 
                     float trail_offset = na, 
                     string oca_name = na, 
                     string comment = na, 
                     string comment_profit = na, 
                     string comment_loss = na, 
                     string comment_trailing = na, 
                     string alert_message = na, 
                     string alert_profit = na, 
                     string alert_loss = na, 
                     string alert_trailing = na,
                     bool when = na
                     ) =>
    if na(this.start_time)
        this.print("start_time has not been set.")
    if not na(when)
        this.print("'when' will be deprecated. We recommend using `if` or `switch` conditional blocks instead.")
    if in_trade(this.start_time, this.end_time, this.hide_trade_line) and (when or na(when))
        strategy.exit(id, 
                      from_entry, 
                      qty, 
                      qty_percent, 
                      profit, 
                      limit, 
                      loss, 
                      stop, 
                      trail_price, 
                      trail_points, 
                      trail_offset, 
                      oca_name, 
                      comment, 
                      comment_profit, 
                      comment_loss, 
                      comment_trailing, 
                      this.auto_alert_message ? exit_message(this.password, this.exit_percent, this.margin_mode, this.kis_number) : na(alert_message) ? exit_message(this.password, this.exit_percent, this.margin_mode, this.kis_number) : alert_message,
                      alert_profit, 
                      alert_loss, 
                      alert_trailing
                      )

// @function        Converts a percentage of the supplied price or the average entry price to ticks.
// @param percent   (float) The percentage of supplied price to convert to ticks. 50 is 50% of the entry price.
// @param from      (float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns         (float) A value in ticks.
export percent_to_ticks(series float percent, series float from = strategy.position_avg_price) =>
    strategy.position_size != 0 ? percent / 100 * from / syminfo.mintick : percent / 100 * close / syminfo.mintick

// @function        Converts a percentage of the supplied price or the average entry price to a price.
// @param percent   (float) The percentage of the supplied price to convert to price. 50 is 50% of the supplied price.
// @param from      (float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns         (float) A value in the symbol's quote currency (USD for BTCUSD).
export percent_to_price(series float percent, series float from = strategy.position_avg_price) =>
    strategy.position_size != 0 ? (1 + percent / 100) * from : float(na)