Hikkake Hunter 2.0
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © SolCollector
// BS Computer Science
// Spencer G.
//
// This is a script that will identify both variants of the Hikkake patterns on 
// a candlestick chart. This reworked version focuses on efficiency and flexibility
// to give the user a better experience when hunting for this specific candlestick
// pattern. It also serves as an educational tool to show the potential value 
// User-Defined-Types (UDTs) may provide when creating complex scripts within the 
// framework of a scripting language. Statistics will be conducted in real time 
// on all patterns identified using this script, and these statistics will be 
// displayed to the user when scrolling over the label of confirmed patterns.

//@version=5
indicator("Hikkake Hunter 2.0", overlay=true, max_labels_count = 100)

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║                            __  ______  ______                             ║
// ║                           / / / / __ \/_  __/____                         ║
// ║                          / / / / / / / / / / ___/                         ║
// ║                         / /_/ / /_/ / / / (__  )                          ║
// ║                         \____/_____/ /_/ /____/                           ║
// ║                                                                           ║
// ║ This script employs 4 different user-defined-types which keep track of    ║
// ║ relevant information that help this script process patterns. The four     ║
// ║ UDTs are:                                                                 ║
//#region                                                                      ║
// ║ returnArray - An object that contains information related to the returns  ║
// ║             of patterns (represented in % form). Each returnArray holds:  ║
// ║                • An array that holds all returns that a pattern has seen  ║
// ║                • The size of that array                                   ║
// ║                • The average of the returns                               ║
// ║                • The median of the returns                                ║
// ║                • The standard deviation of the returns                    ║
// ║                • An array that contains the polarities of all returns*    ║
// ║                                                                           ║
// ║ * Two inputs are related to this: i_NegativeRetTol and i_PositiveRetTol.  ║
// ║ This array holds 3 values: each corresponding to the negative, neutral,   ║
// ║ and positive returns a pattern may yield. If a pattern returns a % below  ║
// ║ i_NegativeRetTol, the value tracking negative returns will be incremented.║
// ║ If a pattern returns a % above i_PositiveRetTol, the value tracking       ║
// ║ positive returns will be incremented. Anything in between will be tracked ║
// ║ as neutral.                                                               ║
// ║                                                                           ║
// ║  patternObj - An object that on creation will represent a Hikkake pattern ║
// ║             to be processed for confirmation. This object holds:          ║
// ║                • An ID that declares which pattern this is (for this      ║
// ║                script 1 - Bullish, 2 - Bearish)                           ║
// ║                • The number of candles that remain for a confirmation to  ║
// ║                be found                                                   ║
// ║                • The number of candles needed to confirm the pattern      ║
// ║                • A target value that will need to be conditionally met    ║
// ║                to confirm the pattern                                     ║
// ║                • A boolean corresponding to the confirmation status of    ║
// ║                pattern (na if still processing)                           ║
// ║                • The partition* that the pattern had been found in        ║
// ║                • The label created for this pattern                       ║
// ║                • The tooltip for this label**                             ║
// ║                                                                           ║
// ║ * This partition value represents where in historical price action the    ║
// ║ pattern had been found in. All patterns will have their returns broken up ║
// ║ into three different partitions and separated by the number of candles    ║
// ║ that were required to confirm them. Example: 2 bullish patterns that had  ║
// ║ required 1 candle to confirm would have their returns placed in different ║
// ║ groups if one of these patterns had occurred near the top of a yearly     ║
// ║ trading range, and one occurred near the bottom of a yearly trading range.║
// ║                                                                           ║
// ║ ** PineScrypt has a label.set_tooltip function but not a label.get_tooltip║
// ║ function. This variable allows the creation of dynamic labels that adds   ║
// ║ information onto the tooltip of the label. This will be used to display   ║
// ║ the relevant statistics for all patterns when the user scrolls over the   ║
// ║ label (which includes displaying the return for that specific pattern).   ║
// ║                                                                           ║
// ║  patConfirm - A simple object that corresponds to a linked-list reduction ║
// ║             of the patternObj. It only contains the relevant information  ║
// ║             needed to retrieve elements from the matrix that holds all    ║
// ║             returns. The reason a linked-list data structure is used here ║
// ║             is to allow multiple patterns to confirm on the same candle   ║
// ║             and still be able to back test for % change analysis. (Using  ║
// ║             the built-in array data structure proved tedious when trying  ║
// ║             to use historical reference). This UDT holds:                 ║
// ║                • The type (ID) of the pattern                             ║
// ║                • The number of candles needed to confirm                  ║
// ║                • The partition this pattern was found in                  ║
// ║                • A link to the next pattern that was confirmed            ║
// ║                                                                           ║
// ║       point - A simple object that contains X,Y coordinates which         ║
// ║             correspond to initialized points in the matrix. This is mainly║
// ║             for efficiency, by cutting down on loops that analyze a matrix║
// ║             of returnArrays to search for extremes (highest or lowest     ║
// ║             average or median returns of the matrix). These extremes will ║
// ║             then be used for creating the gradient used in the adaptive   ║
// ║             coloring setting.                                             ║
// ║                                                                           ║
// ║ Note: This script breaks PineCoders' script formatting guide on several   ║
// ║ occasions. Functions relevant to initializing/simple processing of values ║
// ║ related to these UDTs will be placed by the UDTs themselves. Other times  ║
// ║ this convention is broken by the placement of certain inputs that some    ║
// ║ constants depend on, plot/fill calls, and alerts.                         ║
//#endregion                                                                   ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

// Return Array is a user defined type to be used in the matrix that contains 
// all returns of all types of patterns to be found by this hunter. The variables
// it utilizes are:
//  
// @field returns Contains % representations of all returns for a pattern in an array.
// @field size The size of the returns array. 
// @field stdDev The Standard Deviation of the values in the returns array. 
// @field median The Median of the values in the returns array.
// @field avg The Average of the values in the returns array. 
// @field polarities An array of size 3 that contains the respective polarities of the returns for that pattern (negative, neutral, positive). 
type returnArray 
    float[] returns = na
    int size = 0
    float avg = 0
    float median = 0
    float stdDev = 0
    int[] polarities = na

// Initializes the ReturnArray object to be placed into the matrix that keeps track 
// of the returns for all patterns.
f_InitializeReturnArray() =>
    returnArray initial = 
      returnArray.new(returns = array.new_float(), polarities = array.new_int(3, 0))

    initial

// Pattern Obj(ect) is a UDT that will be initialized for a pattern to be processed
// by the confirmation logic in this script. It will contain: 
//
// @field ID The identifier for this pattern.
// @field candlesLeft The number of candles that remain to confirm this pattern.
// @field ccandles The number of candles that were necessary to confirm this pattern.
// @field targetVal The target value to be tested against closes succeeding the pattern for finding confirmations.
// @field confirmed A boolean value that indicates if this pattern has been confirmed or not ('na' for patterns still processing).
// @field partition The partition of price action that this pattern was detected in.
// @field patLbl The label that is generated for this pattern.
// @field lblTooltip A string that contains the tooltip for the pattern label so it may be modified/updated at later points in this script's execution.
type patternObj 
    int ID = 0
    int candlesLeft = -1
    int ccandles = -1
    float targetVal = -1.0
    bool confirmed = na
    int partition = -1
    label patLbl = na
    string lblTooltip = ""

// Append Return To Tip will display what a pattern has returned directly onto the
// tooltip of the pattern following the stats/'stats unavailable' section. 
f_AppendReturnToTip(patternObj _pat, float _retVal) =>
    formatStr = "\nThis Pattern Returned: " + str.tostring(_retVal, "#.####") + "%"
    updateTooltip = _pat.lblTooltip + formatStr
    label.set_tooltip(_pat.patLbl, updateTooltip)
    _pat.lblTooltip := updateTooltip

// Pat(tern) Confirm is a UDT that acts as reductive linked list created only 
// when a pattern has been confirmed. The reason a linked list is used is to 
// ensure multiple patterns confirmed on the same candle will all be displayed 
// properly. It was also necessary as I found historically referencing the built
// in array type was nearly impossible to implement properly. It contains:
//
// @field patType The type of pattern this confirmation corresponds to.
// @field ccandles The number of candles necessary to confirm this pattern.
// @field part The partition this pattern has appeared in.
// @field link The link to the next patConfirm object.
type patConfirm
    int patType = 0
    int ccandles = -1
    int part = -1
    patConfirm link = na

// Derive Confirmation Object returns the simplified object of the patternObj in 
// a Linked-List form.
//
// @param:
//    patternObj _pat - The current object that has been confirmed.
//
// @return:
//    patConfirm - An object that contains the pattern ID, number of candles 
// needed to confirm the pattern, the pattern's partition, and a 'na' link.
//
f_DeriveConfirmObj(patternObj _pat) =>
    patConfirm pc = patConfirm.new(patType = _pat.ID, ccandles = _pat.ccandles, 
      part = _pat.partition)

    pc

// Link Next Confirmed is an out-of-place feature function specific to the 
// linked-list UDT to link the next patConfirm object in the chain of links 
// supplied to it at the end. It will iterate through each link until the link 
// is 'na' then place the new link at this point in the chain.
//
// @params: 
//      _head - (patConfirm) The head of the linked list.
//      _link - (patConfirm) The link to be added to the supplied _head at the end.
//
// @returns:
//      void
//
f_LinkNextConfirmed(patConfirm _head, patConfirm _link) =>

    if na(_head)
        _link
    else
        currHead = _head
        tmp = _head.link
        while not na(tmp)
            tmp := tmp.link
        tmp := _link

        currHead

// Point is a simple container that will be used to keep track of which elements
// in a matrix have been initialized to cut down on excessive loops when 
// generating a color gradient used for the patterns when they appear.
//
// @field x The row of the matrix to access for information.
// @field y The column of the matrix to access for information.
type point
    int x = -1
    int y = -1

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║          ____          __                          __           __        ║
// ║         /  _/___  ____/ /__  ____  ___  ____  ____/ /__  ____  / /_       ║
// ║         / // __ \/ __  / _ \/ __ \/ _ \/ __ \/ __  / _ \/ __ \/ __/       ║
// ║       _/ // / / / /_/ /  __/ /_/ /  __/ / / / /_/ /  __/ / / / /_         ║
// ║      /___/_/ /_/\__,_/\___/ .___/\___/_/ /_/\__,_/\___/_/ /_/\__/         ║
// ║                          /_/                                              ║
// ║                ______                 __              __                  ║
// ║               / ____/___  ____  _____/ /_____ _____  / /______            ║
// ║              / /   / __ \/ __ \/ ___/ __/ __ `/ __ \/ __/ ___/            ║
// ║             / /___/ /_/ / / / (__  ) /_/ /_/ / / / / /_(__  )             ║
// ║             \____/\____/_/ /_/____/\__/\__,_/_/ /_/\__/____/              ║
// ║                                                                           ║
// ║ The constants in this section are independent of any inputs that may be   ║
// ║ used in part of their creation. These include an invisible color used for ║
// ║ partition background coloring, group names for inputs, and a Ticker String║
// ║ used in alerts.                                                           ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

var color COLOR_INVIS = #00000000
var PARTG = "PARTITION SETTINGS"
var STATG = "STATISTICS SETTINGS"
var ALRTG = "ALERT SETTINGS"
var VISIG = "VISIBILITY SETTINGS"
var TICKERSTRING = 
  "Ticker: " + syminfo.tickerid + "\nResolution: " + timeframe.period + "\n"

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║                        ____                  __                           ║
// ║                       /  _/___  ____  __  __/ /______                     ║
// ║                       / // __ \/ __ \/ / / / __/ ___/                     ║
// ║                     _/ // / / / /_/ / /_/ / /_(__  )                      ║
// ║                    /___/_/ /_/ .___/\__,_/\__/____/                       ║
// ║                             /_/                                           ║
// ║                                                                           ║
// ║ This section comprises all inputs that will be available to the user to   ║
// ║ modify the behavior of this script. All inputs are denoted with an i_     ║
// ║ before the name of the input; with input colors denoted with ic_. Each    ║
// ║ group of inputs affect a different portion of this script's behavior,     ║
// ║ broken up in such a way that makes sense when viewing these in the        ║
// ║ 'Settings' tab of the indicator.                                          ║
//#region                                                                      ║
// ║     MaxToConfirm - Sets the maximum number of candles allowed for         ║
// ║                  confirming patterns.                                     ║
// ║                  range: 2 - 5 (candles)                                   ║
// ║                  defval: 3                                                ║
// ║                  dependencies: PERCENTRETURNS                             ║
// ║                                f_InitCustomLabel                          ║
// ║                                f_MatchConfig                              ║
// ║                                f_ConfirmPattern                           ║
// ║                                f_RunPatternConfirmation                   ║
// ║                                matrixRow (variable - main call block)     ║
// ║                                                                           ║
// ║ AlertOn Booleans (+ functions):                                           ║
// ║             Find - Triggers an alert when a Hikkake pattern has been      ║
// ║                  found.                                                   ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║          Confirm - Triggers an alert when a Hikkake pattern is confirmed. ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║       NonConfirm - Triggers an alert when a Hikkake pattern has not been  ║
// ║                  confirmed within i_MaxToConfirm candles.                 ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║       WithReturn - Triggers an alert that displays the % return a pattern ║
// ║                  has yielded after it was found.                          ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║ Partition Settings:                                                       ║
// ║       Resolution - Sets the resolution that OHLC values will be grabbed   ║
// ║                  from for partitioning the graph into separate sections   ║
// ║                  for grouping returns of patterns.                        ║
// ║                  defval: 'W' (weekly)                                     ║
// ║                                                                           ║
// ║        RefLength - Sets the length at the Partition Resolution to find    ║
// ║                  the highest high and lowest low from.                    ║
// ║                  defval: 52 (@ 'W' - grabs yearly high/low)               ║
// ║                                                                           ║
// ║       UpperLimit - Sets the upper partition's lower limit to a specified  ║
// ║                  progression through the range                            ║
// ║                  defval: 80*                                              ║
// ║                                                                           ║
// ║       LowerLimit - Sets the lower partition's upper limit to a specified  ║
// ║                  progression through the range.                           ║
// ║                  defval: 33*                                              ║
// ║                                                                           ║
// ║ * The default values for these 4 inputs will designate the high of the    ║
// ║ yearly range to 80% of the range as the 'upper' section, 80% - 33% of the ║
// ║ range as the 'middle' section, and below 33% - the yearly low as the      ║
// ║ 'lower' section.                                                          ║
// ║                                                                           ║
// ║    EnableBGColor - Shows the three bands that make up the upper, middle,  ║
// ║                  and lower partitions.                                    ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║            Color - (Upper/Middle/Lower) Sets the colors for the three     ║
// ║                  partitions.                                              ║
// ║                  defvals: green/yellow/red                                ║
// ║                                                                           ║
// ║     ColorOpacity - Opacity setting to change the transparency of the      ║
// ║                  partition background coloring.                           ║
// ║                  defval: 75 (% opaque)                                    ║
// ║                                                                           ║
// ║ Stats/Visual Settings:                                                    ║
// ║  PnLSampleLength - Sets the number of candles ahead of the pattern to     ║
// ║                  calculate the price difference.                          ║
// ║                  defval: 10 (candles ahead)                               ║
// ║                                                                           ║
// ║          PnLType - Determines which starting point to use in PnL          ║
// ║                  calculations.                                            ║
// ║                  defval: 'FROM CONFIRMATION'                              ║
// ║                  options:                                                 ║
// ║                  'FROM CONFIRMATION': PnL Calculations will be done       ║
// ║                  from the open immediately following the confirmation of  ║
// ║                  a pattern.                                               ║
// ║                  'FROM APPEARANCE': PnL Calculations will be done by      ║
// ║                  offsetting the calculation start and end points by the   ║
// ║                  number of candles needed to confirm.                     ║
// ║                                                                           ║
// ║ MinReturnsNeeded - Sets the number of required patterns in each group     ║
// ║                  before statistics are displayed onto the pattern label's ║
// ║                  tooltip (Also affects Adaptive Coloring Mode).           ║
// ║                  defval: 5                                                ║
// ║                                                                           ║
// ║ Stats/Visual Settings:                                                    ║
// ║   EnableAdaptCol - Enables Adaptive Coloring; Patterns will be assessed   ║
// ║                  on bullish/bearishness and be colored based on prior     ║
// ║                  performance.                                             ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║      GradientRef - Sets which returnArray values from the  matrix will be ║
// ║                  used for scaling bullish/bearish coloring by.            ║
// ║                  defval: 'AVG'                                            ║
// ║                  options:                                                 ║
// ║                  'AVG': The average return of the patterns that qualify   ║
// ║                  will be what is compared against the range of the        ║
// ║                  minimum/maximum average of all returns in the matrix.    ║
// ║                  'MEDIAN': The median return of the patterns that qualify ║
// ║                  will be what is compared against the range of the        ║
// ║                  minimum/maximum median of all returns in the matrix.     ║
// ║                                                                           ║
// ║        GradColor - (Bullish/Neutral/Bearish) Sets the gradient colors to  ║
// ║                  be used in Adaptive Coloring.                            ║
// ║                  defvals: Green/Yellow/Red                                ║
// ║                                                                           ║
// ║         NEIColor - Not Enough Info color. This color will be used when a  ║
// ║                  pattern has fewer than MinReturnsNeeded occurrences in   ║
// ║                  Adaptive Coloring mode.                                  ║
// ║                  defval: #888B8D (aluminum gray)                          ║
// ║                                                                           ║
// ║        HardLimit - Alters color scheme to color patterns that exceed the  ║
// ║                  Return Tolerances the fully bullish or bearish gradient  ║
// ║                  colors.                                                  ║
// ║                  defval: false                                            ║
// ║                                                                           ║
// ║PositiveReturnTol - Sets the value patterns must return above after the    ║
// ║                  specified number of candles for that return to be        ║
// ║                  considered 'Positive'.                                   ║
// ║                  defval: 3.0 (3%)                                         ║
// ║                  step: 0.01 (0.01%)                                       ║
// ║                                                                           ║
// ║NegativeReturnTol - Sets the value patterns must return below after the    ║
// ║                  specified number of candles for that return to be        ║
// ║                  considered 'Negative'.                                   ║
// ║                  defval: -3.0 (-3%)                                       ║
// ║                  step: 0.01 (0.01%)                                       ║
// ║                                                                           ║
// ║       LabelNudge - Bumps the label's Y-position further upward or         ║
// ║                  downward based on a percentage of the candle's range     ║
// ║                  it's been placed at.                                     ║
// ║                  defval: 0.125 (12.5%)                                    ║
// ║                                                                           ║
// ║ Standard Colors:                                                          ║
// ║    'ish'Color(s) - Sets the bullish and bearish colors for the labels and ║
// ║                  patterns that have been confirmed.                       ║
// ║                  defval: Green/Red                                        ║
// ║                                                                           ║
// ║      Unconfirmed - Sets the color for the labels and patterns that have   ║
// ║                  yet to be confirmed.                                     ║
// ║                  defval: Yellow                                           ║
// ║                                                                           ║
// ║     NonConfirmed - Sets the color for Non-confirmed patterns if the user  ║
// ║                  has them enabled.                                        ║
// ║                  defval: Black                                            ║
// ║                                                                           ║
// ║ ShowNonConfirmed - Displays patterns that have not been confirmed.        ║
// ║                  defval: false                                            ║
//#endregion                                                                   ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

i_MaxToConfirm = input.int(defval = 3, title = "Max Confirmation Candles", 
  minval = 2, maxval = 5, tooltip = "Changes how many candles may be allowed "
  + "until a confirmation is found.", group = "CONFIRMATION SETTING", confirm = true)

i_AlertOnFind = input.bool(defval = false, title = "On Find", 
  inline = "ALERTS", group = ALRTG)
i_AlertOnConfirm = input.bool(defval = false, title = "On Confirm", 
  inline = "ALERTS", group = ALRTG)
i_AlertOnNonConfirm = input.bool(defval = false, title = "On Non-Confirm", 
  inline = "ALERTS", group = ALRTG)
i_AlertWithReturn = input.bool(defval = false, title = "On Return", 
  tooltip = "Sets which alerts will be triggered during execution:\n\nFind "
  + "- Alert will trigger when a Hikkake pattern has been found.\nConfirm - "
  + "Alert will trigger when a Hikkake pattern has been confirmed.\n"
  + "Non-Confirmed - Alert will trigger when a Hikkake pattern has not been "
  + "confirmed.\nReturn - Alert will be triggered stating the % return that "
  + "Hikkake pattern had yielded.\n\nNote: This script uses the 'alert' "
  + "function and not 'alertcondition', These booleans must be set *before* "
  + "'all alert() calls' is enabled in TradingView's settings for your chart.", 
  inline = "ALERTS", group = ALRTG)

// Alert Functions: On Find - Will trigger an alert if a Hikkake pattern has been
// found/the alert has been enabled.
f_AlertOnFound(patternObj _pat) => 
    if i_AlertOnFind
        patString = str.substring(label.get_text(_pat.patLbl), 0, 16) + "Found"

        alert(TICKERSTRING + patString)

// On Confirm - Will trigger an alert if a Hikkake pattern has been confirmed. This
// will also display the statistics associated with that pattern (viewable from the
// app by expanding the push notification). 
f_AlertOnConfirm(patternObj _pat) =>
    if i_AlertOnConfirm and _pat.confirmed
        patString = str.substring(label.get_text(_pat.patLbl), 0, 16) + "Confirmed"
        
        statString = _pat.lblTooltip

        alert(TICKERSTRING + patString + "\n" + statString)

// On Non-Confirm - Will trigger an alert if the Hikkake pattern identified earlier
// had not been confirmed during its lifespan.
f_AlertOnNonConfirm(patternObj _pat) =>                                                                  
    if i_AlertOnNonConfirm and not (na(_pat.confirmed) or _pat.confirmed)
        patternString = (_pat.ID == 1 ? "Bullish" : "Bearish") + " Hikkake"
        unconfString = patternString + " Non-Confirmed"
        alert(TICKERSTRING + unconfString)
      
// With Return - Will trigger an alert that displays the name of the confirmed Hikkake 
// pattern and the percentage that pattern had returned over the number of candles 
// specified by the i_PnLSampleLength input.
f_AlertWithReturn(patternObj _pat, float _val) =>
    if i_AlertWithReturn
        patString = str.substring(label.get_text(_pat.patLbl), 0, 16) + "returned: "
        formatValStr = str.tostring(_val, "#.####") + "%"

        fullStr = patString + formatValStr

        alert(TICKERSTRING + fullStr)

i_PartitionResolution = input.timeframe(defval = "W", title = "Partition Resolution", 
  tooltip = "Defines which timeframe a price range will be grabbed from.", group = PARTG)
i_PartitionRefLength = input.int(defval = 52, title = "Partition Reference Length", 
  minval = 3, tooltip = "Sets how many candles back at the 'Partition Resolution' "
  + "to grab the price range from that separates the chart into three partitions."
  + "\n\nThe default values for 'Partition Resolution' and 'Partition Reference "
  + "Length' will have this script grab the 52-week 'high' and 52 week 'low' for "
  + "the range that breaks up the chart into these three separate sections.", 
  group = PARTG)

i_UpperPartitionLim = input.float(defval = 80, title = "Partition Limits: Upper", 
  minval = 50, maxval = 100, step = 0.01, group = PARTG, inline = "PARTITION BOUNDS")
i_LowerPartitionLim = input.float(defval = 33, title = "Lower", 
  minval = 0, maxval = 50, step = 0.01, tooltip = "Defines the upper and lower "
  + "bounds of the high-low range from the Reference Resolution/Length that will "
  + "break the range up into three sections.\n\nEx:\nResolution: 'W'\nReference "
  + "Length: 52\nUpper Limit: 80\nLower Limit: 33\n\nYearly High: 100\nYearly "
  + "Low: 50\nUpper Band will be 100 -> 90.\nMiddle Band will be 90 -> 66.66\n"
  + "Lower Band will be 66.66 -> 50", group = PARTG, inline = "PARTITION BOUNDS")

i_PartitionEnableBGColor = input.bool(false, 
  title = "Enable Partition Background Coloring", 
  tooltip = "Enabling this will show the three different partitions of the "
  + "chart's price action over the given Resolution and Length.", group = PARTG)
ic_PartitionUpperColor = input.color(defval = color.green , 
  title = "Band Colors: Upper", inline = "BAND COLORS", group = PARTG)
ic_PartitionMiddleColor = input.color(defval = color.yellow , 
  title = "Middle", inline = "BAND COLORS", group = PARTG)
ic_PartitionLowerColor = input.color(defval = color.red , 
  title = "Lower", inline = "BAND COLORS", group = PARTG)
i_PartitionColorOpacity = input.int(75, title = "Partition Background Opacity", 
  minval = 0, maxval = 100, tooltip = "Determines how transparent the background "
  + "coloring of the partition shading will be (if enabled).", group = PARTG)

i_PnLSampleLength = input.int(defval = 10, title = "P/L Sample Length",
  minval = 2, maxval = 20, tooltip = "Sets the number of candles from the "
  + "appearance of a pattern (or from confirmation) to calculate % Profit/Loss:"
  + "\n\nCalculation:\n(close[0 {+ # to confirm}] - open[P/L Sample{+ # to "
  + "confirm}]) / open[P/L Sample {+ # to confirm}]*\n\n* {} indicates additional "
  + "roll back if \"FROM APPEARANCE\" is selected.", group = STATG)
i_PnLType = input.string(defval = "FROM CONFIRMATION", title = "P/L Starting Point", 
  options = ["FROM CONFIRMATION", "FROM APPEARANCE"], tooltip = "Changes how "
  + "Profit/Loss calculations are conducted.\n\nFROM CONFIRMATION - P/L "
  + "Calculation will be done from the first open after the confirmation of a "
  + "pattern to the close \"P/L Sample Length\" candles later.\nFROM APPEARANCE "
  + "- P/L Calculation will be done from the first open after the appearance of "
  + "a pattern to the close \"P/L Sample Length\" candles later.", group = STATG)
i_MinReturnsNeeded = input.int(defval = 5, title = "Minimum Returns Needed", 
  minval = 3, maxval = 10, tooltip = "Defines the number of occurrences a pattern "
  + "must have in order for % PnL Stats to be displayed on the label's tooltip for "
  + "that pattern.", group = STATG)
i_EnableAdaptCol = input.bool(defval = false, title = "Enable Adaptive Coloring",
  tooltip = "Changes the coloring of patterns based on statistics derived from "
  + "the prior occurrences of each pattern. The gradient colors below will be "
  + "substituted for the pattern coloration, however the default 'Bullish/Bearish "
  + "Hikkake Color(s)' further along will be used for the coloration of the labels."
  + " The color gradient will be created based on the 'Negative/Positive Return "
  + "Tolerance' values. These values will be changed if the 'Gradient Reference' "
  + "value chosen of any patterns that meet the minimum number of returns needed "
  + "exceed these values in either direction. The closer the pattern's 'Gradient "
  + "Reference' value is to the Tolerance values (or substituted values from the "
  + "set of returns), the stronger the coloration to their respective 'Gradient "
  + "Color' will be starting from the 'Neutral Color'.\n\nIf the pattern appeared "
  + "has not met the minimum number of prior instances, 'NEI Color' will be used "
  + "instead for that pattern's color.", group = STATG)
i_GradientRef = input.string(defval = "AVG", title = "Gradient Reference", 
  options = ["AVG", "MEDIAN"], tooltip = "AVG - The average return of the patterns "
  + "will be used for gradient generation.\nMEDIAN - The median return of the "
  + "patterns will be used for gradient generation.", group = STATG)

ic_BullishGradColor = input.color(defval = color.green , title = "Gradient Color: Bullish", 
  inline = "GRADCOLORS", group = STATG)
ic_NeutralGradColor = input.color(defval = color.yellow , title = "Neutral", 
  inline = "GRADCOLORS", group = STATG)

ic_BearishGradColor = input.color(defval = color.red, title = "Bearish", 
  tooltip = "Sets the colors used for generating the gradient in adaptive pattern "
  + "coloration.", inline = "GRADCOLORS", group = STATG)
// Not Enough information color (aluminum/light gray)
ic_NEIColor = input.color(defval = #888B8D, title = "NEI Color", 
  tooltip = "\'Not Enough Information\' Color. Color used for when there hasn't "
  + "been enough of that pattern detected to generate a color for.", group = STATG)

i_HardLimit = input.bool(defval = false, title = "Hard Limit", tooltip = "If enabled, "
  + "color gradient rules are changed so that any pattern which exceeds either "
  + "'Return Tolerance' values will be colored the full Bullish or Bearish gradient "
  + "colors.\n\nNote: This also affects the perceived 'strength' of less bullish/bearish "
  + "patterns. I recommended increasing the related Tolerances with this setting.", 
  group = STATG)

i_NegativeRetTol = input.float(defval = -3.0, title = "Negative Return Tolerance", 
  minval = -100, maxval = 0, step = 0.01, tooltip = "Defines the maximum % return "
  + "a pattern must be below after the specified number of candles to be considered"
  + " \"negative\".", group = STATG)
i_PositiveRetTol = input.float(defval = 3.0, title = "Positive Return Tolerance", 
  minval = 0, maxval = 100, step = 0.01, tooltip = "Defines the minimum % return "
  + "a pattern must be above after the specified number of candles to be considered"
  + " \"Positive\".\n\nNote: Returns that are between this tolerance and the "
  + "\"Negative Return Tolerance\" will be declared \"Neutral\".", group = STATG)

i_LabelNudge = input.float(title = "Label Nudge", defval = 0.125, step = 0.001, 
  maxval = 2.0, minval = 0, tooltip = "This floating point value will determine how "
  + "close/far away from the price points around identified patterns that the "
  + "labels will be placed.", group = VISIG)

ic_BullColor = input.color(title = "Bullish Hikkake Color", defval = color.green, 
  group = VISIG, inline ="Hikkake Colors")
ic_BearColor = input.color(title = "Bearish Hikkake Color", defval = color.red, 
  group = VISIG, inline ="Hikkake Colors")
ic_Unconfirmed = input.color(title = "Unconfirmed Hikkake Color", 
  defval = color.yellow, group = VISIG, inline = "Hikkake Colors")
ic_NonConfirmed = input.color(title = "Non Confirmed Hikkake Color", 
  defval = color.black, group = VISIG, inline = "Hikkake Colors")

i_ShowNonConfirmed = input.bool(title = "Show Non-Confirmed patterns",
  defval = false, group = VISIG)

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║               ____                            __           __             ║
// ║              / __ \___  ____  ___  ____  ____/ /__  ____  / /_            ║
// ║             / / / / _ \/ __ \/ _ \/ __ \/ __  / _ \/ __ \/ __/            ║
// ║            / /_/ /  __/ /_/ /  __/ / / / /_/ /  __/ / / / /_              ║
// ║           /_____/\___/ .___/\___/_/ /_/\__,_/\___/_/ /_/\__/              ║
// ║                     /_/                                                   ║
// ║                ______                 __              __                  ║
// ║               / ____/___  ____  _____/ /_____ _____  / /______            ║
// ║              / /   / __ \/ __ \/ ___/ __/ __ `/ __ \/ __/ ___/            ║
// ║             / /___/ /_/ / / / (__  ) /_/ /_/ / / / / /_(__  )             ║
// ║             \____/\____/_/ /_/____/\__/\__,_/_/ /_/\__/____/              ║
// ║                                                                           ║
// ║ This section contains all constants that will be used in the process of   ║
// ║ identifying, placing, and processing all patterns identified by this      ║
// ║ script. The majority of constants in this section depend values given to  ║
// ║ them by the inputs above.                                                 ║
// ║                                                                           ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

// Modify the opacity of the colors for partition coloring
var color UPPER_COLOR_MOD = color.new(ic_PartitionUpperColor, i_PartitionColorOpacity)
var color MIDDLE_COLOR_MOD = color.new(ic_PartitionMiddleColor, i_PartitionColorOpacity)
var color LOWER_COLOR_MOD = color.new(ic_PartitionLowerColor, i_PartitionColorOpacity)

// MATRIX SET UP FOR % RETURNS COLLECTION
//            P1    P2    P3
//    BULL1   x     x     x
//    BULL2   x     x     x
//    ...
//    BULLN   x     x     x
//    BEAR1   x     x     x
//    BEAR2   x     x     x
//    ...
//    BEARN   x     x     x
//
//  BxxxN -> N indicates the number of candles needed to confirm (specified by the user)
// P(1-3) -> Indicates the partition of established price history 
//
// ctrl-f "MatrixSetup"
//
var PERCENTRETURNS = matrix.new<returnArray>((i_MaxToConfirm * 2), 3, na)
var UNCONFIRMEDHIKKAKES = array.new<patternObj>()
var RETURNLOCATIONS = array.new<point>()

// For color gradient generation.
float MINRETURNVAL = i_NegativeRetTol
float MAXRETURNVAL = i_PositiveRetTol

// Grab candle values of the previous three for pattern set-up detection
C1HIGH = high[3]
C1LOW = low[3]

C2HIGH = high[2]
C2LOW = low[2]
C2RANGE = C2HIGH - C2LOW

C3HIGH = high[1]
C3LOW = low[1]

// Used in label-positioning
HIGHEST3 = math.max(C1HIGH, C2HIGH, C3HIGH)
LOWEST3 = math.min(C1LOW, C2LOW, C3LOW)
HIGHLOW3RANGE = HIGHEST3 - LOWEST3

// Reusable values
CURRCLOSE = close
CURRBARSTATE = barstate.isconfirmed

// Initialize Linked-List
patConfirm confirmedPatternsLL = na

// Used in 'barcolor' calls, these arrays grant this script the 
// ability to confirm and display multiple patterns simultaneously.
COLOROFFSET = array.new_int(6)
CANDLECOLOR = array.new_color(6)

// REQUEST.SECURITY CALLS -> This code creates the partitions that will be used
// to determine where along the specified resolution and sample length of price action
// patterns have occurred in. 
// These variables and their respective plot() and fill() calls are kept together here to avoid
// confusion down the line (PineCoders' Style guide recommends these kinds of calls are moved
// closer to the end of this script).
BARINDREF = request.security(syminfo.tickerid, i_PartitionResolution, bar_index)
BARINDTERN = BARINDREF > i_PartitionRefLength ? i_PartitionRefLength : BARINDREF + 1

HIGHREF = request.security(syminfo.tickerid, i_PartitionResolution, 
  ta.highest(high, BARINDTERN))
LOWREF = request.security(syminfo.tickerid, i_PartitionResolution, 
  ta.lowest(low, BARINDTERN))
UPPERSECTION = ((HIGHREF - LOWREF) * (i_UpperPartitionLim / 100)) + LOWREF
LOWERSECTION = ((HIGHREF - LOWREF) * (i_LowerPartitionLim / 100)) + LOWREF

// Generate bands related to the above variables and display them 
// (if the user wishes to)
REFLINE = plot(series = i_PartitionEnableBGColor ? HIGHREF : na, 
  title = "High Ref Line", color = COLOR_INVIS, editable = false)
UPPERLINE = plot(series = i_PartitionEnableBGColor ? UPPERSECTION : na, 
  title = "Upper Limit Line", color = COLOR_INVIS, editable = false)
LOWERLINE = plot(series = i_PartitionEnableBGColor ? LOWERSECTION : na, 
  title = "Lower Limit Line", color = COLOR_INVIS, editable = false)
PSEUDOZEROLINE = plot(series = i_PartitionEnableBGColor ? LOWREF : na, 
  title = "Zero Line", color = COLOR_INVIS, editable = false)

fill(plot1 = REFLINE, plot2 = UPPERLINE, color = UPPER_COLOR_MOD, editable = false)
fill(plot1 = UPPERLINE, plot2 = LOWERLINE, color = MIDDLE_COLOR_MOD, editable = false)
fill(plot1 = LOWERLINE, plot2 = PSEUDOZEROLINE, color = LOWER_COLOR_MOD, editable = false)

// Grabs the partition at the candle close directly before the appearance of a 
// pattern. This value will be used to determine which returnArray returns for 
// patterns will be added to, effectively dividing up the returns for each 
// pattern based on which point in the yearly (default) price range that pattern 
// had occurred in.
PARTITIONPOS = CURRCLOSE[4] >= UPPERSECTION ? 0 : CURRCLOSE[4] < UPPERSECTION 
  and CURRCLOSE[4] > LOWERSECTION ? 1 : 2

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║                 ______                 __  _                              ║
// ║                / ____/_  ______  _____/ /_(_)___  ____  _____             ║
// ║               / /_  / / / / __ \/ ___/ __/ / __ \/ __ \/ ___/             ║
// ║              / __/ / /_/ / / / / /__/ /_/ / /_/ / / / (__  )              ║
// ║             /_/    \__,_/_/ /_/\___/\__/_/\____/_/ /_/____/               ║
// ║                                                                           ║
// ║ This section of code contains all functions that tear down analyzing the  ║
// ║ three previous candles and determine if a Hikkake pattern has been found. ║
// ║ They are placed in order that they are called in the 'main' call block.   ║
// ║                                                                           ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

// Init Custom label is a helper function that will initially create the label to 
// identify an unconfirmed Hikkake pattern.
// This function will:
//      1. Identify which pattern it is intended for.
//      2. Place the label at the lows for bullish patterns, and highs for bearish.
//      3. Nudge the y-position of the label by a proportion of the middle candle's 
//      high-low range.
//      4. Place the label at the pattern's middle candle.
//      5. Colors the label initially by ic_Unconfirmed.
//
// @param
//      _type - The type of pattern to initialize the label for. Corresponding 
//            values are 1 for the Bullish Hikkake Pattern. 2 for the Bearish 
//            Hikkake Pattern. 
//
// @returns
//      label - a newly generated label that indicates which type of pattern has 
//            occurred and how many candles (based on i_MaxToConfirm) are left to
//            confirm that pattern. 
//
f_InitCustomLabel(int _type) =>

    nudgeRange = i_LabelNudge * HIGHLOW3RANGE
    labelPos = _type == 1 ? LOWEST3 : HIGHEST3
    labelNudgeVal = _type == 1 ? -nudgeRange : nudgeRange
    labelStyle = _type == 1 ? label.style_label_up : label.style_label_down
    builtLabelString = 
      (_type == 1 ? "Bullish " : "Bearish ") + "Hikkake Unconfirmed\n\n(" 
      + str.tostring(i_MaxToConfirm) + " candles remain)" 

     
    label.new(bar_index - 2, labelPos + labelNudgeVal, builtLabelString, 
      color = ic_Unconfirmed, style = labelStyle, textcolor = color.black)

// Match Config(uration) is a helper function which determines if a valid Hikkake
// setup pattern has been detected. This function will either yield 'na' or 
// initialize a new patternObj for the corresponding Hikkake pattern that has been
// detected. If a pattern has been detected, this function will set the ID, number 
// of candles to confirm the pattern (based on i_MaxToConfirm provided by the user),
// the target value depending on which pattern was detected, the current partition of
// the pattern when it was detected, and initializes the label for the pattern that 
// will be updated throughout its lifespan.
//
// @returns:
//    patternObj - A pattern object representing a found Hikkake pattern (both 
//               variants). It contains the number of candles left to confirm the
//               pattern, the targetVal to confirm the pattern, the partition
//               of price action the pattern was found in, and the label +
//               tooltip associated with the pattern. 'na' otherwise.
//
f_MatchConfig() =>
    // Both patterns need to have a nested second candle
    secondCandleInside = C2HIGH < C1HIGH and C2LOW > C1LOW

    // Test bullish and bearish cases
    thirdCandleValidBear = C3HIGH > C2HIGH and C3LOW > C2LOW
    thirdCandleValidBull = C3HIGH < C2HIGH and C3LOW < C2LOW

    bearish = secondCandleInside and thirdCandleValidBear

    bullish = secondCandleInside and thirdCandleValidBull
    
    // Generate new Pattern Object for detected patterns, return
    patternObj retObj = na
    if bullish
        retObj := patternObj.new(ID = 1, candlesLeft = i_MaxToConfirm, 
          targetVal = C2HIGH, partition = PARTITIONPOS, patLbl = f_InitCustomLabel(1))

    if bearish
        retObj := patternObj.new(ID = 2, candlesLeft = i_MaxToConfirm, 
          targetVal = C2LOW, partition = PARTITIONPOS, patLbl = f_InitCustomLabel(2))

    retObj

// Matrix Min/Max is a helper function which will reference all initialized 
// returnArray objects in the matrix and return the minimum and maximum 
// values of the user-specified value (average or median return) of the matrix.
// These values will then be used to determine a color gradient to be used for 
// patterns. 
//
// @inputs:
//    i_MinReturnsNeeded - Defines the minimum number of occurred patterns 
//                       necessary to include the user-specified value of that 
//                       pattern's return array as a part of the determination
//                       process.
//         i_GradientRef - The specified statistics value to be used in comparing
//                       all returns in the matrix.
//
// @returns:
//        minVal - The minimum value for the matrix.
//        maxVal - The maximum value for the matrix.
f_MatrixMinMax() =>
    pointListSize = array.size(RETURNLOCATIONS)
    float minVal = na
    float maxVal = na
    if pointListSize != 0
        for i = 0 to pointListSize - 1
            pointV = array.get(RETURNLOCATIONS, i)
            retRow = pointV.x
            retCol = pointV.y
            returnArr = matrix.get(PERCENTRETURNS, retRow, retCol)
            if returnArr.size >= i_MinReturnsNeeded
                valToAssign = i_GradientRef == "AVG" ? returnArr.avg : returnArr.median
                minVal := na(minVal) or valToAssign < minVal ? valToAssign : minVal
                maxVal := na(maxVal) or valToAssign > maxVal ? valToAssign : maxVal
    [minVal, maxVal]

// Confirm Pattern is a helper function which takes in a pattern object and 
// determines if the pattern is confirmed at the current candle in the 
// confirmation process. 
//
// @param:
//      patternObj pat - Pattern Object which contains the pattern type,
//                     number of candles remaining, and the target value to 
//                     be used in confirming the pattern.
//
// @return:
//      bool - true/false if the pattern at its current point during the 
//           lifespan to confirm meets the requirements to confirm the 
//           pattern.
//
f_ConfirmPattern(patternObj _pat) =>
    patType = _pat.ID
    currCandle = i_MaxToConfirm - _pat.candlesLeft

    confirmationTest = false
    if patType == 1
        confirmationTest := CURRCLOSE > _pat.targetVal[currCandle]

    if patType == 2
        confirmationTest := CURRCLOSE < _pat.targetVal[currCandle]

    confirmationTest

// Update Label is a helper function which will update labels for both confirmed
// and unconfirmed patterns accordingly. For confirmed patterns, this function 
// will:
//    Display if the pattern is in the process of being confirmed.
//    Display the number of candles required to confirm the pattern.
//    Change the color of the label accordingly.
//
// For unconfirmed patterns, this will update the number of candles that remain 
// for the pattern to be confirmed.
//
// @params:
//    int _updateType - Tells this function which type of update will occur. 1
//                    for unconfirmed patterns, 2 for confirmed patterns.
//    patternObj _pat - UDT that contains the label to be modified.
//   color _confColor - Color to be set for the label (confirmed only).
//
// @returns:
//    void
//
f_UpdateLabel(int _updateType, patternObj _pat, color _confColor = na) =>
    textToMod = label.get_text(_pat.patLbl)
    if _updateType == 1
        currCandle = _pat.candlesLeft
        // "Bxxxish Hikkake Unconfirmed\n\n(X {candle}s remain{s}"
        //  _______________________________ -> string length = 30 (\n = 1 character)
        updateText = str.substring(textToMod, 0, 30)
        plural = currCandle != 1
        updateText := updateText + str.tostring(currCandle) + " candle" 
          + (plural ? "s" : "") + " remain" + (plural ? "" : "s") + ")"
        label.set_text(_pat.patLbl, updateText)
    if _updateType == 2
        // "Bxxxish Hikkake Unconfirmed\n\n(X {candle}s remain{s}"
        //  ________________ -> string length = 16
        updateText = str.substring(textToMod, 0, 16)
        updateText := updateText + "Confirm" + (CURRBARSTATE ? "ed" : "ing") + "\n\n("
        updateText := updateText + str.tostring(_pat.ccandles) + " candle" 
          + (_pat.ccandles == 1 ? "" : "s") + " needed)"
        label.set_text(_pat.patLbl, updateText)
        label.set_color(_pat.patLbl, _confColor)

// Grab Color From Gradient takes a returnArray object and determines the color 
// needed for this object based on the statistics it contains compared to the 
// upper and lower limits of the returns contained by the matrix. It will return 
// the NEI color if the returnArray object's size does not contain enough data 
// specified with i_MinReturnsNeeded.
//
// @inputs:
//       i_MinReturnsNeeded - The minimum required returns for determining a 
//                          color generated for that pattern.
//            i_GradientRef - The specified returnArray stats value to be used 
//                          in the gradient generation process.
//      ic_BullishGradColor - The color that corresponds to patterns yielding above
//                          0.
//      ic_BearishGradColor - The color that corresponds to patterns yielding below
//                          0.
//      ic_NeutralGradColor - The color that will serve as an intermediary between
//                          bullish and bearish patterns.
//              ic_NEIColor - The color to be used when the pattern does not meet 
//                          i_MinReturnsNeeded.
//
// @param:
//    returnArray _retArr - The array that contains the necessary information to 
//                        generate a color out of the gradient for that pattern.
//
// @returns:
//    color - Either NEIColor OR a color along the gradient corresponding to 
//          the (bull/bear)ishness of that pattern compared to others in the matrix 
//          dataset.
//
f_GrabColorFromGrad(returnArray _retArr) =>
    if _retArr.size < i_MinReturnsNeeded
        ic_NEIColor
    else
        gradientVal = i_GradientRef == "AVG" ? _retArr.avg : _retArr.median

        if gradientVal > 0
            if i_HardLimit and gradientVal > MAXRETURNVAL
                ic_BullishGradColor
            else
                color.from_gradient(gradientVal, 0, MAXRETURNVAL, ic_NeutralGradColor, 
                  ic_BullishGradColor)
        else
            if i_HardLimit and gradientVal < MINRETURNVAL
                ic_BearishGradColor
            else
                color.from_gradient(gradientVal, MINRETURNVAL, 0, ic_BearishGradColor, 
                  ic_NeutralGradColor)

// Display Stats on Label will take all of the relevant statistics from a 
// returnArray matrix entry for a specific pattern, and display them onto the 
// tooltip of the pattern's label. When the user scrolls over the pattern label,
// it will display:
//
//    - The partition of the chart the pattern has been found in.
//    - The number of previous instances.
//    - The median return for that pattern.
//    - The average return for that pattern.
//    - The standard deviation of returns for that pattern.
//    - A 95% confidence interval of the expected return for this pattern.
//    - The current number of negative, neutral, and positive returns this pattern
//    has experienced.
//
// If the pattern has not occurred at least i_MinReturnsNeeded times, it will 
// instead specify an 'return stats unavailable' message with the number of
// patterns needed.
//
// @input:
//    i_MinReturnsNeeded - The number of patterns that need to occur before the 
//                       statistics of the pattern is displayed on the tooltip.
//
// @params:
//     patternObj _p - The pattern to assess data from (partition) and the 
//                   label/tooltip to be modified.
//    returnArray _r - The returnArray that contains all stats on the pattern 
//                   that has occurred.
//
// @returns:
//      void
//
f_DisplayStatsOnLabel(patternObj _p, returnArray _r) =>
    updateTooltip = ""
    sizeStr = str.tostring(_r.size)
    partString = switch _p.partition
        0 => "Upper"
        1 => "Middle"
        2 => "Lower"
    partString := partString + " Section"

    if _r.size >= i_MinReturnsNeeded

        stdDevStr = str.tostring(_r.stdDev, "#.####")
        medianStr = str.tostring(_r.median, "#.####")
        averageStr = str.tostring(_r.avg, "#.####")

        // CONFIDENCE INTERVAL CALC (ASSUMES NORMAL DISTRIBUTION):
        //    CI = avg ± (Z * (σ / √n))
        //    Z -> Z-score for 95% intervals (~1.96)
        //    σ -> standard deviation of the set
        //    n -> number of elements in the set
        CILower = str.tostring(_r.avg - 
          (1.96 * (_r.stdDev / math.sqrt(_r.size))), "#.####") + "%"
        CIUpper = str.tostring(_r.avg + 
          (1.96 * (_r.stdDev / math.sqrt(_r.size))), "#.####") + "%"

        negReturns = array.get(_r.polarities, 0)
        neuReturns = array.get(_r.polarities, 1)
        posReturns = array.get(_r.polarities, 2)
        totReturns = negReturns + neuReturns + posReturns

        negRetStr = str.tostring(negReturns) + " (" + 
          str.tostring((negReturns / totReturns) * 100, "#.####") + "%)"
        neuRetStr = str.tostring(neuReturns) + " (" + 
          str.tostring((neuReturns / totReturns) * 100, "#.####") + "%)"
        posRetStr = str.tostring(posReturns) + " (" + 
          str.tostring((posReturns / totReturns) * 100, "#.####") + "%)"

        combinedString = "Return Stats:\n\nPartition: "+ partString
          + "\nPrevious Instances: " + sizeStr 
          + "\nMedian Return: " + medianStr + "%"
          + "\nAverage Return: " + averageStr + "%"
          + "\nStandard Deviation: ±" + stdDevStr + "%" 
          + "\n95% Confidence Interval: [" + CILower + ", " + CIUpper + "]"
          + "\nNegative Returns: " + negRetStr 
          + "\nNeutral Returns: " + neuRetStr
          + "\nPositive Returns: " + posRetStr

        updateTooltip := combinedString
    else
        updateTooltip := "Return Stats (Unavailable):\n\nPartition: " + partString
          + "\nPrevious Instances: " + sizeStr + "\n\n" 
          + str.tostring(i_MinReturnsNeeded - _r.size) + " more patterns needed."
    label.set_tooltip(_p.patLbl, updateTooltip)
    _p.lblTooltip := updateTooltip

// Run Pattern Confirmation is the overarching logic function that determines if 
// a pattern meets the requirements to be confirmed and subsequently sets the 
// values in the object associated with that pattern to reflect this confirmation. 
// This function heavily relies on the use of the CURRBARSTATE variable, thus 
// repainting patterns/their labels during the currently-closing candle.
//
// @inputs:
//      i_MaxToConfirm - Used here to determine confirmation candles, matrix 
//                     positions, and which candles to color.
//    i_EnableAdaptCol - Determines the altered color scheme for patterns.
// 
// @param:
//    patternObj _patObj - The current pattern being evaluated for confirmation. 
//                       The values of this object are to be changed when it has 
//                       been confirmed, and the label properly updated throughout 
//                       the lifespan of the pattern.
//
// @return:
//    bool - true if a pattern has been confirmed or has reached the end of its 
//         lifespan.
//         false if the pattern is still unconfirmed after a bar-close.
//
f_RunPatternConfirmation(patternObj _patObj) =>

    patConfirmed = f_ConfirmPattern(_patObj)
    barstateOffset = CURRBARSTATE ? 0 : 1
    if patConfirmed
        
        // Only decrement candlesLeft/set confirmed boolean after bar-close
        _patObj.candlesLeft := 
          CURRBARSTATE ? _patObj.candlesLeft - 1 : _patObj.candlesLeft
        _patObj.ccandles := i_MaxToConfirm - _patObj.candlesLeft + barstateOffset
        _patObj.confirmed := CURRBARSTATE ? true : na
        confColor = _patObj.ID == 1 ? ic_BullColor : ic_BearColor
        f_UpdateLabel(2, _patObj, confColor)

        // MatrixSetup
        // Bullish Confirmed Patterns will be rows 0 -> i_MaxToConfirm - 1
        // Bearish Confirmed Patterns will be rows i_MaxToConfirm -> 2 * i_MaxToConfirm - 1
        // Columns will be the partitions in which the patterns have appeared.
        returnMatrixRow = (_patObj.ID == 1 ? 0 : i_MaxToConfirm) + (_patObj.ccandles - 1)
        returnMatrixCol = _patObj.partition
        returnArrObj = matrix.get(PERCENTRETURNS, returnMatrixRow, returnMatrixCol)
        
        // Initialize the returnArray if it hasn't been yet, reassign (necessary).
        if na(returnArrObj)
            matrix.set(PERCENTRETURNS, returnMatrixRow, returnMatrixCol, f_InitializeReturnArray())
            returnArrObj := matrix.get(PERCENTRETURNS, returnMatrixRow, returnMatrixCol)
            // Only add new points when these returns are initialized.
            array.push(RETURNLOCATIONS, point.new(x = returnMatrixRow, y = returnMatrixCol))

        // This will keep the label color the same, while changing the color of the 
        // bars at the pattern.
        if i_EnableAdaptCol
            confColor := f_GrabColorFromGrad(returnArrObj)

        // Display stats on label, 
        f_DisplayStatsOnLabel(_patObj, returnArrObj)
        array.push(COLOROFFSET, _patObj.ccandles)
        array.set(CANDLECOLOR, _patObj.ccandles - 1, confColor)
        true
    else
        // RetVal used here to force code past if...else block to execute.
        bool retVal = na
        
        // Decrement on non-confirms
        _patObj.candlesLeft := CURRBARSTATE ? _patObj.candlesLeft - 1 : _patObj.candlesLeft
        if _patObj.candlesLeft == 0
            _patObj.confirmed := false
            label.delete(_patObj.patLbl)

            retVal := true
        // Unconfirmed patterns update
        else
            f_UpdateLabel(1, _patObj)
            
            retVal := false
        
        // Add the color to the correct indices for candle coloring
        candleColorIndex = (i_MaxToConfirm - _patObj.candlesLeft)
        array.push(COLOROFFSET, candleColorIndex + barstateOffset)
        array.set(CANDLECOLOR, candleColorIndex, candleColorIndex == i_MaxToConfirm ? na : ic_Unconfirmed)

        // handle non-confirmed coloration
        if retVal and i_ShowNonConfirmed
            array.push(COLOROFFSET, i_MaxToConfirm)
            array.set(CANDLECOLOR, i_MaxToConfirm - 1, ic_NonConfirmed)

        retVal

// Calculate Percent Return will calculate from open->close the percent change
// in price for a specified length (at an offset if specified).
//
//  @params:
//          _length - How far back to grab the open from previous price data.
//          _offset - The number of candles to offset this open value with 
//                  (mainly for "FROM APPEARANCE" setting, this offset is 
//                  typically the number of candles required for confirmation).
//
//  @return:
//      float - The percent change (ratio multiplied by 100) of the close[_offset] 
//            compared to the open[_length + _offset] candles ago.
//
f_CalculatePercentReturn(int _length, int _offset) =>
    openAt = open[_length + _offset]
    closeAfter = close[_offset]

    percentRet = ((closeAfter - openAt) / openAt) * 100

    percentRet

// This function will append the given return array with a new return. It will 
// also update all relevant statistics for that return array:
//    The array's size
//    Array's average
//    Array's median
//    Array's standard deviation
//    Which polarity of returns this pattern has impacted
//
// @inputs:
//    i_(Nega/Posi)tiveRetTol - These two inputs specify which polarities to
//                            change dependent upon the _returnVal to be added. 
//                            
//                            If the _returnVal is:
//                              <= i_NegativeRetTol -> increment negative val
//                              > i_NegativeRetTol & < i_PositiveRetTol -> 
//                              increment neutral val
//                              >= i_PositiveRetTol -> increment positive val
// 
// @params:
//       returnArray _r - The return array that corresponds to this pattern.
//     float _returnVal - The value to add to the returnArray's collection and
//                      generate statistics from.
//
// @returns:
//      void
//
f_AddReturnAndUpdate(returnArray _r, float _returnVal) =>
    array.push(_r.returns, _returnVal)
    polarityToInc = _returnVal <= i_NegativeRetTol ? 0 : _returnVal > 
      i_NegativeRetTol and _returnVal < i_PositiveRetTol ? 1 : 2
    currPolVal = array.get(_r.polarities, polarityToInc)
    array.set(_r.polarities, polarityToInc, currPolVal + 1)
    _r.size := _r.size + 1
    _r.stdDev := array.stdev(_r.returns)
    _r.median := array.median(_r.returns)
    _r.avg := array.avg(_r.returns)

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║      __  ___      _          ______      ____   ____  __           __     ║
// ║     /  |/  /___ _(_)___     / ____/___ _/ / /  / __ )/ /___  _____/ /__   ║
// ║    / /|_/ / __ `/ / __ \   / /   / __ `/ / /  / __  / / __ \/ ___/ //_/   ║
// ║   / /  / / /_/ / / / / /  / /___/ /_/ / / /  / /_/ / / /_/ / /__/ ,<      ║
// ║  /_/  /_/\__,_/_/_/ /_/   \____/\__,_/_/_/  /_____/_/\____/\___/_/|_|     ║
// ║                                                                           ║
// ║ This section will utilize all the defined functions above to identify     ║
// ║ Hikkake patterns and process them through their lifespan. It creates the  ║
// ║ alerts, places labels, processes confirmations, performs the % return     ║
// ║ analysis, and colors bars to user specification.                          ║
// ║                                                                           ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

if bar_index > 3
    
    hikkakeTestVal = f_MatchConfig()

    if not na(hikkakeTestVal)
        array.push(UNCONFIRMEDHIKKAKES, hikkakeTestVal)
        f_AlertOnFound(hikkakeTestVal)
    unconfSize = array.size(UNCONFIRMEDHIKKAKES)
    updateNecessary = unconfSize != 0

    // Only run testing logic when there was a recently found or confirmed hikkake.
    if updateNecessary
        // Stack -> FILO enforced
        indicesToRemove = array.new<int>()
        for i = 0 to unconfSize - 1
            patToTest = array.get(UNCONFIRMEDHIKKAKES, i)
            // This must be done first because f_GrabColorFromGrad references the 
            // global variables MIN/MAXRETURNVAL (function variables cannot be mutable).
            if i_EnableAdaptCol
                if not i_HardLimit
                    [min, max] = f_MatrixMinMax()
                    MINRETURNVAL := na(min) or min > MINRETURNVAL ? MINRETURNVAL : min
                    MAXRETURNVAL := na(max) or max < MAXRETURNVAL ? MAXRETURNVAL : max
            confirmed = f_RunPatternConfirmation(patToTest)
            if confirmed
                array.push(indicesToRemove, i)
                f_AlertOnConfirm(patToTest)
                f_AlertOnNonConfirm(patToTest)
        
        // Pop all items from the stack and remove them from the UNCONFIRMEDHIKKAKES 
        // array in reverse order to avoid corrupting that array (reverse order 
        // prevents index-out-of-bounds errors/removing unconfirmed patterns as 
        // though they had been confirmed).
        indicesRemovedSize = array.size(indicesToRemove)
        // The loop will attempt to execute even if the size of the array is 0 
        // and even if the step specified is 'by +1'
        if indicesRemovedSize != 0
            for j = 0 to indicesRemovedSize - 1
                currInd = array.pop(indicesToRemove)
                patternRem = array.remove(UNCONFIRMEDHIKKAKES, currInd)
                if patternRem.confirmed
                    confirmedPatternsLL := 
                      f_LinkNextConfirmed(confirmedPatternsLL, f_DeriveConfirmObj(patternRem))

    // +1 used here to properly offset % return calculations since confirmation objects are
    // created on the candle that had confirmed the pattern; not the candle where price 
    // difference measurements are initiated.
    confPatLLBacktest = confirmedPatternsLL[i_PnLSampleLength + 1]
    if not na(confPatLLBacktest) and CURRBARSTATE
        tmp = confPatLLBacktest
        while not na(tmp)
            patType = confPatLLBacktest.patType
            confCandles = confPatLLBacktest.ccandles
            matrixRow = (patType == 1 ? 0 : i_MaxToConfirm) + (confCandles - 1)
            matrixCol = confPatLLBacktest.part

            returnArrToAppend = matrix.get(PERCENTRETURNS, matrixRow, matrixCol)

            PnLOffset = i_PnLType == "FROM CONFIRMATION" ? 0 : confCandles
            percentReturned = f_CalculatePercentReturn(i_PnLSampleLength, PnLOffset)

            hikkakeBackTest = hikkakeTestVal[i_PnLSampleLength + confCandles]

            f_AddReturnAndUpdate(returnArrToAppend, percentReturned)
            // Add return to the label created for this pattern.
            f_AppendReturnToTip(hikkakeBackTest, percentReturned)
            f_AlertWithReturn(hikkakeBackTest, percentReturned)
            tmp := tmp.link

// barcolor() does not like variables for the offset, no idea why.
barcolor(color=array.includes(COLOROFFSET, 6) ? array.get(CANDLECOLOR, 5) : na, offset=-8)
barcolor(color=array.includes(COLOROFFSET, 6) ? array.get(CANDLECOLOR, 5) : na, offset=-7)
barcolor(color=array.includes(COLOROFFSET, 6) ? array.get(CANDLECOLOR, 5) : na, offset=-6)

barcolor(color=array.includes(COLOROFFSET, 5) ? array.get(CANDLECOLOR, 4) : na, offset=-7)
barcolor(color=array.includes(COLOROFFSET, 5) ? array.get(CANDLECOLOR, 4) : na, offset=-6)
barcolor(color=array.includes(COLOROFFSET, 5) ? array.get(CANDLECOLOR, 4) : na, offset=-5)

barcolor(color=array.includes(COLOROFFSET, 4) ? array.get(CANDLECOLOR, 3) : na, offset=-6)
barcolor(color=array.includes(COLOROFFSET, 4) ? array.get(CANDLECOLOR, 3) : na, offset=-5)
barcolor(color=array.includes(COLOROFFSET, 4) ? array.get(CANDLECOLOR, 3) : na, offset=-4)

barcolor(color=array.includes(COLOROFFSET, 3) ? array.get(CANDLECOLOR, 2) : na, offset=-5)
barcolor(color=array.includes(COLOROFFSET, 3) ? array.get(CANDLECOLOR, 2) : na, offset=-4)
barcolor(color=array.includes(COLOROFFSET, 3) ? array.get(CANDLECOLOR, 2) : na, offset=-3)

barcolor(color=array.includes(COLOROFFSET, 2) ? array.get(CANDLECOLOR, 1) : na, offset=-4)
barcolor(color=array.includes(COLOROFFSET, 2) ? array.get(CANDLECOLOR, 1) : na, offset=-3)
barcolor(color=array.includes(COLOROFFSET, 2) ? array.get(CANDLECOLOR, 1) : na, offset=-2)

barcolor(color=array.includes(COLOROFFSET, 1) ? array.get(CANDLECOLOR, 0) : na, offset=-3)
barcolor(color=array.includes(COLOROFFSET, 1) ? array.get(CANDLECOLOR, 0) : na, offset=-2)
barcolor(color=array.includes(COLOROFFSET, 1) ? array.get(CANDLECOLOR, 0) : na, offset=-1)