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

//@version=5

// +------------------------------+------------------------+
// |  Legacy (COT) Metric Names   |       Directions       |
// +------------------------------+------------------------+
// | Open Interest                | No direction           |
// | Noncommercial Positions      | Long, Short, Spreading |
// | Commercial Positions         | Long, Short            |
// | Total Reportable Positions   | Long, Short            |
// | Nonreportable Positions      | Long, Short            |
// | Traders Total                | No direction           |
// | Traders Noncommercial        | Long, Short, Spreading |
// | Traders Commercial           | Long, Short            |
// | Traders Total Reportable     | Long, Short            |
// | Concentration Gross LT 4 TDR | Long, Short            |
// | Concentration Gross LT 8 TDR | Long, Short            |
// | Concentration Net LT 4 TDR   | Long, Short            |
// | Concentration Net LT 8 TDR   | Long, Short            |
// +------------------------------+------------------------+

// +-----------------------------------+------------------------+
// | Disaggregated (COT2) Metric Names |       Directions       |
// +-----------------------------------+------------------------+
// | Open Interest                     | No Direction           |
// | Producer Merchant Positions       | Long, Short            |
// | Swap Positions                    | Long, Short, Spreading |
// | Managed Money Positions           | Long, Short, Spreading |
// | Other Reportable Positions        | Long, Short, Spreading |
// | Total Reportable Positions        | Long, Short            |
// | Nonreportable Positions           | Long, Short            |
// | Traders Total                     | No Direction           |
// | Traders Producer Merchant         | Long, Short            |
// | Traders Swap                      | Long, Short, Spreading |
// | Traders Managed Money             | Long, Short, Spreading |
// | Traders Other Reportable          | Long, Short, Spreading |
// | Traders Total Reportable          | Long, Short            |
// | Concentration Gross LE 4 TDR      | Long, Short            |
// | Concentration Gross LE 8 TDR      | Long, Short            |
// | Concentration Net LE 4 TDR        | Long, Short            |
// | Concentration Net LE 8 TDR        | Long, Short            |
// +-----------------------------------+------------------------+

// +-------------------------------+------------------------+
// | Financial (COT3) Metric Names |       Directions       |
// +-------------------------------+------------------------+
// | Open Interest                 | No Direction           |
// | Dealer Positions              | Long, Short, Spreading |
// | Asset Manager Positions       | Long, Short, Spreading |
// | Leveraged Funds Positions     | Long, Short, Spreading |
// | Other Reportable Positions    | Long, Short, Spreading |
// | Total Reportable Positions    | Long, Short            |
// | Nonreportable Positions       | Long, Short            |
// | Traders Total                 | No Direction           |
// | Traders Dealer                | Long, Short, Spreading |
// | Traders Asset Manager         | Long, Short, Spreading |
// | Traders Leveraged Funds       | Long, Short, Spreading |
// | Traders Other Reportable      | Long, Short, Spreading |
// | Traders Total Reportable      | Long, Short            |
// | Concentration Gross LE 4 TDR  | Long, Short            |
// | Concentration Gross LE 8 TDR  | Long, Short            |
// | Concentration Net LE 4 TDR    | Long, Short            |
// | Concentration Net LE 8 TDR    | Long, Short            |
// +-------------------------------+------------------------+


// @description This library provides tools to help Pine programmers fetch Commitment of Traders (​COT) data for futures.
library("LibraryCOT", false)


isNotEmpty(simple string str) =>
    bool result = str != ""


// @function    Accepts a futures root and returns the relevant CFTC code.
// @param root  Root prefix of the future's symbol, e.g. "ZC" for "ZC1!"" or "ZCU2021".
// @returns     The <CFTCCode> part of a COT ticker corresponding to `root`, or "" if no CFTC code exists for the `root`.
export rootToCFTCCode(simple string root) =>
    switch root
        "ZC"  => "002602" 
        "ZO"  => "004603" 
        "ZS"  => "005602" 
        "ZL"  => "007601" 
        "ZB"  => "020601" 
        "UB"  => "020604" 
        "ZM"  => "026603" 
        "TN"  => "043607" 
        "ZR"  => "039601" 
        "ZQ"  => "045601" 
        "DC"  => "052641" 
        "6E"  => "099741" 
        "6J"  => "097741" 
        "RY"  => "399741" 
        "RP"  => "299741" 
        "6M"  => "095741" 
        "6S"  => "092741" 
        "6R"  => "089741" 
        "6C"  => "090741" 
        "6N"  => "112741" 
        "6B"  => "096742" 
        "6L"  => "102741" 
        "6Z"  => "122741" 
        "RX"  => "124606" 
        "AW"  => "221602" 
        "BTC" => "133741" 
        "MBT" => "133742" 
        "CB"  => "050642" 
        "GDK" => "052644" 
        "CSC" => "063642" 
        "SI"  => "084691" 
        "GC"  => "088691" 
        "PL"  => "076651" 
        "PA"  => "075651" 
        "HE"  => "054642" 
        "LE"  => "057642" 
        "LBS" => "058643" 
        "DY"  => "052645" 
        "GF"  => "061641" 
        "GNF" => "052642" 
        "UFV" => "251607" 
        "SR1" => "134742" 
        "SR3" => "134741" 
        "MGC" => "088695" 
        "SDA" => "43874A" 
        "DX"  => "098662" 
        "CC"  => "073732" 
        "SB"  => "080732" 
        "KC"  => "083731" 
        "CT"  => "033661" 
        "A8K" => "06665T" 
        "6A"  => "232741" 
        "CC"  => "073732" 
        "CT"  => "033661" 
        "OJ"  => "040701" 
        "MME" => "244042"
        "SP"  => "138741" 
        "XK"  => "005603" 
        "RTY" => "239742" 
        "AUP" => "191693" 
        "EDP" => "191696" 
        "ES"  => "13874A" 
        "ZF"  => "044601" 
        "ZN"  => "043602" 
        "ZT"  => "042601" 
        "ZW"  => "001602" 
        "KE"  => "001612" 
        "BWF" => "00160F" 
        "CUP" => "085692" 
        "NKT" => "240741" 
        "NIT" => "240743" 
        "HRC" => "192651" 
        "BUS" => "257652" 
        "MNQ" => "209747" 
        "CRB" => "111A34" 
        "S5M" => "021A65" 
        "CU"  => "025651" 
        "A7E" => "06665B" 
        "WCW" => "067A75" 
        "BZ"  => "06765T" 
        "HTT" => "0676A5" 
        "CSX" => "06765A" 
        "B0"  => "06665O" 
        "AC0" => "06665P" 
        "AD0" => "06665Q" 
        "A7Q" => "06665R" 
        "A1R" => "06665G" 
        "AZ1" => "025608" 
        "MTF" => "024656" 
        "TTF" => "023B98" 
        "VX"  => "1170E1" 
        "EMD" => "33874A" 
        "CL"  => "067651" 
        "NG"  => "023651" 
        "HP"  => "03565C" 
        "NPG" => "023A56" 
        "XAP" => "138748" 
        "XAE" => "138749" 
        "XAF" => "13874C" 
        "XAV" => "13874E" 
        "XAI" => "13874F" 
        "XAU" => "13874J" 
        "MNC" => "03265J" 
        "HO"  => "022651" 
        "A80" => "06665W" 
        "NQ"  => "209742" 
        "QMW" => "001626" 
        "YM"  => "124603" 
        "ETH" => "146021"
        "ND"  => "20974+"
        "GE"  => "132741" 
        "HH"  => "023A55" 
        "WTT" => "067A71" 
        "RB"  => "111659"
        => ""


// @function    Converts a currency string to its corresponding CFTC code.
// @param curr  Currency code, e.g., "USD" for US Dollar.
// @returns     The <CFTCCode> corresponding to the currency, if one exists.
export currencyToCFTCCode(simple string currency) =>
    switch currency
        "BTC" => "133741"
        "USD" => "098662"
        "EUR" => "099741"
        "JPY" => "097741"
        "MXN" => "095741"
        "CHF" => "092741"
        "RUB" => "089741"
        "CAD" => "090741"
        "NZD" => "112741"
        "GBP" => "096742"
        "BRL" => "102741"
        "ZAR" => "122741"
        "AUD" => "232741"
        "ETH" => "146021"
        => ""


// @function             Returns the <includeOptions> part of a COT ticker using the `includeOptions` value supplied, which determines whether options data is to be included.
// @param includeOptions A "bool" value: 'true' if the symbol should include options and 'false' otherwise.
// @returns              The <includeOptions> part of a COT ticker: "FO" for data that includes options and "F" for data that doesn't.
export optionsToTicker(simple bool includeOptions) =>
    includeOptions ? "FO" : "F"


// @function              Returns a string corresponding to a metric name and direction, which is one component required to build a valid COT ticker ID.
// @param metricName      One of the metric names listed in this library's chart. Invalid values will cause a runtime error.
// @param metricDirection Metric direction. Possible values are: "Long", "Short", "Spreading", and "No direction". Valid values vary with metrics. Invalid values will cause a runtime error.
// @returns               The <metricCode><metricDirection> part of a COT ticker ID string, e.g., "OI_OLD" for "Open Interest" and "No direction", or "TC_L" for "Traders Commercial" and "Long".
export metricNameAndDirectionToTicker(simple string metricName, simple string metricDirection) =>
    metricCode = 
      switch metricName
        "Asset Manager Positions"      => "AMP"
        "Commercial Positions"         => "CP"
        "Concentration Gross LE 4 TDR" => "CON_GROSS_LE_4"
        "Concentration Gross LE 8 TDR" => "CON_GROSS_LE_8"
        "Concentration Gross LT 4 TDR" => "CON_GROSS_LT_4"
        "Concentration Gross LT 8 TDR" => "CON_GROSS_LT_8"
        "Concentration Net LE 4 TDR"   => "CON_NET_LE_4"
        "Concentration Net LE 8 TDR"   => "CON_NET_LE_8"
        "Concentration Net LT 4 TDR"   => "CON_NET_LT_4"
        "Concentration Net LT 8 TDR"   => "CON_NET_LT_8"
        "Dealer Positions"             => "DP"
        "Leveraged Funds Positions"    => "LMP"
        "Managed Money Positions"      => "MMP"
        "Noncommercial Positions"      => "NCP"
        "Nonreportable Positions"      => "NRP"
        "Open Interest"                => "OI"
        "Other Reportable Positions"   => "ORP"
        "Producer Merchant Positions"  => "PMR"
        "Swap Positions"               => "SP"
        "Total Reportable Positions"   => "TRP"
        "Traders Asset Manager"        => "TAM"
        "Traders Commercial"           => "TC"
        "Traders Dealer"               => "TD"
        "Traders Leveraged Funds"      => "TLM"
        "Traders Managed Money"        => "TMM"
        "Traders Noncommercial"        => "TNC"
        "Traders Other Reportable"     => "TOR"
        "Traders Producer Merchant"    => "TPM"
        "Traders Swap"                 => "TS"
        "Traders Total"                => "TT"
        "Traders Total Reportable"     => "TTR"
        => runtime.error(str.format("Unrecognized metric: `{0}`", metricName)), na
    
    if metricDirection == "No direction"
        if metricName != "Open Interest" and metricName != "Traders Total"
            runtime.error(str.format("`{0}` direction is not applicable for `{1}`", metricDirection, metricName))
    else if metricDirection == "Spreading"
        isIncorrect = 
          switch metricName
            "Commercial Positions"         => true
            "Total Reportable Positions"   => true
            "Nonreportable Positions"      => true
            "Traders Commercial"           => true
            "Traders Total Reportable"     => true
            "Concentration Gross LT 4 TDR" => true
            "Concentration Gross LT 8 TDR" => true
            "Concentration Net LT 4 TDR"   => true
            "Concentration Net LT 8 TDR"   => true
            "Concentration Gross LE 4 TDR" => true
            "Concentration Gross LE 8 TDR" => true
            "Concentration Net LE 4 TDR"   => true
            "Concentration Net LE 8 TDR"   => true
            "Producer Merchant Positions"  => true
            "Traders Producer Merchant"    => true
            => false
        if isIncorrect
            runtime.error(str.format("`{0}` direction is not applicable for `{1}`", metricDirection, metricName))
    else if metricName == "Open Interest" or metricName == "Traders Total"
        runtime.error(str.format("`{0}` direction is not applicable for `{1}`", metricDirection, metricName))
    
    directionCode = 
      switch metricDirection
        "Long"         => "_L"
        "Short"        => "_S"
        "Spreading"    => "_SPREAD"
        "No direction" => ""
        => runtime.error(str.format("Unrecognized metric direction: `{0}`", metricDirection)), na
        
    metricCode + directionCode


// @function            Converts a metric type into one component required to build a valid COT ticker ID. See the "Old and Other Futures" section of the CFTC's Explanatory Notes for details on types.
// @param metricType    Metric type. Accepted values are: "All", "Old", "Other".
// @returns             The <metricType> part of a COT ticker.
export typeToTicker(simple string metricType) =>
    switch metricType
        "All"   => ""
        "Old"   => "_OLD"
        "Other" => "_OTHER"
        => runtime.error(str.format("Unrecognized metric type: `{0}`", metricType)), na


// @function           Depending on the `mode`, returns a CFTC code using the chart's symbol or its currency information when `convertToCOT = true`. Otherwise, returns the symbol's root or currency information. If no COT data exists, a runtime error is generated.
// @param mode         A string determining how the function will work. Valid values are:
//                       "Root": the function extracts the futures symbol root (e.g. "ES" in "ESH2020") and looks for its CFTC code.
//                       "Base currency": the function extracts the first currency in a pair (e.g. "EUR" in "EURUSD") and looks for its CFTC code.
//                       "Currency": the function extracts the quote currency ("JPY" for "TSE:9984" or "USDJPY") and looks for its CFTC code.
//                       "Auto": the function tries the first three modes (Root -> Base Currency -> Currency) until a match is found.
// @param convertToCOT "bool" value that, when `true`, causes the function to return a CFTC code. Otherwise, the root or currency information is returned. Optional. The default is `true`.
// @returns            If `convertToCOT` is `true`, the <CFTCCode> part of a COT ticker ID string. If `convertToCOT` is `false`, the root or currency extracted from the current symbol.
export convertRootToCOTCode(simple string mode, simple bool convertToCOT = true) =>
    hasRoot = syminfo.root != syminfo.ticker
    root = rootToCFTCCode(syminfo.root)
    baseCurrency = currencyToCFTCCode(syminfo.basecurrency)
    mainCurrency = currencyToCFTCCode(syminfo.currency)
    
    result = switch mode
        "Auto" =>
            switch
                hasRoot and isNotEmpty(root) => convertToCOT ? root : syminfo.root
                isNotEmpty(baseCurrency) => convertToCOT ? baseCurrency : syminfo.basecurrency
                isNotEmpty(mainCurrency) => convertToCOT ? mainCurrency : syminfo.currency
                => ""
        "Root" => convertToCOT ? root : syminfo.root
        "Base currency" => convertToCOT ? baseCurrency : syminfo.basecurrency
        "Currency" => convertToCOT ? mainCurrency : syminfo.currency
        => ""
        
    if result == ""
        runtime.error(str.format("Could not find relevant COT data based on the current symbol and the source search mode `{0}`", mode))
        
    result
    
    
// @function                Returns a valid TradingView ticker for the COT symbol with specified parameters.
// @param COTType           A string with the type of the report requested with the ticker, one of the following: "Legacy", "Disaggregated", "Financial".
// @param CTFCCode          The <CFTCCode> for the asset, e.g., wheat futures (root "ZW") have the code "001602".
// @param includeOptions    A boolean value. 'true' if the symbol should include options and 'false' otherwise.
// @param metricName        One of the metric names listed in this library's chart.
// @param metricDirection   Direction of the metric, one of the following: "Long", "Short", "Spreading", "No direction". 
// @param metricType        Type of the metric. Possible values: "All", "Old", and "Other".
// @returns                 A ticker ID string usable with `request.security()` to fetch the specified Commitment of Traders data.
export COTTickerid(simple string COTType, simple string CFTCCode, simple bool includeOptions, simple string metricName, simple string metricDirection, simple string metricType) =>
    typeCode = switch COTType
        "Legacy" => ""
        "Disaggregated" => "2"
        "Financial" => "3"
        => runtime.error("Unknown `COTType`: " + COTType), na
    
    invalidType = if COTType == "Legacy"
        switch metricName
            "Open Interest"                 => false
            "Traders Total"                 => false
            "Traders Noncommercial"         => false
            "Noncommercial Positions"       => false
            "Commercial Positions"          => false
            "Total Reportable Positions"    => false
            "Nonreportable Positions"       => false
            "Traders Commercial"            => false
            "Traders Total Reportable"      => false
            "Concentration Gross LT 4 TDR"  => false
            "Concentration Gross LT 8 TDR"  => false
            "Concentration Net LT 4 TDR"    => false
            "Concentration Net LT 8 TDR"    => false
    
    else if COTType == "Financial"
        switch metricName
            "Open Interest"                 => false
            "Traders Total"                 => false
            "Managed Money Positions"       => false
            "Other Reportable Positions"    => false
            "Swap Positions"                => false
            "Traders Managed Money"         => false
            "Traders Other Reportable"      => false
            "Traders Swap"                  => false
            "Concentration Gross LE 4 TDR"  => false
            "Concentration Gross LE 8 TDR"  => false
            "Concentration Net LE 4 TDR"    => false
            "Concentration Net LE 8 TDR"    => false
            "Nonreportable Positions"       => false
            "Producer Merchant Positions"   => false
            "Traders Producer Merchant"     => false
            "Total Reportable Positions"    => false
            "Traders Total Reportable"      => false
    
    else if COTType == "Disaggregared"
        switch metricName
            "Open Interest"                 => false
            "Traders Total"                 => false
            "Asset Manager Positions"       => false
            "Dealer Positions"              => false
            "Leveraged Funds Positions"     => false
            "Other Reportables Positions"   => false
            "Traders Asset Manager"         => false
            "Traders Dealer"                => false
            "Traders Leveraged Funds"       => false
            "Traders Other Reportables"     => false
            "Concentration Gross LE 4 TDR"  => false
            "Concentration Gross LE 8 TDR"  => false
            "Concentration Net LE 4 TDR"    => false
            "Concentration Net LE 8 TDR"    => false
            "Nonreportable Positions"       => false
            "Total Reportable Positions"    => false
            "Traders Total Reportable"      => false
    
    if invalidType
        runtime.error(str.format("The `{0}` metric is not applicable for the `{1}` COT type", metricName, COTType))
    
    str.format("COT{0}:{1}_{2}_{3}{4}", typeCode, CFTCCode, optionsToTicker(includeOptions), metricNameAndDirectionToTicker(metricName, metricDirection), typeToTicker(metricType))


// ————— Example plot
exampleTicker = COTTickerid(
                     COTType = "Legacy",
                     CFTCCode = convertRootToCOTCode("Auto"),
                     includeOptions = false, 
                     metricName = "Open Interest",
                     metricDirection = "No direction",
                     metricType = "All")
s1 = request.security(exampleTicker, "1D", close)
plot(s1, style = plot.style_stepline_diamond)
