CNTLibrary
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © CN_FX-999

// Coded By Christian Nataliano
// First Coded In 10/06/2023
// Last Edited In 02/08/2023

// Huge Shout Out To © ZenAndTheArtOfTrading and his ZenLibrary V5, Some Of The Custom Functions Were Heavlily Inspired By Matt's Work & His Pine Script Mastery Course
// Another Shout Out To The TradingView's Team Library ta V5 

//@version=5
// @description Custom Functions To Help Code In Pinescript V5
library("CNTLibrary")

//Import Refrance Libraries
import TradingView/ta/5 as Tv

//======================================================================================================================================================================
// Custom Indicator Functions
//======================================================================================================================================================================

//== Calculate Kaufman's Adaptive Moving Average ==
// @function Calculates An Adaptive Moving Average Based On Perry J Kaufman's Calculations 
// @param KAMA_lenght Is The KAMA Lenght
// @param Fast_KAMA Is The KAMA's Fastes Moving Average
// @param Slow_KAMA Is The KAMA's Slowest Moving Average
// @returns Float Of The KAMA's Current Calculations
export GetKAMA(int KAMA_lenght, int Fast_KAMA, int Slow_KAMA) =>
    //Efficiency Ratio
    Fast_Trend = 2 / (Fast_KAMA + 1)
    Slow_Trend = 2 / (Slow_KAMA + 1)

    Difference = math.abs(close - close[1])
    Signal = math.abs(close - close[KAMA_lenght])
    Noise = math.sum(Difference,KAMA_lenght)

    ER = 1.0
    if(Noise != 0)
        ER := Signal/Noise 

    //Smoothing Constant
    Smoothing_Const = math.pow(ER * (Fast_Trend-Slow_Trend) + Slow_Trend,2)

    //KAMA Indicator
    KAMA = 1.0
    if barstate.isfirst
        KAMA := close
    else 
        KAMA := KAMA[1] + Smoothing_Const * (close - KAMA[1])

    KAMA

//== Get Custom Moving Average Values ==
// @function Get Custom Moving Averages Values
// @param Source Of The Moving Average, Defval = close
// @param Lenght Of The Moving Average, Defval = 50
// @param Type Of The Moving Average, Defval = Exponential Moving Average
// @returns The Moving Average Calculation Based On Its Given Source, Lenght & Calculation Type (Please Call Function On Global Scope)
export GetMovingAverage(float Source = close, simple int Lenght = 50, string Type = "") =>
	//Moving Average Export Value
    float MovingAverage = 0

	//Moving Average Type 
    MAValue = switch Type
        "EMA"  => ta.ema(Source,Lenght)
        "SMA"  => ta.sma(Source,Lenght)
        "WMA"  => ta.wma(Source,Lenght)
        "RMA"  => ta.rma(Source,Lenght)
        "HMA"  => ta.hma(Source,Lenght)
        "VWMA" => ta.vwma(Source,Lenght)
        "SWMA" => ta.swma(Source)
        "VWAP" => ta.vwap
        "DEMA" => 
            e1 = ta.ema(Source,Lenght)
            e2 = ta.ema(e1,Lenght)
            2 * e1 - e2
        => ta.ema(Source,Lenght)
    
    MovingAverage := MAValue

//== Get Decimals ==
// @function Calculates how many decimals are on the quote price of the current market © ZenAndTheArtOfTrading
// @returns The current decimal places on the market quote price
export GetDecimals() => math.abs(math.log(syminfo.mintick) / math.log(10))

//== Truncate Number ==
// @function Truncates (cuts) excess decimal places © ZenAndTheArtOfTrading
// @param float number The number to truncate
// @param float decimalPlaces (default=2) The number of decimal places to truncate to
// @returns The given number truncated to the given decimalPlaces
export Truncate(float number, simple float decimalPlaces = 2.0) =>
    factor = math.pow(10, decimalPlaces)
    int(number * factor) / factor

//== Convert To Whole Number ==
// @function Converts pips into whole numbers © ZenAndTheArtOfTrading
// @param float number The pip number to convert into a whole number
// @returns The converted number
export ToWhole(float number) =>
    _return = number < 1.0 ? number / syminfo.mintick / (10 / syminfo.pointvalue) : number
    _return := number >= 1.0 and number <= 100.0 ? _return * 100 : _return

//== Convert To Pips ==
// @function Converts whole numbers back into pips © ZenAndTheArtOfTrading
// @param float number The whole number to convert into pips
// @returns The converted number
export ToPips(float number) =>
    number * syminfo.mintick * 10

//== Get Precentage Change
// @function Gets the percentage change between 2 float values over a given lookback period © ZenAndTheArtOfTrading
// @param float value1 The first value to reference
// @param float value2 The second value to reference
// @param int lookback The lookback period to analyze
export GetPctChange(float value1, float value2, int lookback) =>
    vChange = value1 - value2
    vDiff   = vChange - vChange[lookback]
    (vDiff / vChange) * 100

//== Prev Bars Above MA ==
// @function Counts how many candles are above the MA © ZenAndTheArtOfTrading
// @param int lookback The lookback period to look back over
// @param float ma The moving average to check
// @returns The bar count of how many recent bars are above the MA
export BarsAboveMA(int lookback, float ma) =>
    counter = 0
    for i = 1 to lookback by 1
        if close[i] > ma[i]
            counter := counter + 1
    counter

//== Prev Bars Below MA ==
// @function Counts how many candles are below the MA © ZenAndTheArtOfTrading
// @param int lookback The lookback period to look back over
// @param float ma The moving average to reference
// @returns The bar count of how many recent bars are below the EMA
export BarsBelowMA(int lookback, float ma) =>
    counter = 0
    for i = 1 to lookback by 1
        if close[i] < ma[i]
            counter := counter + 1
    counter

//== Prev Bars That Crossed MA ==
// @function Counts how many times the EMA was crossed recently © ZenAndTheArtOfTrading
// @param int lookback The lookback period to look back over
// @param float ma The moving average to reference
// @returns The bar count of how many times price recently crossed the EMA
export BarsCrossedMA(int lookback, float ma) =>
    counter = 0
    for i = 1 to lookback by 1
        if open[i] > ma and close[i] < ma or open[i] < ma and close[i] > ma
            counter := counter + 1
    counter

//== Get Prev Pullback Bar Count ==
// @function Counts how many green & red bars have printed recently (ie. pullback count) © ZenAndTheArtOfTrading
// @param int lookback The lookback period to look back over
// @param int direction The color of the bar to count (1 = Green, -1 = Red)
// @returns The bar count of how many candles have retraced over the given lookback & direction
export GetPullbackBarCount(int lookback, int direction) =>
    recentCandles = 0
    for i = 1 to lookback by 1
        if direction == 1 and close[i] > open[i]  // Count green bars
            recentCandles := recentCandles + 1
        if direction == -1 and close[i] < open[i]  // Count red bars
            recentCandles := recentCandles + 1
    recentCandles

//== Calculate Last Swing High ==
// @function Check If Price Has Made A Recent Swing High 
// @param Lookback Is For The Swing High Lookback Period, Defval = 7
// @param SwingType Is For The Swing High Type Of Identification, Defval = 1
// @returns A Bool - True If Price Has Made A Recent Swing High
export GetSwingHigh(int Lookback = 7, int SwingType = 1) =>
    bool SwingHigh = false
    HighestHigh = high[0] == ta.highest(Lookback)
    NewSwingHigh = switch SwingType
        1 => HighestHigh[0]
        2 => HighestHigh[0] or HighestHigh[1]
        3 => HighestHigh[0] or HighestHigh[1] or HighestHigh[2]
        => HighestHigh[0]
    SwingHigh := NewSwingHigh

//== Calculate Last Swing Low ==
// @function Check If Price Has Made A Recent Swing Low 
// @param Lookback Is For The Swing Low Lookback Period, Defval = 7
// @param SwingType Is For The Swing Low Type Of Identification, Defval = 1
// @returns A Bool - True If Price Has Made A Recent Swing Low
export GetSwingLow(int Lookback = 7, int SwingType = 1) =>
    bool SwingLow = false
    LowestLow = low[0] == ta.lowest(Lookback)
    NewSwingLow = switch SwingType
        1 => LowestLow[0]
        2 => LowestLow[0] or LowestLow[1]
        3 => LowestLow[0] or LowestLow[1] or LowestLow[2]
        => LowestLow[0]
    SwingLow := NewSwingLow

//======================================================================================================================================================================
// Custom Risk Management Functions
//======================================================================================================================================================================

//== Calculate StopLoss Level ==
// @function Calculate StopLoss Level
// @param OrderType Is To Determine A Long / Short Position, Defval = 1
// @param Entry Is The Entry Level Of The Order, Defval = na
// @param StopLoss Is The Custom StopLoss Distance, Defval = 2x ATR Below Close
// @returns Float - The StopLoss Level In Actual Price As A
export CalculateStopLossLevel(int OrderType = 1, float Entry = na, float StopLoss = na) =>
    //Stop Loss Export & ATR Value
    float StopLossLevel = na
    ATR = ta.atr(20)

    //Calculate Inputed SL Level 
    if(not na(Entry) and not na(StopLoss))
        if(OrderType == 1)
            StopLossLevel := Entry - StopLoss
        else if (OrderType == -1)
            StopLossLevel := Entry + StopLoss
        else 
            StopLossLevel := na
        
    //Calculate Default SL Level
    else 
        ATR_SL = ATR * 2
        if(OrderType == 1)
            StopLossLevel := close[0] - ATR_SL
        else if (OrderType == -1)
            StopLossLevel := close[0] + ATR_SL
        else 
            StopLossLevel := na
    
    StopLossLevel

//== Calculate StopLoss Distance ==
// @function Calculate StopLoss Distance In Pips
// @param OrderType Is To Determine A Long / Short Position, Defval = 1
// @param Entry Is The Entry Level Of The Order, NEED TO INPUT PARAM
// @param StopLoss Level Based On Previous Calculation, NEED TO INPUT PARAM
// @returns Float - The StopLoss Value In Pips
export CalculateStopLossDistance(int OrderType = 1, float Entry, float StopLoss) =>
    //Stop Loss Export Value
    float StopLossDistance = na

    //Calculate Inputed SL Level 
    if(not na(Entry) and not na(StopLoss))
        if(OrderType == 1)
            StopLossDistance := Entry - StopLoss
        else if (OrderType == -1)
            StopLossDistance := StopLoss - Entry
        else 
            StopLossDistance := na
    
    StopLossDistance

//== Calculate TakeProfit Level ==
// @function Calculate TakeProfit Level
// @param OrderType Is To Determine A Long / Short Position, Defval = 1
// @param Entry Is The Entry Level Of The Order, Defval = na
// @param TakeProfit Is The Custom TakeProfit Distance, Defval = 1 RRR Distance
// @returns Float  - The TakeProfit Level In Actual Price
export CalculateTakeProfitLevel(int OrderType = 1, float Entry = na, float StopLossDistance = na, float RiskReward = na) =>
    //Stop Loss Export & ATR Value
    float TakeProfitLevel = na
    ATR = ta.atr(20)

    //Calculate Inputed SL Level 
    if(not na(Entry) and not na(StopLossDistance) and not na(RiskReward))
        if(OrderType == 1)
            TakeProfitLevel := Entry + (StopLossDistance * RiskReward)
        else if (OrderType == -1)
            TakeProfitLevel := Entry - (StopLossDistance * RiskReward)
        else 
            TakeProfitLevel := na
        
    //Calculate Default SL Level
    else 
        ATR_SL = ATR * 2
        if(OrderType == 1)
            SLDistance = close[0] - (close[0] - ATR_SL)
            TakeProfitLevel := close[0] + (SLDistance * 1)
        else if (OrderType == -1)
            SLDistance = (close[0] + ATR_SL) - close[0]
            TakeProfitLevel := close[0] - (SLDistance * 1)
        else 
            TakeProfitLevel := na
    
    TakeProfitLevel

//== Calculate Take Profit Distance ==
// @function Get TakeProfit Distance In Pips
// @param OrderType Is To Determine A Long / Short Position, Defval = 1
// @param Entry Is The Entry Level Of The Order, NEED TO INPUT PARAM
// @param TakeProfit Level Based On Previous Calculation, NEED TO INPUT PARAM
// @returns Float - The TakeProfit Value In Pips
export CalculateTakeProfitDistance(int OrderType = 1, float Entry, float TakeProfit) =>
    //Stop Loss Export Value
    float TakeProfitDistance = na

    //Calculate Inputed SL Level 
    if(not na(Entry) and not na(TakeProfit))
        if(OrderType == 1)
            TakeProfitDistance := TakeProfit - Entry
        else if (OrderType == -1)
            TakeProfitDistance :=  Entry - TakeProfit
        else 
            TakeProfitDistance := na
    
    TakeProfitDistance

//== Calculate Conversion Currency ==
// @function Get The Conversion Currecny Between Current Account Currency & Current Pair's Quoted Currency (FOR FOREX ONLY)
// @param AccountCurrency Is For The Account Currency Used 
// @param SymbolCurrency Is For The Current Symbol Currency (Front Symbol)
// @param BaseCurrency Is For The Current Symbol Base Currency (Back Symbol)
// @returns Tuple Of A Bollean (Convert The Currency ?) And A String (Converted Currency) 
export CalculateConversionCurrency(simple string AccountCurrency, simple string SymbolCurrency, simple string BaseCurrency) =>
    AccountSameAsSymbol = AccountCurrency == SymbolCurrency
    AccountSameAsBase = AccountCurrency == BaseCurrency
    AccountNeither = not AccountSameAsSymbol and not AccountSameAsBase
    ConversionCurrency = AccountSameAsBase ? syminfo.tickerid:AccountCurrency + SymbolCurrency
    ConvertCurrency = AccountSameAsBase or AccountNeither

    [ConvertCurrency,ConversionCurrency]

//== Calculate Conversion Currency Rate ==
// @function Get The Conversion Rate Between Current Account Currency & Current Pair's Quoted Currency (FOR FOREX ONLY)
// @param ConvertCurrency Is To Check If The Current Symbol Needs To Be Converted Or Not
// @param ConversionRate Is The Quoted Price Of The Conversion Currency (Input The request.security Function Here)
// @returns Float Price Of Conversion Rate (If In The Same Currency Than Return Value Will Be 1.0)
export CalculateConversionRate(bool ConvertCurrency, float ConversionRate) => ConversionCurrencyRate = ConvertCurrency ? ConversionRate:1.0

//== Calculate Lot Sizing ==
// @function Get Current Lot Size 
// @param LotSizeSimple Is To Toggle Lot Sizing Calculation (Simple Is Good Enough For Stocks & Crypto, Whilst Complex Is For Forex)
// @param Balance Is For The Current Account Balance To Calculate The Lot Sizing Based Off
// @param Risk Is For The Current Risk Per Trade To Calculate The Lot Sizing Based Off
// @param SLDistance Is The Current Position StopLoss Distance From Its Entry Price
// @param ConversionRate Is The Currency Conversion Rate (Used For Complex Lot Sizing Only)
// @returns Float - Position Size In Units 
export LotSize(bool LotSizeSimple = false, float Balance = 100000, float Risk = 1, float SLDistance, float ConversionRate = 0) =>
    float LotSize = na
    
    //Simple Lot Sizing
    if((LotSizeSimple and ConversionRate == 0) or syminfo.type != "forex")
        LotSize := math.floor((Balance * (Risk / 100)) / (SLDistance))

    //Complex (FOREX) Lot Sizing
    else if(not LotSizeSimple and ConversionRate != 0 and syminfo.type == "forex")
        RiskPerTrade = (Risk / 100) * Balance * ConversionRate
        SLDistance_Points = ToWhole(SLDistance) * 10
        RiskPerPoint = SLDistance_Points * syminfo.pointvalue
        LotSize := math.round(RiskPerTrade / RiskPerPoint / syminfo.mintick)
    
    //Adjust LotSizing To Lots / Units
    LotSize := LotSize

//== Convert Units To Lots ==
// @function Converts Units To Lots
// @param Units Is For How Many Units Need To Be Converted Into Lots (Minimun 1000 Units)
// @returns Float - Position Size In Lots
export ToLots(float Units) => UnitsToLots = math.round(Units / 100000,2)

//== Convert Lots To Units ==
// @function Converts Lots To Units 
// @param Lots Is For How Many Lots Need To Be Converted Into Units (Minimun 0.01 Units)
// @returns Int - Position Size In Units
export ToUnits(float Lots) => LotsToUnits = math.round(Lots * 100000,0)

//== Convert Units To Lots To Units ==
// @function Converts Units To Lots Than Back To Units
// @param Units Is For How Many Units Need To Be Converted Into Lots (Minimun 1000 Units)
// @returns Float - Position Size In Lots That Were Rounded To Units
export ToLotsInUnits(float Units) => UnitsToLots = math.round(Units / 100000,2) * 100000

//== Calculate ATR Trailing Stop ==
// @function Calculate ATR Trailing Stop 
// @param OrderType Is To Determine A Long / Short Position, Defval = 1 
// @param SourceType Is To Determine Where To Calculate The ATR Trailing From, Defval = close
// @param ATRPeriod Is To Change Its ATR Period, Defval = 20
// @param ATRMultiplyer Is To Change Its ATR Trailing Distance, Defval = 1
// @param SwingLookback Is To Change Its Swing HiLo Lookback (Only From Source Type 5), Defval = 7
// @returns Float - Number Of The Current ATR Trailing 
export ATRTrail(int OrderType = 1, int SourceType = 4,simple int ATRPeriod = 20, float ATRMultiplyer = 1, int SwingLookback = 7) =>
    ATR = ta.atr(ATRPeriod) * ATRMultiplyer
    SwingLow = ta.lowest(SwingLookback)
    SwingHigh = ta.highest(SwingLookback)
    float ATRTrailing = na
    float NewATRTrailing = switch SourceType
        1 => OrderType == 1 ? open - ATR : OrderType == -1 ? open + ATR : na
        2 => OrderType == 1 ? high - ATR : OrderType == -1 ? high + ATR : na
        3 => OrderType == 1 ? low - ATR : OrderType == -1 ? low + ATR : na
        4 => OrderType == 1 ? close - ATR : OrderType == -1 ? close + ATR : na
        5 => OrderType == 1 ? SwingLow - ATR : OrderType == -1 ? SwingHigh + ATR : na
        => OrderType == 1 ? close - ATR : OrderType == -1 ? close + ATR : na

    ATRTrailing := NewATRTrailing

//== Calculate Danger Zone ==
// @function Calculate Danger Zone Of A Given Strategy
// @param WinRate Is The Strategy WinRate
// @param AvgRRR Is The Strategy Avg RRR
// @param Filter Is The Minimum Profit It Needs To Be Out Of BE Zone, Defval = 3
// @returns Int - Value, 1 If Out Of Danger Zone, 0 If BE, -1 If In Danger Zone
export DangerZone(float WinRate, float AvgRRR, float Filter = 3) =>
    LossTrades = 100 - WinRate
    WinTrades = WinRate * AvgRRR
    DangerZone = WinTrades - LossTrades > Filter ? 1 : WinTrades - LossTrades < (Filter * -1) ? -1 : 0

//== Check For Questionable Trades ==
// @function Checks For Questionable Trades (Which Are Trades That Its TP & SL Level Got Hit At The Same Candle)
// @param TradeTP Is The Trade In Question Take Profit Level
// @param TradeSL Is The Trade In Question Stop Loss Level
// @returns Bool - True If The Last Trade Was A "Questionable Trade"
export IsQuestionableTrades(float TradeTP, float TradeSL) =>
    PrevBuyTrade = strategy.position_size[0] == 0 and strategy.position_size[1] > 0
    PrevSellTrade = strategy.position_size[0] == 0 and strategy.position_size[1] < 0
    BuyTPSL = PrevBuyTrade and high[0] > TradeTP[1] and low < TradeSL[1]
    SellTPSL = PrevSellTrade and high[0] > TradeSL[1] and low < TradeTP[1]

    QuestionableTrades = BuyTPSL or SellTPSL ? true:false

//======================================================================================================================================================================
// Custom Strategy Functions
//======================================================================================================================================================================

//== Strategy Open Long ==
// @function Open A Long Order Based On The Given Params
// @param EntryID Is The Trade Entry ID, Defval = "Long"
// @param LotSize Is The Lot Size Of The Trade, Defval = 1
// @param LimitPrice Is The Limit Order Price To Set The Order At, Defval = Na / Market Order Execution
// @param StopPrice Is The Stop Order Price To Set The Order At, Defval = Na / Market Order Execution
// @param Comment Is The Order Comment, Defval = Long Entry Order
// @param CommentValue Is For Custom Values In The Order Comment, Defval = Na
// @returns Void
export OpenLong(string EntryID = "Long", float LotSize = 1, float LimitPrice = na, float StopPrice = na, string Comment = "Long Entry Order", string CommentValue = "") =>
    strategy.entry(id =  EntryID, direction = strategy.long, qty = LotSize, limit = LimitPrice, stop = StopPrice, comment = Comment + CommentValue)

//== Strategy Open Short ==
// @function Open A Short Order Based On The Given Params
// @param EntryID Is The Trade Entry ID, Defval = "Short"
// @param LotSize Is The Lot Size Of The Trade, Defval = 1
// @param LimitPrice Is The Limit Order Price To Set The Order At, Defval = Na / Market Order Execution
// @param StopPrice Is The Stop Order Price To Set The Order At, Defval = Na / Market Order Execution
// @param Comment Is The Order Comment, Defval = Short Entry Order
// @param CommentValue Is For Custom Values In The Order Comment, Defval = Na
// @returns Void
export OpenShort(string EntryID = "Short", float LotSize = 1, float LimitPrice = na, float StopPrice = na, string Comment = "Short Entry Order", string CommentValue = "") =>
    strategy.entry(id =  EntryID, direction = strategy.short, qty = LotSize, limit = LimitPrice, stop = StopPrice, comment = Comment + CommentValue)

//== Strategy Exit By TP / SL ==
// @function Exits Based On Predetermined TP & SL Levels
// @param FromID Is The Trade ID That The TP & SL Levels Be Palced 
// @param TPLevel Is The Take Profit Level
// @param SLLevel Is The StopLoss Level
// @param PercentageClose Is The Amount To Close The Order At (In Percentage) Defval = 100
// @param Comment Is The Order Comment, Defval = Exit Order
// @param CommentValue Is For Custom Values In The Order Comment, Defval = Na 
// @returns Void
export TP_SLExit(string FromID, float TPLevel, float SLLevel, float PercentageClose = 100, string Comment = "Exit Order", string CommentValue = "") =>
    strategy.exit(id = TPLevel > SLLevel ? "Long Exit":"Short Exit", from_entry = FromID, limit = TPLevel, stop = SLLevel, qty_percent = PercentageClose, comment = Comment + CommentValue)

//== Strategy Exit Long On Condition == 
// @function Exits A Long Order Based On A Specified Condition
// @param ExitID Is The Trade ID That Will Be Closed, Defval = "Long"
// @param PercentageClose Is The Amount To Close The Order At (In Percentage) Defval = 100
// @param Comment Is The Order Comment, Defval = Exit Order
// @param CommentValue Is For Custom Values In The Order Comment, Defval = Na 
// @param Instant Is For Exit Execution Type, Defval = false
// @returns Void
export CloseLong(string ExitID = "Long", float PercentageClose = 100, string Comment = "Long Exit Order", string CommentValue = "", bool Instant = false) =>
    strategy.close(id = ExitID, qty_percent = PercentageClose, comment =  Comment + CommentValue, immediately = Instant)

//== Strategy Exit Short On Condition == 
// @function Exits A Short Order Based On A Specified Condition
// @param ExitID Is The Trade ID That Will Be Closed, Defval = "Short"
// @param PercentageClose Is The Amount To Close The Order At (In Percentage) Defval = 100
// @param Comment Is The Order Comment, Defval = Exit Order
// @param CommentValue Is For Custom Values In The Order Comment, Defval = Na 
// @param Instant Is For Exit Execution Type, Defval = false
// @returns Void
export CloseShort(string ExitID = "Short", float PercentageClose = 100, string Comment = "Short Exit Order", string CommentValue = "", bool Instant = false) =>
    strategy.close(id = ExitID, qty_percent = PercentageClose, comment =  Comment + CommentValue, immediately = Instant)

//== Broker Check ==
// @function Checks Traded Broker With Current Loaded Chart Broker
// @param Broker Is The Current Broker That Is Traded 
// @returns Bool - True If Current Traded Broker Is Same As Loaded Chart Broker
export BrokerCheck(string Broker) => SameBroker = str.contains(syminfo.tickerid,Broker)

//== Pine Connector Open Position ==
// @function Compiles Given Parameters Into An Alert String Format To Open Trades Using Pine Connector 
// @param LicenseID Is The Users PineConnector LicenseID
// @param OrderType Is The Desired OrderType To Open
// @param UseLimit Is If We Want To Enter Using A Limit Order
// @param UseStop Is If We Want To Enter Using A Stop Order
// @param Price Is The Limit Price Of The Trade (Only For Pending Orders)
// @param SymbolPrefix Is The Current Symbol Prefix (If Any)
// @param Symbol Is The Traded Symbol
// @param SymbolSuffix Is The Current Symbol Suffix (If Any)
// @param Risk Is The Trade Risk Per Trade / Fixed Lot Sizing
// @param SL Is The Trade SL In Price / In Pips
// @param TP Is The Trade TP In Price / In Pips
// @param OrderComment Is The Executed Trade Comment
// @param Spread is The Maximum Spread For Execution
// @returns String - Pine Connector Order Syntax Alert Message
export OpenPC(string LicenseID, int OrderType, bool UseLimit, bool UseStop, float Price, string SymbolPrefix, string Symbol, string SymbolSuffix, float Risk, float SL, float TP, string OrderComment, float Spread) =>
    MarketOrder = OrderType == 1 ? "buy" : OrderType == -1 ? "sell" : na
    PendingOrder = UseLimit ? MarketOrder + "limit" : UseStop ? MarketOrder + "stop" : MarketOrder
    StringOrderType = PendingOrder
    PendingPrice = UseLimit or UseStop ? ",price=" + str.tostring(Truncate(Price,GetDecimals())) : ""
    CurrentSymbol = SymbolPrefix + Symbol + SymbolSuffix
    PCAlert = LicenseID + "," + StringOrderType + "," + CurrentSymbol + PendingPrice + ",risk=" + str.tostring(Risk,'#.##') + ",sl=" + str.tostring(SL) + ",tp=" + str.tostring(TP) + ",comment=" + "\"" + OrderComment + "\"" + ",spread=" + str.tostring(Spread)

//== PineConnector Close Position ==
// @function Compiles Given Parameters Into An Alert String Format To Close Trades Using Pine Connector 
// @param LicenseID Is The Users PineConnector LicenseID
// @param OrderType Is The Desired OrderType To Close
// @param SymbolPrefix Is The Current Symbol Prefix (If Any)
// @param Symbol Is The Traded Symbol
// @param SymbolSuffix Is The Current Symbol Suffix (If Any)
// @param OrderComment Is The Executed Trade Comment For Multi Strategy Implementation
// @returns String - Pine Connector Order Syntax Alert Message
export ClosePC(string LicenseID, int OrderType, string SymbolPrefix, string Symbol, string SymbolSuffix, string OrderComment = "")=>
    StringOrderType = OrderType == 1 ? "closelong" : OrderType == -1 ? "closeshort" : na
    CurrentSymbol = SymbolPrefix + Symbol + SymbolSuffix
    AlertComment = OrderComment == "" ? na : ",comment=" + "\"" + OrderComment + "\""
    PCAlert = LicenseID + "," + StringOrderType + "," + CurrentSymbol + AlertComment

//== PineConnector Cancel Position ==
// @function Compiles Given Parameters Into An Alert String Format To Cancel Trades Using Pine Connector 
// @param LicenseID Is The Users PineConnector LicenseID
// @param OrderType Is The Desired OrderType To Close
// @param SymbolPrefix Is The Current Symbol Prefix (If Any)
// @param Symbol Is The Traded Symbol
// @param SymbolSuffix Is The Current Symbol Suffix (If Any)
// @param OrderComment Is The Executed Trade Comment For Multi Strategy Implementation
// @returns String - Pine Connector Order Syntax Alert Message
export CancelPC(string LicenseID, int OrderType, string SymbolPrefix, string Symbol, string SymbolSuffix, string OrderComment = "") =>
    StringOrderType = OrderType == 1 ? "cancellong" : OrderType == -1 ? "cancelshort" : na
    CurrentSymbol = SymbolPrefix + Symbol + SymbolSuffix
    AlertComment = OrderComment == "" ? na : ",comment=" + "\"" + OrderComment + "\""
    PCAlert = LicenseID + "," + StringOrderType + "," + CurrentSymbol + AlertComment

//======================================================================================================================================================================
// Custom Backtesting Calculation Functions
//======================================================================================================================================================================

//== Calculate Trade PNL ==
// @function Calculates Trade PNL Based On Entry, Eixt & Lot Size
// @param EntryPrice Is The Trade Entry
// @param ExitPrice Is The Trade Exit
// @param LotSize Is The Trade Sizing
// @param ConversionRate Is The Currency Conversion Rate (Used For Complex Lot Sizing Only)
// @returns Float - The Current Trade PNL
export CalculatePNL(float EntryPrice, float ExitPrice, float LotSize, float ConversionRate) => TradePNL = Truncate(Truncate((ExitPrice - EntryPrice) * LotSize,2) / ConversionRate,2)

//== Update Balance ==
// @function Updates The Previous Ginve Balance To The Next PNL
// @param PrevBalance Is The Previous Balance To Be Updated
// @param PNL Is The Current Trade PNL To Be Added
// @returns Float - The Current Updated PNL
export UpdateBalance(float PrevBalance, float PNL) => NewBalance = Truncate(PrevBalance + PNL,2)

//== Calculate Slippage & Commisions ==
// @function Calculates Random Slippage & Commisions Fees Based On The Parameters
// @param PNL Is The Current Trade PNL 
// @param MaxRate Is The Upper Limit (In Percentage) Of The Randomized Fee
// @returns Float - A Percentage Fee Of The Current Trade PNL
export CalculateSlpComm(float PNL, float MaxRate = 0.025) =>
    MaxRatePercent = MaxRate / 100
    MinRatePercent = 0.005 / 100
    RandomRate = math.random(MinRatePercent,MaxRate)

    SlpComm = Truncate(math.abs(PNL * RandomRate))

//== Calculate & Than Update DD Statistics == 
// @function Calculates & Updates The DD Based On Its Given Parameters
// @param MaxBalance Is The Maximum Balance Ever Recorded
// @param Balance Is The Current Account Balance
// @returns Float - The Current Strategy DD
export UpdateDD(float MaxBalance, float Balance) => Drawdown = Truncate(((MaxBalance-Balance) / MaxBalance) * 100,2)

//== Calculate Total Trades, Long Trades, And Short Trades Win Rate ==
// @function Calculate The Total, Long & Short Trades Win Rate
// @param TotalTrades Are The Current Total Trades That The Strategy Has Taken
// @param LongID Is The Order ID Of The Long Trades Of The Strategy
// @param  ShortID Is The Order ID Of The Short Trades Of The Strategy
// @returns Tuple Of Long WR%, Short WR%, Total WR%, Total Winning Trades, Total Losing Trades, Total Long Trades & Total Short Trades
export CalculateWR(int TotalTrades, string LongID, string ShortID) =>
    //Declare Variables
    LongTradeCount = 0
    ShortTradeCount = 0
    LongTradeWin = 0
    LongTradeLoss = 0
    ShortTradeWin = 0
    ShortTradeLoss = 0

    //Loop Through All Closed Trades
    for i = 0 to TotalTrades
        if strategy.closedtrades.entry_id(i - 1) == LongID
            if strategy.closedtrades.profit(i - 1) > 0
                LongTradeCount += 1
                LongTradeWin += 1
            else if strategy.closedtrades.profit(i - 1) < 0
                LongTradeCount += 1
                LongTradeLoss += 1
        
        if strategy.closedtrades.entry_id(i - 1) == ShortID
            if strategy.closedtrades.profit(i - 1) > 0
                ShortTradeCount += 1
                ShortTradeWin += 1
            else if strategy.closedtrades.profit(i - 1) < 0
                ShortTradeCount += 1
                ShortTradeLoss += 1
    
    //Calculate Each WR
    LongWR = Truncate((LongTradeWin / LongTradeCount) * 100,2)
    ShortWR = Truncate((ShortTradeWin / ShortTradeCount) * 100,2)
    TotalWR = Truncate(((LongTradeWin + ShortTradeWin) / TotalTrades) * 100,2)
    TotalWinTrades = LongTradeWin + ShortTradeWin
    TotalLossTrades = TotalTrades - TotalWinTrades

    [LongWR, ShortWR, TotalWR, TotalWinTrades, TotalLossTrades, LongTradeCount, ShortTradeCount]

//== Calculate The Avg RRR ==
// @function Calculates The Overall Strategy Avg Risk Reward Ratio
// @param WinTrades Are The Strategy Winning Trades
// @param LossTrades Are The Strategy Losing Trades
// @returns Float - The Average RRR Values
export CalculateAvgRRR(int WinTrades, int LossTrades) =>
    AvgWinArray = array.new_float(WinTrades, 0)
    AvgLossArray = array.new_float(LossTrades, 0)

    for i = 0 to WinTrades + LossTrades
        if strategy.closedtrades.profit(i - 1) > 0
            array.push(AvgWinArray,strategy.closedtrades.profit(i - 1))
        else if strategy.closedtrades.profit(i - 1) < 0
            array.push(AvgLossArray,strategy.closedtrades.profit(i - 1))
        
    AvgWin = array.avg(AvgWinArray)
    AvgLoss = array.avg(AvgLossArray) * -1
    AvgRRR = Truncate(AvgWin / AvgLoss,2)

//== Calculate CAGR ==
// @function Calculates The CAGR Over The Given Time Period © TradingView
// @param StartTime Is The Starting Time Of The Calculation
// @param StartPrice Is The Starting Price Of The Calculation
// @param EndTime Is The Ending Time Of The Calculation
// @param EndPrice Is The Ending Price Of The Calculation
// @returns Float - The CAGR Values 
export CAGR(int StartTime, float StartPrice, int EndTime, float EndPrice) => Truncate(Tv.cagr(StartTime,StartPrice,EndTime,EndPrice) ,2) 

//======================================================================================================================================================================
// Custom Plot Functions
//======================================================================================================================================================================

//== Modify Labels ==
// @function Edit / Delete Labels
// @param LabelID Is The ID Of The Selected Label
// @param X1 Is The X1 Coordinate IN BARINDEX Xloc
// @param Y1 Is The Y1 Coordinate IN PRICE Yloc
// @param Text Is The Text Than Wants To Be Written In The Label
// @param Color Is The Color Value Change Of The Label
// @param Color Is The Color Value Change Of The Label Text
// @param EditCondition Is The Edit Condition of The Line (Setting Location / Color)
// @param DeleteCondition Is The Delete Condition Of The Line If Ture Deletes The Prev Itteration Of The Line
// @returns Void
export EditLabels(series label LabelID, int X1, float Y1, string Text, color Color, color TextColor, int EditCondition, bool DeleteCondition) =>
    if(EditCondition == 1)
        label.set_x(LabelID, X1)
        label.set_y(LabelID, Y1)
    else if(EditCondition == 2)
        label.set_text(LabelID, Text)
    else if(EditCondition == 3)
        label.set_color(LabelID, Color)
    else if(EditCondition == 4)
        label.set_textcolor(LabelID, TextColor)
    else if(DeleteCondition)
        label.delete(LabelID[1])

//== Modify Lines ==
// @function Edit / Delete Lines
// @param LineID Is The ID Of The Selected Line
// @param X1 Is The X1 Coordinate IN BARINDEX Xloc
// @param Y1 Is The Y1 Coordinate IN PRICE Yloc
// @param X2 Is The X2 Coordinate IN BARINDEX Xloc
// @param Y2 Is The Y2 Coordinate IN PRICE Yloc
// @param Color Is The Color Value Change Of The Line
// @param EditCondition Is The Edit Condition of The Line (Setting Location / Color)
// @param DeleteCondition Is The Delete Condition Of The Line If Ture Deletes The Prev Itteration Of The Line
// @returns Void
export EditLine(line LineID, int X1, float Y1, int X2, float Y2, color Color, int EditCondition, bool DeleteCondition) =>
    if(EditCondition == 1)
        line.set_x1(LineID, X1)
        line.set_y1(LineID, Y1)
        line.set_x2(LineID, X2)
        line.set_y2(LineID, Y2)
    else if(EditCondition == 2)
        line.set_color(LineID, Color)
    else if(DeleteCondition)
        line.delete(LineID[1])

//======================================================================================================================================================================
// Custom Display Functions (Using Tables)
//======================================================================================================================================================================

//== Fill Table Function ==
// @function Filling The Selected Table With The Inputed Information
// @param TableID Is The Table ID That Wants To Be Edited
// @param Column Is The Current Column Of The Table That Wants To Be Edited
// @param Row Is The Current Row Of The Table That Wants To Be Edited 
// @param Title Is The String Title Of The Current Cell Table
// @param Value Is The String Value Of The Current Cell Table
// @param BgColor Is The Selected Color For The Current Table
// @param TextColor Is The Selected Color For The Current Table
// @param ToolTip Is The ToolTip Of The Current Cell In The Table
// @returns Void
export FillTable(table TableID, int Column, int Row, string Title, string Value, color BgColor, color TextColor, string ToolTip) =>
    CellText = Title + "\n" + Value
    table.cell(table_id = TableID, column = Column, row = Row, text = CellText, bgcolor = BgColor, text_color = TextColor, tooltip = ToolTip)

//== Display Backtest Results Function ==
// @function Filling The Selected Table With The Inputed Information
// @param TableID Is The Table ID That Wants To Be Edited
// @param BgColor Is The Selected Color For The Current Table
// @param TextColor Is The Selected Color For The Current Table
// @param StartingBalance Is The Account Starting Balance
// @param DollarReturn Is The Account Dollar Reture
// @param TotalPips Is The Total Pips Gained / loss
// @param MaxDD Is The Maximum Drawdown Over The Backtesting Period
// @returns Void
export DisplayBTResults(table TableID, color BgColor, color TextColor, float StartingBalance, float Balance, float DollarReturn, float TotalPips, float MaxDD) =>
    FillTable(TableID, 0, 0, "Total Trades:", str.tostring(strategy.closedtrades), BgColor, TextColor, "")
    FillTable(TableID, 0, 1, "Win Rate:", str.tostring(Truncate(strategy.wintrades / strategy.closedtrades * 100)) + "%", BgColor, TextColor, "")
    FillTable(TableID, 1, 0, "Starting Balance:", "$" + str.tostring(StartingBalance), BgColor, TextColor, "")
    FillTable(TableID, 1, 1, "Ending Balance:", "$" + str.tostring(Truncate(Balance)), BgColor, TextColor, "")
    FillTable(TableID, 2, 0, "Total Returns:", str.tostring((Truncate(DollarReturn))), BgColor, TextColor, "")
    FillTable(TableID, 2, 1, "Total Pips:", (TotalPips > 0 ? "+" : "") + str.tostring(Truncate(TotalPips)), BgColor, TextColor, "")
    FillTable(TableID, 3, 0, "Return In %:", (DollarReturn > 0 ? "+" : "") + str.tostring(Truncate(DollarReturn / StartingBalance * 100)) + "%", DollarReturn > 0 ? color.green : color.red, TextColor, "")
    FillTable(TableID, 3, 1, "Max DD:", str.tostring(Truncate(MaxDD * 100)) + "%", color.red, TextColor, "")

//== Display Backtest Results Function Version 2==
// @function Filling The Selected Table With The Inputed Information
// @param TableID Is The Table ID That Wants To Be Edited
// @param BgColor Is The Selected Color For The Current Table
// @param TextColor Is The Selected Color For The Current Table
// @param TotalWR Is The Strategy Total WR In %
// @param QTCount Is The Strategy Questionable Trades Count
// @param LongWR Is The Strategy Total WR In %
// @param ShortWR Is The Strategy Total WR In %
// @param InitialCapital Is The Strategy Initial Starting Capital
// @param CumProfit Is The Strategy Ending Cumulative Profit
// @param CumFee Is The Strategy Ending Cumulative Fee (Based On Randomized Fee Assumptions)
// @param AvgRRR Is The Strategy Average Risk Reward Ratio
// @param MaxDD Is The Strategy Maximum DrawDown In Its Backtesting Period
// @param CAGR Is The Strategy Compounded Average GRowth In %
// @param MeanDD Is The Strategy Mean / Average Drawdown In The Backtesting Period
// @returns Void
export DisplayBTResultsV2(table TableID, color BgColor, color TextColor, float TotalWR, int QTCount, float LongWR, float ShortWR, float InitialCapital, float CumProfit, float CumFee, float AvgRRR, float MaxDD, float CAGR, float MeanDD) =>
    //First Block
    FillTable(TableID, 0, 0, "Total Trades:", str.tostring(strategy.closedtrades), BgColor, TextColor, "Questionable Trade Count : " + str.tostring(QTCount))
    FillTable(TableID, 1, 0, "Total WR%:", str.tostring(TotalWR) + "%", DangerZone(TotalWR, AvgRRR) == 1 ? color.green:DangerZone(TotalWR, AvgRRR) == -1 ? color.red:color.orange, TextColor, "Total Trades Win Rate In %")
    FillTable(TableID, 0, 1, "Long WR%:", str.tostring(LongWR) + "%", color.green, TextColor, "Long ONLY Trades Winning Rate In %")
    FillTable(TableID, 1, 1, "Short WR%:", str.tostring(ShortWR) + "%", color.red, TextColor, "Short ONLY Trades Winning Rate In %")
    //Second Block
    FillTable(TableID, 2, 0, "Starting Balance:", "$" + str.tostring(InitialCapital), BgColor, TextColor, "Strategy Initial Starting Balance In Base Currency")
    FillTable(TableID, 3, 0, "Return In %:", (CumProfit > 0 ? "+" : "") + str.tostring(Truncate(CumProfit / InitialCapital * 100)) + "%", CumProfit > 0 ? color.green:color.red, TextColor, "Strategy Total Returns In %")
    FillTable(TableID, 2, 1, "Total Returns:", "$" + str.tostring(CumProfit), CumProfit > 0 ? color.green:color.red, TextColor, "Strategy Total Returns In Base Currency")
    FillTable(TableID, 3, 1, "Total Fees:", "$" + str.tostring(CumFee), color.red, TextColor, "Total Approximate Slippage & Commisions Fees In Base Currency")
    //Third Block
    FillTable(TableID, 4, 0, "Avg RRR:", str.tostring(AvgRRR), DangerZone(TotalWR, AvgRRR) == 1 ? color.green:DangerZone(TotalWR, AvgRRR) == -1 ? color.red:color.orange, TextColor, "Average Risk Reward Ratio Of The Strategy")
    FillTable(TableID, 5, 0, "MaxDD In %:", str.tostring(MaxDD) + "%", color.red, TextColor, "Max Draw Down Of The Current Strategy In %")
    FillTable(TableID, 4, 1, "CAGR %:", (CAGR > 0 ? "+" : "") + str.tostring(CAGR) + "%", CAGR > 0 ? color.green:color.red, TextColor, "Compounded Average GRowth Of The Strategy In %")   
    FillTable(TableID, 5, 1, "CAGR/MeanDD :", str.tostring(Truncate(CAGR/MeanDD)), (CAGR/MeanDD) > 1 ? color.green:(CAGR/MeanDD) < -1 ?color.red:color.orange, TextColor, "Mean DD = " + str.tostring(MeanDD))

//======================================================================================================================================================================
// Custom Pattern Detection Functions
//======================================================================================================================================================================

//== Calculate Bullish Fibonacci Values ==
// @function Calculates A Bullish Fibonacci Value (From Swing Low To High) © ZenAndTheArtOfTrading
// @param PriceLow The Lowest Price Point
// @param PriceHigh The highest Price Point
// @param FibRatio The Fibonacci % Ratio To Calculate, Defval = 0.382
// @returns The Fibonacci Value Of The Given Ratio Between The Two Price Points
export BullFib(float priceLow, float priceHigh, float fibRatio = 0.382) => (priceLow - priceHigh) * fibRatio + priceHigh

//== Calculate Bearish Fibonacci Values ==
// @function Calculates A Bearish Fibonacci Value (From Swing High To Low) © ZenAndTheArtOfTrading
// @param PriceLow The Lowest Price Point
// @param PriceHigh The Highest Price Point
// @param FibRatio The Fibonacci % Ratio To Calculate
// @returns The Fibonacci Value Of The Given Ratio Between The Two Price Points
export BearFib(float priceLow, float priceHigh, float fibRatio = 0.382) => (priceHigh - priceLow) * fibRatio + priceLow

//== Calculate A Candle Body Size ==
// @function Gets The Current Candle Body Size IN POINTS © ZenAndTheArtOfTrading
// @returns The Current Candle Body Size IN POINTS
export GetBodySize() => math.abs(close - open) / syminfo.mintick

//== Calculate A Candle Upper Wick Size ==
// @function Gets The Current Candle Top Wick Size IN POINTS © ZenAndTheArtOfTrading
// @returns The Current Candle Top Wick Size IN POINTS
export GetTopWickSize() => math.abs(high - (close > open ? close : open)) / syminfo.mintick

//== Calculate A Candle Lower Wick Size ==
// @function Gets The Current Candle Bottom Wick Size IN POINTS © ZenAndTheArtOfTrading
// @returns The Current Candle Bottom Wick Size IN POINTS
export GetBottomWickSize() => math.abs((close < open ? close : open) - low) / syminfo.mintick

//== Calculate A Candle Body Percentage Size ==
// @function Gets The Current Candle Body Size As A Percentage Of Its Entire Size Including Its Wicks © ZenAndTheArtOfTrading
// @returns The Current Candle Body Size IN PERCENTAGE
export GetBodyPercent() => math.abs(open - close) / math.abs(high - low)

//== Calculate A Candle Upper Wick Percentage Size ==
// @function Gets The Current Top Wick Size As A Percentage Of Its Entire Body Size
// @returns Float - The Current Candle Top Wick Size IN PERCENTAGE
export GetTopWickPercent() => GetTopWickSize() / GetBodySize()

//== Calculate A Candle Lower Wick Percentage Size ==
// @function Gets The Current Bottom Wick Size As A Percentage Of Its Entire Bodu Size
// @returns Float - The Current Candle Bottom Size IN PERCENTAGE
export GetBottomWickPercent() => GetBottomWickSize() / GetBodySize()

//== Bullish Engulfing ==
// @function Checks If The Current Bar Is A Bullish Engulfing Candle
// @param Allowance To Give Flexibility Of Engulfing Pattern Detection In Markets That Have Micro Gaps, Defval = 0
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bullsih Engulfing Pattern, Defval = na
// @param EngulfWick To Specify If We Want The Pattern To Also Engulf Its Upper & Lower Previous Wicks, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing Low, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing Low, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bullish Engulfing Candle
export BullishEC(int Allowance = 0, float RejectionWickSize = na, bool EngulfWick = false, bool NearSwings = true, int SwingLookBack = 10) =>
    //Swing Low Detection
    SwingLow = (low[0] == ta.lowest(SwingLookBack) or low[1] == ta.lowest(SwingLookBack)) or (not NearSwings)

    //Engulfing Pattern Detection
    Engulfing = open[1] > close[1] and open <= (close[1] + Allowance) and close >= (open[1] + Allowance)

    //Also Check If Engulfing The Wick ?
    Engulf_Wick = (close[0] > high[1] and open[0] < low[1]) or (not EngulfWick)

    //Maximum Rejection Wick Size
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Bullish Engulfing
    Bullish_Engulfing = Engulfing and Engulf_Wick and Rejection_Wick and SwingLow
    Bullish_Engulfing

//== Bearish Engulfing ==
// @function Checks If The Current Bar Is A Bearish Engulfing Candle
// @param Allowance To Give Flexibility Of Engulfing Pattern Detection In Markets That Have Micro Gaps, Defval = 0
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bearish Engulfing Pattern, Defval = na
// @param EngulfWick To Specify If We Want The Pattern To Also Engulf Its Upper & Lower Previous Wicks, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing High, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing High, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bearish Engulfing Candle
export BearishEC(int Allowance = 0, float RejectionWickSize = na, bool EngulfWick = false, bool NearSwings = true, int SwingLookBack = 10) =>
    //Swing High Detection
    SwingHigh = (high[0] == ta.highest(SwingLookBack) or high[1] == ta.highest(SwingLookBack)) or (not NearSwings)
    
    //Engulfing Pattern Detection
    Engulfing =  open[1] < close[1] and open >= (close[1] - Allowance) and close <= (open[1] + Allowance)

    //Also Check If Engulfing The Wick ?
    Engulf_Wick = (close[0] < low[1] and open[0] > high[1]) or (not EngulfWick)

    //Maximum Rejection Wick Size
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Bearish Engulfing
    Bearish_Engulfing = Engulfing and Engulf_Wick and Rejection_Wick and SwingHigh
    Bearish_Engulfing

//== Hammer Pattern ==
// @function Checks If The Current Bar Is A Hammer Candle
// @param Fib To Specify Which Fibonacci Ratio To Use When Determining The Hammer Candle, Defval = 0.382 Ratio
// @param ColorMatch To Filter Only Bullish Closed Hammer Candle Pattern, Defval = false
// @param NearSwings To Specify If We Want The Doji To Be Near A Recent Swing Low, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing Low, Defval = 10
// @param ATRFilterCheck To Filter Smaller Hammer Candles That Might Be Better Classified As A Doji Candle, Defval = 1
// @param ATRPeriod To Change ATR Period Of The ATR Filter, Defval = 20
// @returns Bool - True If The Current Bar Matches The Requirements of a Hammer Candle
export Hammer(float Fib = 0.382, bool ColorMatch = false, bool NearSwings = true, int SwingLookBack = 10, float ATRFilterCheck = 1, int ATRPeriod = 20) =>
    //Swing HiLo & Lowest Body
    SwingLow = (low[0] == ta.lowest(SwingLookBack)) or (not NearSwings)
    LowestBody = (close > open ? open:close)

    //ATR Filter Check
    ATR = ta.atr(ATRPeriod)
    CandleSize = high - low
    ATRFilter = CandleSize >= (ATR * ATRFilterCheck)

    //Color Match
    ColorCandle = (not ColorMatch or close > open)

    //Hammer Pattern
    HammerPattern = LowestBody >= BullFib(low,high,Fib) and SwingLow and ATRFilter and ColorCandle

//== Stars Pattern ==
// @function Checks If The Current Bar Is A Hammer Candle
// @param Fib To Specify Which Fibonacci Ratio To Use When Determining The Hammer Candle, Defval = 0.382 Ratio
// @param ColorMatch To Filter Only Bullish Closed Hammer Candle Pattern, Defval = false
// @param NearSwings To Specify If We Want The Doji To Be Near A Recent Swing Low, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing Low, Defval = 10
// @param ATRFilterCheck To Filter Smaller Hammer Candles That Might Be Better Classified As A Doji Candle, Defval = 1
// @param ATRPeriod To Change ATR Period Of The ATR Filter, Defval = 20
// @returns Bool - True If The Current Bar Matches The Requirements of a Hammer Candle
export Star(float Fib = 0.382, bool ColorMatch = false, bool NearSwings = true, int SwingLookBack = 10, float ATRFilterCheck = 1, int ATRPeriod = 20) =>
    //Swing HiLo & Lowest Body
    SwingHigh = (high[0] == ta.highest(SwingLookBack)) or (not NearSwings)
    HighestBody = (close > open ? close:open) 

    //ATR Filter Check
    ATR = ta.atr(ATRPeriod)
    CandleSize = high - low
    ATRFilter = CandleSize >= (ATR * ATRFilterCheck)

    //Color Match
    ColorCandle = (not ColorMatch or close < open)

    //Hammer Pattern
    StarPattern = HighestBody <= BearFib(low,high,Fib) and SwingHigh and ATRFilter and ColorCandle

//== Doji Pattern ==
// @function Checks If The Current Bar Is A Doji Candle
// @param MaxWickSize To Specify The Maximum Lenght Of Its Upper & Lower Wick, Defval = 2
// @param MaxBodySize To Specify The Maximum Lenght Of Its Candle Body IN PERCENT, Defval = 0.05
// @param Doji Type To Specify The Type Of Doji That Want To Be Detected, Defval = Netral Doji
// @param NearSwings To Specify If We Want The Doji To Be Near A Recent Swing High / Low (Only In Dragonlyf / Gravestone Mode), Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing High / Low (Only In Dragonlyf / Gravestone Mode), Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Doji Candle
export Doji(float MaxWickSize = 2, float MaxBodySize = 0.05, int DojiType = 0, bool NearSwings = true, int SwingLookBack = 10) =>
    //Swing HiLo
    SwingHigh = (high[0] == ta.highest(SwingLookBack)) or (not NearSwings)
    SwingLow = (low[0] == ta.lowest(SwingLookBack)) or (not NearSwings)

    //Doji Detection
    NetralDoji = GetTopWickSize() <= (GetBottomWickSize() * MaxWickSize) and GetBottomWickSize() <= (GetTopWickSize() * MaxWickSize) and GetBodyPercent() <= MaxBodySize
    Dragonfly = GetTopWickSize() <= (GetBottomWickSize() * MaxWickSize) and GetBottomWickSize() >= (GetTopWickSize() * MaxWickSize) and GetBodyPercent() <= MaxBodySize
    Gravestone = GetTopWickSize() >= (GetBottomWickSize() * MaxWickSize) and GetBottomWickSize() <= (GetTopWickSize() * MaxWickSize) and GetBodyPercent() <= MaxBodySize

    //Export Value Doji
    bool Doji_Candle = na
    if(DojiType == 0)
        Doji_Candle := NetralDoji
    else if(DojiType == 1)
        Doji_Candle := Dragonfly and SwingLow
    else if(DojiType == -1)
        Doji_Candle := Gravestone and SwingHigh
    else 
        Doji_Candle := false
    
    Doji_Candle

//== Bullish Harami Pattern ==
// @function Checks If The Current Bar Is A Bullish Harami Candle
// @param Allowance To Give Flexibility Of Harami Pattern Detection In Markets That Have Micro Gaps, Defval = 0
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bullsih Harami Pattern, Defval = na
// @param EngulfWick To Specify If We Want The Pattern To Also Engulf Its Upper & Lower Previous Wicks, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing Low, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing Low, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bullish Harami Candle
export BullishIB(int Allowance = 0, float RejectionWickSize = na, bool EngulfWick = false, bool NearSwings = true, int SwingLookBack = 10) =>
    //Check Near Swing Low
    SwingLow = (low[0] == ta.lowest(SwingLookBack) or low[1] == ta.lowest(SwingLookBack)) or (not NearSwings)

    //Check Harami Candle
    Harami = open[1] > close[1] and open[0] < close[0] and open >= (close[1] + Allowance) and close <= (open[1] + Allowance)

    //Check Engulf Wick
    Engulf_Wick = (open[1] > high[0] and close[1] < low[0]) or (not EngulfWick)

    //Check Rejection Wick
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Check Bullish Harami
    Bullish_Harami = Harami and Engulf_Wick and Rejection_Wick and SwingLow
    Bullish_Harami

//== Bullish Harami Pattern Version 2==
// @function Checks If The Current Bar Is A Bullish Harami Candle
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bullsih Harami Pattern, Defval = na
// @param IncludeBody To Specify If We Want The Pattern To Also Be "Inside" Its Previous Body Candle And Not Just HiLo Range, Defval = false
// @param ColorMatch To Have A Bullish & Bearish Variaton Similar To Bullish Engulfing, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing Low, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing Low, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bullish Harami Candle Version 2
export BullishIBV2(float RejectionWickSize = na, bool IncludeBody = false, bool ColorMatch = false,bool NearSwings = true, int SwingLookBack = 10) =>
    //Check Near Swing Low
    SwingLow = (low[0] == ta.lowest(SwingLookBack) or low[1] == ta.lowest(SwingLookBack)) or (not NearSwings)

    //Check Harami Candle
    Harami = high[1] > high[0] and low[1] < low[0]

    //ColorMatch
    BullBear = (open[1] > close[1] and open[0] < close[0]) or (not ColorMatch)

    //Check If Use Body Calculation Instead
    BodyCalculation = IncludeBody ? false : true
    if IncludeBody and not BodyCalculation
        HighestBody = open[0] > close[0] ? open[0] : close[0]
        LowestBody = open[0] < close[0] ? open[0] : close[0]
        PrevHighesttBody = open[1] > close[1] ? open[1] : close[1]
        PrevLowestBody = open[1] < close[1] ? open[1] : close[1]

        BodyCalculation := PrevHighesttBody >= HighestBody and PrevLowestBody <= LowestBody

    //Check Rejection Wick
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Check Bullish Harami
    Bullish_Harami = Harami and BullBear and BodyCalculation and Rejection_Wick and SwingLow
    Bullish_Harami

//== Bearish Harami Pattern ==
// @function Checks If The Current Bar Is A Bullish Harami Candle
// @param Allowance To Give Flexibility Of Harami Pattern Detection In Markets That Have Micro Gaps, Defval = 0
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bearish Harami Pattern, Defval = na
// @param EngulfWick To Specify If We Want The Pattern To Also Engulf Its Upper & Lower Previous Wicks, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing High, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing High, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bearish Harami Candle
export BearishIB(int Allowance = 0, float RejectionWickSize = na, bool EngulfWick = false, bool NearSwings = true, int SwingLookBack = 10) =>
    //Check Near Swing High
    SwingHigh = (high[0] == ta.highest(SwingLookBack) or high[1] == ta.highest(SwingLookBack)) or (not NearSwings)

    //Check Bearish Harami
    Harami = open[1] < close[1] and open[0] > close[0] and open <= (close[1] + Allowance) and close >= (open[1] + Allowance)

    //Check Engulf Wick
    Engulf_Wick = (open[1] < low[0] and close[1] > high[0]) or (not EngulfWick)

    //Check Rejection Wick
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Check Bearish Harami
    Bearish_Harami = Harami and Engulf_Wick and Rejection_Wick and SwingHigh
    Bearish_Harami

//== Bearish Harami Pattern Version 2==
// @function Checks If The Current Bar Is A Bearish Harami Candle
// @param RejectionWickSize To Filter Out long (Upper And Lower) Wick From The Bearish Harami Pattern, Defval = na
// @param IncludeBody To Specify If We Want The Pattern To Also Be "Inside" Its Previous Body Candle And Not Just HiLo Range, Defval = false
// @param ColorMatch To Have A Bullish & Bearish Variaton Similar To Bullish Engulfing, Defval = false
// @param NearSwings To Specify If We Want The Pattern To Be Near A Recent Swing High, Defval = true
// @param SwingLookBack To Specify How Many Bars Back To Detect A Recent Swing High, Defval = 10
// @returns Bool - True If The Current Bar Matches The Requirements of a Bearish Harami Candle Version 2
export BearishIBV2(float RejectionWickSize = na, bool IncludeBody = false, bool ColorMatch = false,bool NearSwings = true, int SwingLookBack = 10) =>
    //Check Near Swing High
    SwingHigh = (high[0] == ta.highest(SwingLookBack) or high[1] == ta.highest(SwingLookBack)) or (not NearSwings)

    //Check Harami Candle
    Harami = high[1] > high[0] and low[1] < low[0]

    //ColorMatch
    BearBull = (open[1] < close[1] and open[0] > close[0]) or (not ColorMatch)

    //Check If Use Body Calculation Instead
    BodyCalculation = IncludeBody ? false : true
    if IncludeBody and not BodyCalculation
        HighestBody = open[0] > close[0] ? open[0] : close[0]
        LowestBody = open[0] < close[0] ? open[0] : close[0]
        PrevHighesttBody = open[1] > close[1] ? open[1] : close[1]
        PrevLowestBody = open[1] < close[1] ? open[1] : close[1]

        BodyCalculation := PrevHighesttBody >= HighestBody and PrevLowestBody <= LowestBody

    //Check Rejection Wick
    Rejection_Wick = (GetTopWickPercent() < RejectionWickSize and GetBottomWickPercent() < RejectionWickSize) or (na(RejectionWickSize))

    //Check Bearish Harami
    Bearish_Harami = Harami and BearBull and BodyCalculation and Rejection_Wick and SwingHigh
    Bearish_Harami

//======================================================================================================================================================================
// Custom Time Functions
//======================================================================================================================================================================

//== Bar In Session Checker ==
// @function Determines if the current price bar falls inside the specified session © ZenAndTheArtOfTrading
// @param string sess The session to check
// @param bool useFilter (default=true) Whether or not to actually use this filter
// @returns A boolean - true if the current bar falls within the given time session
export BarInSession(simple string sess, bool useFilter = true) => na(time(timeframe.period, sess + ":1234567")) == false or not useFilter

//== Bar Out Of Session Checker ==
// @function Determines if the current price bar falls outside the specified session © ZenAndTheArtOfTrading
// @param string sess The session to check
// @param bool useFilter (default=true) Whether or not to actually use this filter
// @returns A boolean - true if the current bar falls outside the given time session
export BarOutSession(simple string sess, bool useFilter = true) => na(time(timeframe.period, sess + ":1234567")) or not useFilter

//== Date Checker ==
// @function Determines if this bar's time falls within date filter range © ZenAndTheArtOfTrading
// @param int startTime The UNIX date timestamp to begin searching from
// @param int endTime the UNIX date timestamp to stop searching from
// @returns A boolean - true if the current bar falls within the given dates
export DateFilter(int startTime, int endTime) => time >= startTime and time <= endTime

//== Get Day Of Week Function ==
// @function Checks if the current bar's day is in the list of given days to analyze © ZenAndTheArtOfTrading
// @param bool monday Should the script analyze this day? (true/false)
// @param bool tuesday Should the script analyze this day? (true/false)
// @param bool wednesday Should the script analyze this day? (true/false)
// @param bool thursday Should the script analyze this day? (true/false)
// @param bool friday Should the script analyze this day? (true/false)
// @param bool saturday Should the script analyze this day? (true/false)
// @param bool sunday Should the script analyze this day? (true/false)
// @returns A boolean - true if the current bar's day is one of the given days
export DayFilter(bool monday, bool tuesday, bool wednesday, bool thursday, bool friday, bool saturday, bool sunday) => 
  dayofweek == dayofweek.monday and monday or
  dayofweek == dayofweek.tuesday and tuesday or
  dayofweek == dayofweek.wednesday and wednesday or
  dayofweek == dayofweek.thursday and thursday or
  dayofweek == dayofweek.friday and friday or
  dayofweek == dayofweek.saturday and saturday or
  dayofweek == dayofweek.sunday and sunday

//== Get Australian Session ==
// @function Checks If The Current Australian Forex Session In Running
// @returns Bool - True If Currently The Australian Session Is Running
export AUSSess() => 
    var bool Australian_Session = na
    if(str.tonumber(str.format_time(time,"HH","UTC+0")) == 21 and barstate.isconfirmed)
        Australian_Session := true
    if((str.tonumber(str.format_time(time,"HH","UTC+0")) == 6))
        Australian_Session := false
    Australian_Session

//== Get Asia Session ==
// @function Checks If The Current Asian Forex Session In Running
// @returns Bool - True If Currently The Asian Session Is Running
export ASIASess() => 
    var bool Asian_Session = na
    if(str.tonumber(str.format_time(time,"HH","UTC+0")) == 0 and barstate.isconfirmed)
        Asian_Session := true
    if((str.tonumber(str.format_time(time,"HH","UTC+0")) == 8))
        Asian_Session := false
    Asian_Session

//== Get European Session ==
// @function Checks If The Current European Forex Session In Running
// @returns Bool - True If Currently The European Session Is Running
export EURSess() => 
    var bool European_Session = na
    if(str.tonumber(str.format_time(time,"HH","UTC+0")) == 7 and barstate.isconfirmed)
        European_Session := true
    if((str.tonumber(str.format_time(time,"HH","UTC+0")) == 16))
        European_Session := false
    European_Session

//== Get US Session ==
// @function Checks If The Current US Forex Session In Running
// @returns Bool - True If Currently The US Session Is Running
export USSess() => 
    var bool US_Session = na
    if(str.tonumber(str.format_time(time,"HH","UTC+0")) == 12 and barstate.isconfirmed)
        US_Session := true
    if((str.tonumber(str.format_time(time,"HH","UTC+0")) == 21))
        US_Session := false
    US_Session

//== UNIX To Datetime ==
// @function Converts UNIX Time To Datetime
// @param Time Is The UNIX Time Input
// @param ConversionType Is The Datetime Output Format, Defval = DD-MM-YYYY
// @param TimeZone Is To Convert The Outputed Datetime Into The Specified Time Zone, Defval = Exchange Time Zone
// @returns String - String Of Datetime
export UNIXToDate(int Time, int ConversionType = 1, string TimeZone = na) =>
    SelectedTimeZone = na(TimeZone) ? syminfo.timezone : TimeZone

    ConvertedTime = switch ConversionType
        1 => str.format_time(Time,"dd-MM-yyyy", SelectedTimeZone)
        2 => str.format_time(Time,"MM-dd-yyyy", SelectedTimeZone)
        3 => str.format_time(Time,"yyyy-MM-dd", SelectedTimeZone)
        4 => str.format_time(Time,"dd-MM-yyyy HH:mm", SelectedTimeZone)
        5 => str.format_time(Time,"MM-dd-yyyy HH:mm", SelectedTimeZone)
        6 => str.format_time(Time,"yyyy-MM-ddd HH:mm", SelectedTimeZone)
        7 => str.format_time(Time,"HH:mm:ss", SelectedTimeZone)
        => str.format_time(Time,"yyyy-MM-dd' 'HH:mm:ssZ", SelectedTimeZone)

    ConvertedTime

//======================================================================================================================================================================
// End Of Library
//======================================================================================================================================================================