Dividend Calendar (Zeiierman)
// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
//@version=5
// © Zeiierman {

// ~~ Description {
_= "
 The Dividend Calendar (Zeiierman) is a financial tool designed to assist investors and analysts in the stock market by providing    
 a schedule of expected dividend payments from various companies. This tool is essential for investors focusing on dividend income,                                                                                                                          
 as it helps them effectively plan and manage their investment strategies. It offers a comprehensive view of dividend payout dates,                
 aiding in informed decision-making and optimizing dividend income through timely stock transactions. Additionally, the Dividend Calendar                                                                                                                                          
 is useful for forecasting cash flow and evaluating the financial stability and consistency of dividend payments from different companies."

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

//@version=5
indicator("Dividend Calendar (Zeiierman)",overlay=true)
//~~}

// ~~ Inputs {
// ~~ Tooltips {
t1 = "The first input box is for the currency of the dividends. It is set to USD by default, which means that all dividend values are displayed in USD. You can change this to your local currency. \n\nSelect if you want to show the Ex-dividend day or the Actual Payout day. \n\nEstimate Forward enables traders to predict future dividends."
t2 = "2Choose whether you want to view the full dividend calendar, just the cumulative monthly dividend, or a summary. \n\nEnable dividend growth based on the number of years."
t3 = "The first input field is where you specify the stock from which you want to track the dividend. \n\nThe second input field is the number of shares you hold in that particular stock. \n\nThe third input field enables you to customize the dividend payout amount. \n\nThe fourth input field allows you to customize the payout months. For example, type it like this: 1,2,3,5, which corresponds to January, February, March, and May."
//~~}

// ~~ Inputs {
//General inputs
userCurrency = input.string("USD","Currency        ",group="Dividend Calendar", inline="Currency")
xday         = input.string("Ex-Date","",options=["Ex-Date","Pay-Date"],group="Dividend Calendar",inline="Currency")
estimate     = input.bool(true,"Estimate Forward",group="Dividend Calendar",inline="Currency", tooltip=t1)

//Table
tableDesign  = input.string("Full Calendar","Dividend Table",["Full Calendar","Monthly","Summary"],group="Dividend Calendar",inline="Calendar")
showGrowth   = input.bool(false,"Growth",group="Dividend Calendar",inline="Calendar")
growthyears  = input.int(5,"",minval=2,group="Dividend Calendar",inline="Calendar", tooltip=t2)

//Stocks & user inputs
h1   = input.bool(true,"Customize | Tickers | Shares | Custom Payout | Custom Month (1,4)", group="Dividend Calendar", inline="", tooltip=t3)

show = array.from(
 input.bool(true, "", inline = "00"), 
 input.bool(true, "", inline = "01"), 
 input.bool(true, "", inline = "02"), 
 input.bool(true, "", inline = "03"), 
 input.bool(true, "", inline = "04"), 
 input.bool(true, "", inline = "05"), 
 input.bool(true, "", inline = "06"), 
 input.bool(true, "", inline = "07"), 
 input.bool(true, "", inline = "08"), 
 input.bool(true, "", inline = "09"),
 input.bool(false, "", inline = "10"), 
 input.bool(false, "", inline = "11"), 
 input.bool(false, "", inline = "12"), 
 input.bool(false, "", inline = "13"), 
 input.bool(false, "", inline = "14"), 
 input.bool(false, "", inline = "15"), 
 input.bool(false, "", inline = "16"), 
 input.bool(false, "", inline = "17"), 
 input.bool(false, "", inline = "18"), 
 input.bool(false, "", inline = "19")
 )

s00 = input.symbol("KO", "", inline = "00")
s01 = input.symbol("JNJ", "", inline = "01")
s02 = input.symbol("PG", "", inline = "02")
s03 = input.symbol("AWR", "", inline = "03")
s04 = input.symbol("TXN", "",   inline = "04")
s05 = input.symbol("O", "",    inline = "05")
s06 = input.symbol("BLK", "",  inline = "06")
s07 = input.symbol("LMT", "", inline = "07")
s08 = input.symbol("DOV", "", inline = "08")
s09 = input.symbol("ADP", "",  inline = "09")
s10 = input.symbol("MCD", "", inline = "10")
s11 = input.symbol("WMT", "", inline = "11")
s12 = input.symbol("NWN", "", inline = "12")
s13 = input.symbol("VZ", "", inline = "13")
s14 = input.symbol("T", "",   inline = "14")
s15 = input.symbol("INTC", "",    inline = "15")
s16 = input.symbol("PFE", "",  inline = "16")
s17 = input.symbol("CVX", "", inline = "17")
s18 = input.symbol("XOM", "", inline = "18")
s19 = input.symbol("AA", "",  inline = "19")

shares = array.from(
 input.int(100, title="", inline = "00"),
 input.int(100, title="", inline = "01"),
 input.int(100, title="", inline = "02"),
 input.int(100, title="", inline = "03"),
 input.int(100, title="", inline = "04"),
 input.int(100, title="", inline = "05"),
 input.int(100, title="", inline = "06"),
 input.int(100, title="", inline = "07"),
 input.int(100, title="", inline = "08"),
 input.int(100, title="", inline = "09"),
 input.int(100, title="", inline = "10"),
 input.int(100, title="", inline = "11"),
 input.int(100, title="", inline = "12"),
 input.int(100, title="", inline = "13"),
 input.int(100, title="", inline = "14"),
 input.int(100, title="", inline = "15"),
 input.int(100, title="", inline = "16"),
 input.int(100, title="", inline = "17"),
 input.int(100, title="", inline = "18"),
 input.int(100, title="", inline = "19")
 )

customPayout = array.from(
 input.float(0, title="", inline = "00"),
 input.float(0, title="", inline = "01"),
 input.float(0, title="", inline = "02"),
 input.float(0, title="", inline = "03"),
 input.float(0, title="", inline = "04"),
 input.float(0, title="", inline = "05"),
 input.float(0, title="", inline = "06"),
 input.float(0, title="", inline = "07"),
 input.float(0, title="", inline = "08"),
 input.float(0, title="", inline = "09"),
 input.float(0, title="", inline = "10"),
 input.float(0, title="", inline = "11"),
 input.float(0, title="", inline = "12"),
 input.float(0, title="", inline = "13"),
 input.float(0, title="", inline = "14"),
 input.float(0, title="", inline = "15"),
 input.float(0, title="", inline = "16"),
 input.float(0, title="", inline = "17"),
 input.float(0, title="", inline = "18"),
 input.float(0, title="", inline = "19")
 )

customMonth = array.from(
 input.string("","",inline="00"),
 input.string("","",inline="01"),
 input.string("","",inline="02"),
 input.string("","",inline="03"),
 input.string("","",inline="04"),
 input.string("","",inline="05"),
 input.string("","",inline="06"),
 input.string("","",inline="07"),
 input.string("","",inline="08"),
 input.string("","",inline="09"),
 input.string("","",inline="10"),
 input.string("","",inline="11"),
 input.string("","",inline="12"),
 input.string("","",inline="13"),
 input.string("","",inline="14"),
 input.string("","",inline="15"),
 input.string("","",inline="16"),
 input.string("","",inline="17"),
 input.string("","",inline="18"),
 input.string("","",inline="19")
 )

//Table design
pos          = input.string(position.top_right, title="Position",options =[position.top_right,position.top_center,
 position.top_left,position.bottom_right,position.bottom_center,position.bottom_left,position.middle_right,position.middle_left],group="Style",inline="tbl")
TblSize      = input.string(size.auto,title="",options=[size.auto,size.tiny,size.small,size.normal,size.large,size.huge],group="Style",inline="tbl")
bullbackgr   = input.color(#11132d,"Bg",group="Style",inline="tbl")
frame        = input.color(color.rgb(255, 255, 255),"Frame",group="Style",inline="tbl")
textcol      = input.color(color.white,"Text",group="Style",inline="col")
col_1        = input.color(color.new(#60e565, 80),title="Divended",  group="Style",inline="col")
col_2        = input.color(color.new(#1848cc, 80),title="Summary", group="Style",inline="col")
col_3        = input.color(#0c3299,"Header", group="Style",inline="col")
col_4        = input.color(#1848cc,"", group="Style",inline="col")
//~~}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Functions {
//Estimation
Estimate(mat)=>
    date = math.round(str.tonumber(str.format_time(time,"MM")))
    for [i,row] in mat
        max = row.max()
        for j=date-1 to 11
            if mat.get(i,j)!=0
                mat.set(i,j,max)

//Sum the months and stocks
Sum(arr,ft)=>
    newArray = array.new<float>()
    for i=0 to (ft?arr.columns():arr.rows())-1
        newArray.push(math.round((ft?arr.col(i).sum():arr.row(i).sum()),2))
    newArray

//Stock dividend growth 
Growth(growth)=>
    growthArray = array.new<float>()
    for [i,g] in growth
        percentages = array.new<float>()
        pays = g.gr
        for j=1 to pays.size()-1
            pres = pays.get(j)
            prev = pays.get(j-1)
            perc = (pres-prev)/prev
            percentages.push(perc)
        growthArray.push(percentages.avg())
    growthArray

//Table header design
TableBG(col)=>
    even = array.from(0,2,4,6,8,10,12)
    odd  = array.from(1,3,5,7,9,11)
    if even.includes(col)
        color.new(col_3,25)
    else
        col_4

//Switch text in Summary Table
TextSwitch(t1,t2)=>
    varip txtchange = true
    txtchange := not txtchange
    txt = txtchange ? t1 : t2

//Custom month if user enteres custom months using "," as reference. Example "1,4,7,10"
method CustomMonth(matrix<float> m,i,cash)=>
    user_input = customMonth.get(i)
    if str.contains(user_input,",")
        split = str.split(user_input,",")
        for mon in split
            m.set(i,math.round(str.tonumber(mon))-1,cash)
    else
        m.set(i,math.round(str.tonumber(user_input))-1,cash)

//Get stock data
Requests(ticker)=>
    tickerid = str.replace(ticker,":",";",0)
    payTime = request.security("ESD_FACTSET:"+tickerid+";DIVIDENDS","D",time,lookahead=barmerge.lookahead_on,currency=userCurrency)
    payOut = request.security("ESD_FACTSET:"+tickerid+";DIVIDENDS","D",close,lookahead=barmerge.lookahead_on,currency=userCurrency)
    [payTime,payOut]
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ UDT's & Variables & Arrays {

// ~~ UDT {
type Growth
    array<float> gr

//~~}

// ~~ Variables {
var refreshcount = 0 

//Table
var tbl = table.new(pos, 15,30,bullbackgr,frame,2,frame,2)

//Request the data
[l00, t00] = Requests(s00)
[l01, t01] = Requests(s01)
[l02, t02] = Requests(s02)
[l03, t03] = Requests(s03)
[l04, t04] = Requests(s04)
[l05, t05] = Requests(s05)
[l06, t06] = Requests(s06)
[l07, t07] = Requests(s07)
[l08, t08] = Requests(s08)
[l09, t09] = Requests(s09)
[l10, t10] = Requests(s10)
[l11, t11] = Requests(s11)
[l12, t12] = Requests(s12)
[l13, t13] = Requests(s13)
[l14, t14] = Requests(s14)
[l15, t15] = Requests(s15)
[l16, t16] = Requests(s16)
[l17, t17] = Requests(s17)
[l18, t18] = Requests(s18)
[l19, t19] = Requests(s19)

//Alert
alert = false
alert_msg = ""

//~~}

// ~~ Arrays {

//Symbols array
symbols = array.from(
 s00,
 s01,
 s02,
 s03,
 s04,
 s05,
 s06,
 s07,
 s08,
 s09,
 s10,
 s11,
 s12,
 s13,
 s14,
 s15,
 s16,
 s17,
 s18,
 s19
 )

// Paytime array
payTime = array.from(
 l00,
 l01,
 l02,
 l03,
 l04,
 l05,
 l06,
 l07,
 l08,
 l09,
 l10,
 l11,
 l12,
 l13,
 l14,
 l15,
 l16,
 l17,
 l18,
 l19
 )

// Payout array
payOut = array.from(
 t00,
 t01,
 t02,
 t03,
 t04,
 t05,
 t06,
 t07,
 t08,
 t09,
 t10,
 t11,
 t12,
 t13,
 t14,
 t15,
 t16,
 t17,
 t18,
 t19
 )

//Months
months = array.from(
 "January",
 "February",
 "Mars",
 "April",
 "May",
 "June",
 "July",
 "August",
 "September",
 "Oktober",
 "November",
 "December"
 )

// Matrix to store the data
var data = matrix.new<float>(20,12,0)
var growth = array.new<Growth>()

//~~}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Main {

if barstate.isfirst
    for i=0 to 19
        growth.push(Growth.new(array.new<float>()))

if year!=year[1]
    for [i,save] in growth
        save.gr.push(data.row(i).sum())
        if save.gr.size()>growthyears
            save.gr.shift()
    refreshcount+=1
    if refreshcount>=2
        refreshcount := 0
        data := matrix.new<float>(20,12,0)
else
    for [i,val] in payTime
        if show.get(i)
            amount = customPayout.get(i)==0?
             payOut.get(i)*shares.get(i):
             customPayout.get(i)
            if customMonth.get(i)!=""
                data.CustomMonth(i,amount)
            else if time==val
                date = math.round(str.tonumber(str.format_time(time,"MM")))
                data.set(i,xday=="Ex-Date"?date-1:(date>=12?0:date),amount)
                alert := true
                alert_msg += str.split(symbols.get(i),":").get(1)
                 + " " +
                 xday + 
                 " with a payout of: " + 
                 str.tostring(amount) + 
                 "\n"
if estimate
    Estimate(data)

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Plots & Outputs {

// ~~ Table {
if barstate.islast and timeframe.period=='D'
    //Month Total
    monthTotal = Sum(data,true)
    //Stock Yearly Total
    stockTotal = Sum(data,false)

    //Full Calendar & Monthly
    if tableDesign=="Full Calendar" or tableDesign=="Monthly"
        //Table Display
        //Months
        for [i,m] in months
            tbl.cell(i+1,0,m,text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace,bgcolor=TableBG(i))
        //Month Total
        tbl.cell(0,1,"Total",text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace)
        for [i,mon] in monthTotal
            tbl.cell(i+1,1,str.tostring(mon),text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace,
             bgcolor=color.from_gradient(mon,monthTotal.min(),monthTotal.max(),
             color.new(col_1,90),color.new(col_1,20)))
        if tableDesign=="Monthly"
            tbl.cell(13,1,str.tostring(math.round(monthTotal.sum(),2)),
             bgcolor=bullbackgr,text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace)  

        if tableDesign=="Full Calendar"
            //Stock Total
            tbl.cell(13,1,str.tostring(math.round(monthTotal.sum(),2)),text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace)
            for [i,st] in stockTotal
                if show.get(i)
                    tbl.cell(13,i+2,str.tostring(st),text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace,
                     bgcolor=color.from_gradient(st,stockTotal.min(),stockTotal.max(),
                     color.new(col_2,90),color.new(col_2,20)))
            // Stocks
            for [i,sy] in symbols
                if show.get(i)
                    tbl.cell(0,i+2,str.split(sy,":").get(1),text_color=textcol,text_size=TblSize,
                     text_font_family=font.family_monospace)
                    for j=0 to 11
                        tbl.cell(j+1,i+2,str.tostring(math.round(data.get(i,j),2)),
                         text_color=textcol,text_size=TblSize,text_font_family=font.family_monospace,
                         bgcolor=math.round(data.get(i,j),2)!=0?color.from_gradient(math.round(data.get(i,j),2),
                         data.min(),data.max(),
                         color.new(col_1,90),color.new(col_1,20)):bullbackgr)
            tbl.cell(0,0,"💰")
            tbl.cell(13,0,"💰")

            //Growth
            if showGrowth
                growthArray = Growth(growth)

                for [i,perc] in growthArray
                    if show.get(i)
                        tbl.cell(14,i+2,str.tostring(perc*100,format.percent),text_color=textcol,text_size=TblSize,
                         text_font_family=font.family_monospace,bgcolor=bullbackgr)

//Income Table
    if tableDesign=="Summary"
        monMax = monthTotal.max()
        monMin = monthTotal.min()
        stoMax = stockTotal.max()
        stoMin = stockTotal.min()

        info = array.from(TextSwitch("Best month",months.get(monthTotal.indexof(monMax))),
         TextSwitch("Worst month",months.get(monthTotal.indexof(monMin))),
         TextSwitch("Highest paid stock",str.split(symbols.get(stockTotal.indexof(stoMax)),":").get(1)),
         TextSwitch("Lowest paid stock",str.split(symbols.get(stockTotal.indexof(stoMin)),":").get(1)))

        values = array.from(monMax,monMin,stoMax,stoMin)

        for [i,desc] in info
            tbl.cell(0,i,desc,bgcolor=bullbackgr,text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace)
            tbl.cell(1,i,str.tostring(values.get(i)),bgcolor=bullbackgr,text_color=textcol,text_size=TblSize,
             text_font_family=font.family_monospace)

        summary   = array.from("Yearly","Quarterly","Monthly","Daily")
        income    = stockTotal.sum()
        sumIncome = array.from(income,income/4,income/12,income/365)
        for [i,inc] in sumIncome
            tbl.cell(2,i,summary.get(i),text_color=textcol,text_size=TblSize,text_font_family=font.family_monospace)
            tbl.cell(3,i,str.tostring(math.round(inc,2)),text_color=textcol,text_size=TblSize,text_font_family=font.family_monospace)

//Error table
if timeframe.period!='D'
    tbl.cell(0,0,"Error Message: Please go to the Daily Timeframe",
     bgcolor=color.new(color.red,50),text_color=textcol,text_size=TblSize,
      text_font_family=font.family_monospace)
//~~}

// ~~ Alerts { 
if alert
    alert(alert_msg,alert.freq_once_per_bar)
//~~}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}