Treasury Yields Heatmap [By MUQWISHI]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © MUQWISHI

//@version=5
indicator("Treasury Yields Heatmap", overlay = true)

// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
// |                                   INPUT                                    |
// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
// +++++++++++++++ Table Settings
// Location 
tablePos = input.string("Middle Right", "Table Location ", 
             ["Top Right" , "Middle Right"  , "Bottom Right" , 
              "Top Center", "Middle Center" , "Bottom Center", 
              "Top Left"  , "Middle Left"   , "Bottom Left" ], inline = "1", group = "Table Setting")

// Size
tableSiz = input.string("Small", "Table Size   ", 
             ["Auto", "Huge", "Large", "Normal", "Small", "Tiny"], inline = "2", group = "Table Setting")

// Table Color
tBgCol = input.color(#696969, " Title",  group = "Table Setting", inline = "1")
txtCl1 = input.color(#ffffff, " Text​ ​",  group = "Table Setting", inline = "2")

upCol  = input.color(#006400, "Cell ",   group = "Table Setting", inline = "3")
mdCol  = input.color(#FFEF00, " ",       group = "Table Setting", inline = "3")
dnCol  = input.color(#882336, " ",       group = "Table Setting", inline = "3")
txtCl2 = input.color(#000000, "  Text ", group = "Table Setting", inline = "3")

// +++++++++++++++ Technical Setting
contry = input.string("United States", "Country", ["Australia", "Brazil", "Canada", "China", "Euro", "France", "Germany",
 "India", "Indonesia", "Italy", "Japan", "Russia", "South Africa", "South Korea", "United Kingdom", "United States"], group = "Technical Setting")
timFrm = input.string("Quarterly", "Timeframe", ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"], group = "Technical Setting")
calMod = input.string("Period", "Fetch By", ["Date", "Period"], group = "Technical Setting")
sDate  = input.time(timestamp("01 Jan 2019 00:00"), " ‣ Date", group = "Technical Setting")
sPerd  = input.int(25, " ‣ Period", 1, 200, group = "Technical Setting")

// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
// |                                 CALCULATION                                |
// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
code = switch contry 
    "United States"  => "US" 
    "Euro"           => "EU"
    "United Kingdom" => "GB"
    "Germany"        => "DE" 
    "France"         => "FR"
    "Italy"          => "IT"
    "Canada"         => "CA"
    "Japan"          => "JP"
    "India"          => "IN"
    "China"          => "CN"
    "Indonesia"      => "ID"
    "Australia"      => "AU"
    "Brazil"         => "BR"
    "Russia"         => "RU"
    "South Korea"    => "KR"
    "South Africa"   => "ZA"
    "Turkey"         => "TR"


//++++++++++ Pearson Correlation
pearson(x) =>
    // Clean NaNs in Matrix
    a = array.new<float>(0)
    if array.size(x) > 0
        for i = 1 to x.size() - 1
            val = x.get(i)
            if not na(val)
                a.push(val)
    
    // Calculate Pearson Correlation
    r = float(na)
    if array.size(a) > 3
        n = a.size()
        X = 0.0,    Y = 0.0, 
        
        for i = 0 to n - 1
            X += i,   
            Y += array.get(a, i)

        Xmean = X/n, 
        Ymean = Y/n
        
        XY = 0.0,   X2 = 0.0,   Y2 = 0.0
        for i = 0 to n - 1
            XY += (i - Xmean) * (a.get(i) - Ymean)
            X2 += math.pow(i - Xmean,           2)
            Y2 += math.pow(a.get(i) - Ymean,    2)
        
        // Pearson Correlation Coefficient
        r := XY / math.sqrt(X2 * Y2)
    r


//++++++++++ Main function
main() =>
    var cls = array.new<float>(na)
    var tim = array.new<float>(na)
    
    if calMod == "Date" ? time >= sDate : true
        cls.unshift(math.round_to_mintick(close)) 
        tim.unshift(int(time/100000))

    if calMod == "Date" ? cls.size() > 200 : cls.size() > sPerd
        cls.pop()
        tim.pop()
   
    [cls, tim]

//++++++++++ Collect Data
// Title Array
titl = array.new<string>(na)

// Security Function
call(sym, nm) =>
    tf = timFrm == "Yearly"  ? "12M" : timFrm == "Quarterly" ? "3M" : 
         timFrm == "Monthly" ? "1M"  : timFrm == "Weekly"    ? "1W" : "1D"
    [cls, tim]  = request.security(sym, tf, main(), ignore_invalid_symbol = true)
    
    c = array.new<float>(0)
    t = array.new<float>(0)

    if not na(cls)
        array.push(titl,  nm)
        c := cls
        t := tim 
    [c, t]

// Import Data
[mn01, tmn01] = call(code + "01MY",  "1Mo"), [mn03, tmn03] = call(code + "03MY",  "3Mo"), 
[mn06, tmn06] = call(code + "06MY",  "6Mo"), [yr01, tyr01] = call(code +  "01Y",  "1Yr"), 
[yr03, tyr03] = call(code +  "03Y",  "3Yr"), [yr05, tyr05] = call(code +  "05Y",  "5Yr"), 
[yr07, tyr07] = call(code +  "07Y",  "7Yr"), [yr10, tyr10] = call(code +  "10Y", "10Yr"), 
[yr30, tyr30] = call(code +  "30Y", "30Yr"), 

// Add "Date" & "Yield Curve" Columns to title array
array.unshift(titl, "Date")
array.push(titl, "Yield Curve")

//++++++++++ Build Matrix
cols = titl.size() -1
rows = math.max(mn01.size(), mn03.size(), mn06.size(), yr01.size(), yr03.size(), yr05.size(), yr07.size(), yr10.size(), yr30.size())
mtx  = matrix.new<float>(rows, cols, na)

addMtx(r, tim, nCol, x, y) =>
    if x.size() > 0 and y.size() > 0
        idx = array.indexof(y, mtx.get(r, 0))
        if idx > 0 
            mtx.set(r, titl.indexof(nCol), array.get(x, idx))

        else if mtx.get(r, 0) - y.get(0) == 0
            mtx.set(r, titl.indexof(nCol), x.get(0))

if barstate.islast
    if rows > 0 
        // Fill 1st Cell with Time
        if rows == yr30.size()
            mtx.add_col(0, tyr30)
        else if rows == yr10.size()
            mtx.add_col(0, tyr10)
        else if rows == yr07.size()
            mtx.add_col(0, tyr07)
        else if rows == yr05.size()
            mtx.add_col(0, tyr05)
        else if rows == yr03.size()
            mtx.add_col(0, tyr03)
        else if rows == yr01.size()
            mtx.add_col(0, tyr01)
        else if rows == mn06.size()
            mtx.add_col(0, tmn06)
        else if rows == mn03.size()
            mtx.add_col(0, tmn03)
        else if rows == mn01.size() 
            mtx.add_col(0, tmn01)
        
        for r = 0 to rows - 1
            tim = mtx.get(r, 0)
            // Fill Matrix With Bonds
            addMtx(r, tim,  "1Mo", mn01, tmn01), addMtx(r, tim,  "3Mo", mn03, tmn03), 
            addMtx(r, tim,  "6Mo", mn06, tmn06), addMtx(r, tim,  "1Yr", yr01, tyr01), 
            addMtx(r, tim,  "3Yr", yr03, tyr03), addMtx(r, tim,  "5Yr", yr05, tyr05),
            addMtx(r, tim,  "7Yr", yr07, tyr07), addMtx(r, tim, "10Yr", yr10, tyr10), 
            addMtx(r, tim, "30Yr", yr30, tyr30),

            // Find Pearson Coefficient and add it. 
            mtx.set(r, cols, pearson(mtx.row(r)))


// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
// |                                   TABLE                                    |
// |++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
// Get Tbale Location & Size
locNsze(x) => 
    y   = str.split(str.lower(x), " ")
    out = ""
    for i = 0 to array.size(y) - 1
        out := out + array.get(y, i)
        if i != array.size(y) - 1
            out := out + "_"
    out

// Create Table
tbl = table.new(locNsze(tablePos), cols+1, rows+2, color.new(tBgCol, 100), 
                 color.new(tBgCol, 100), 1, color.new(tBgCol, 100), 1)

// Get Cell Function
cell(col, row, txt, color, txtCol) => 
    table.cell(tbl, col, row, text = txt, text_color = txtCol, bgcolor = color, 
     text_size = locNsze(tableSiz))

// Get Cell Color
cellCol(max, min, val) =>
    avg = (min + max)/2
    if val > avg
        color.from_gradient(val, avg, max, mdCol, upCol)
    else if val <= avg
        color.from_gradient(val, min, avg, dnCol, mdCol)
    else 
        tBgCol

// Get Time Format
timeFrm(x) =>
    mnNm = array.from("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
    frmt = timFrm == "Yearly" ? "yyyy" : timFrm == "Quarterly" or timFrm == "Monthly" ? "MM-yyyy" : "dd-MM-yyyy"

    out  = str.format_time(int(x*100000)+86400000, frmt, syminfo.timezone)
    out := timFrm == "Quarterly" or timFrm == "Monthly" ? mnNm.get(int(str.tonumber(str.split(out, "-").get(0)))-1) 
           + "-" + str.split(out, "-").get(1) : out

// Get Yield Status
yield(x) =>
    out = x >=  0.7 ? "Normal"   : x <  0.7 and x >=  0.35 ? "Slight Normal"   :
          x <= -0.7 ? "Inverted" : x > -0.7 and x <= -0.35 ? "Slight Inverted" : "Flat"


if barstate.islast and rows > 0
    if mtx.rows() > 0
        //+++++++ Find Min & Max
        m = mtx.submatrix(0, mtx.rows()-1, 1, mtx.columns()-2)
        maxVal = m.max()
        minVal = m.min()

        //+++++++ Fill Table
        y = 0

        // Header
        cell(0, y, (str.contains(contry, " ") ? "The ": "") + contry 
                 + " Treasury Yields " + timFrm + " Intervals: " 
                 + timeFrm(mtx.get(rows - 1, 0)) +" To "+ timeFrm(mtx.get(0, 0)), 
                  tBgCol, txtCl1), table.merge_cells(tbl, 0, 0, cols, 0), y += 1

        // Title Columns
        for i = 0 to cols
            cell(i, y, titl.get(i), tBgCol, txtCl1)
        y += 1

        // Cells
        for j = 0 to rows - 1
            for i = 0 to cols
                if i == 0
                    cell(i, y, timeFrm(mtx.get(j, i)), tBgCol, txtCl1)

                else if i == cols
                    val = mtx.get(j, i)
                    cell(i, y, na(val) ? "" : yield(val), cellCol(1, -1, val), txtCl2)
                
                else
                    val = mtx.get(j, i)
                    cell(i, y, na(val) ? "" : str.tostring(val) + "%", cellCol(maxVal, minVal, val), txtCl2)
            y += 1

