Odd_mod Econ Calendar
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Orginal by © jdehorty
// Modified by OddLogicXYZ
// @version=5

indicator('Econ Calendar', overlay=true, scale=scale.none, max_lines_count=500)

//Library with the data
import jdehorty/EconomicCalendar/2 as calendar

//Unix time constants
u_minutes =	60000
u_hours = 3600000
u_days = 86400000
u_week = 604800000

// ==================
// ====  Inputs  ====
// ==================

next_text = "Next meeting countdown"
next_tt = "Add a countdown to the next meeting on the legend."
alert_text = "Create Alert"
alert_tt = "Untested"

fomc_meet_group =  "📅Federal Open Market Committee - Meeting"

fomc_meet_en = input.bool(defval = true, title = "Enable ", inline = "FOMC", group= fomc_meet_group, tooltip="The FOMC meets eight times a year to determine the course of monetary policy. The FOMC's decisions are announced in a press release at 2:15 p.m. ET on the day of the meeting. The press release is followed by a press conference at 2:30 p.m. ET. The FOMC's decisions are based on a review of economic and financial developments and its assessment of the likely effects of these developments on the economic outlook.")
fomc_meet_c = input.color(color.new(color.red, 50), title = "", group= fomc_meet_group, inline = "FOMC")
fomc_meet_next_en = input.bool(defval = false, title = next_text, inline = "FOMC2", group= fomc_meet_group, tooltip=next_tt)
fomc_meet_alert_en = input.bool(defval = false, title = alert_text, inline = "FOMC3", group= fomc_meet_group, tooltip=alert_tt)

fomc_min_group =  "📅Federal Open Market Committee - Minutes"
fomc_min_en = input.bool(defval = true, title = "Enable", inline = "FOMCMinutes", group= fomc_min_group, tooltip="The FOMC minutes are released three weeks after each FOMC meeting. The minutes provide a detailed account of the FOMC's discussion of economic and financial developments and its assessment of the likely effects of these developments on the economic outlook.")
fomc_min_c = input.color(color.new(color.orange, 50), title = "", group= fomc_min_group, inline = "FOMCMinutes")
fomc_min_next_en = input.bool(defval = false, title = next_text, inline = "FOMCMinutes2", group= fomc_min_group, tooltip=next_tt)
fomc_min_alert_en = input.bool(defval = false, title = alert_text, inline = "FOMCMinutes3", group= fomc_min_group, tooltip=alert_tt)

ppi_group =  "📅 Producer Price Index (PPI)"
ppi_en = input.bool(defval = true, title = "Enable", inline = "PPI", group= ppi_group, tooltip="The Producer Price Index (PPI) measures changes in the price level of goods and services sold by domestic producers. The PPI is a weighted average of prices of a basket of goods and services, such as transportation, food, and medical care. The PPI is a leading indicator of CPI.")
ppi_c = input.color(color.new(color.yellow, 50), title = "", group= ppi_group, inline = "PPI")
ppi_next_en = input.bool(defval = false, title = next_text, inline = "PPI2", group= ppi_group, tooltip=next_tt)
ppi_alert_en = input.bool(defval = false, title = alert_text, inline = "PPI3", group= ppi_group, tooltip=alert_tt)

cpi_group =  "📅 Consumer Price Index (CPI)"
cpi_en = input.bool(defval = true, title = "Enable", inline = "CPI", group= cpi_group, tooltip="The Consumer Price Index (CPI) measures changes in the price level of goods and services purchased by households. The CPI is a weighted average of prices of a basket of consumer goods and services, such as transportation, food, and medical care. The CPI-U is the most widely used measure of inflation. The CPI-U is based on a sample of about 87,000 households and measures the change in the cost of a fixed market basket of goods and services purchased by urban consumers.")
cpi_c = input.color(color.new(color.lime, 50), title = "", group= cpi_group, inline = "CPI")
cpi_next_en = input.bool(defval = false, title = next_text, inline = "CPI2", group= cpi_group, tooltip=next_tt)
cpi_alert_en = input.bool(defval = false, title = alert_text, inline = "CPI3", group= cpi_group, tooltip=alert_tt)

csi_group =  "📅 Consumer Sentiment Index (CSI)"
csi_en = input.bool(defval = true, title = "Enable", inline = "CSI", group= csi_group, tooltip="The University of Michigan's Consumer Sentiment Index (CSI) is a measure of consumer attitudes about the economy. The CSI is based on a monthly survey of 500 U.S. households. The index is based on consumers' assessment of present and future economic conditions. The CSI is a leading indicator of consumer spending, which accounts for about two-thirds of U.S. economic activity.")
csi_c = input.color(color.new(color.aqua, 50), title = "", group= csi_group, inline = "CSI")
csi_next_en = input.bool(defval = false, title = next_text, inline = "CSI2", group= csi_group, tooltip=next_tt)
csi_alert_en = input.bool(defval = false, title = alert_text, inline = "CSI3", group= csi_group, tooltip=alert_tt)

cci_group =  "📅 Consumer Confidence Index (CCI)"
cci_en = input.bool(defval = true, title = "Enable", inline = "CCI", group= cci_group, tooltip="The Conference Board's Consumer Confidence Index (CCI) is a measure of consumer attitudes about the economy. The CCI is based on a monthly survey of 5,000 U.S. households. The index is based on consumers' assessment of present and future economic conditions. The CCI is a leading indicator of consumer spending, which accounts for about two-thirds of U.S. economic activity.")
cci_c = input.color(color.new(color.fuchsia, 50), title = "", group= cci_group, inline = "CCI")
cci_next_en = input.bool(defval = false, title = next_text, inline = "CCI2", group= cci_group, tooltip=next_tt)
cci_alert_en = input.bool(defval = false, title = alert_text, inline = "CCI3", group= cci_group, tooltip=alert_tt)

nfp_group =  "📅 Non-Farm Payroll (NFP)"
nfp_en = input.bool(defval = true, title = "Enable", inline = "NFP", group= nfp_group, tooltip="The Non-Farm Payroll (NFP) is a measure of the change in the number of employed persons, excluding farm workers and government employees. The NFP is a leading indicator of consumer spending, which accounts for about two-thirds of U.S. economic activity.")
nfp_c = input.color(color.new(color.silver, 50), title = "", group= nfp_group, inline = "NFP")
nfp_next_en = input.bool(defval = false, title = next_text, inline = "NFP2", group= nfp_group, tooltip=next_tt)
nfp_alert_en = input.bool(defval = false, title = alert_text, inline = "NFP3", group= nfp_group, tooltip=alert_tt)

legend_group = "📊 Legend"

show_legend = input.bool(true, "Show Legend", group= legend_group, inline = "Legend", tooltip="Show the color legend for the economic calendar events.")
legend_loc = input.string("top_right", "Location", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group= legend_group, inline="Legend2")
add_top_margins = input.bool(true, "Top extra margins", group= legend_group, inline = "Legend3", tooltip="Adds extra spacers to the table when set to top locations")
short_names = input.bool(true, "Use Short Names", group= legend_group, inline = "Legend4", tooltip="Abbreviated names on the legend")
countdown_format = input.string("DAYS:HOURS:MINS", "Countdown time format", options=["DAYS:HOURS:MINS", "DAYS:HOURS", "DAYS"], group= legend_group, inline="Legend5")

alert_group = "⏰ Alerts"

alert_time = input.int(15, "Set alerts for", group= alert_group, inline="Alert")
alert_time_period = input.string("Mins", "", options=["Mins","Hours","Days"], group= alert_group, inline="Alert")

// =======================
// ====   Charting    ====
// =======================
//column number placeholders
name_col = 0
next_col = 1
alert_col = 2

//row number placeholders
var int fomc_meet_r = na
var int fomc_min_r = na
var int ppi_r = na
var int cpi_r = na
var int csi_r = na
var int cci_r = na
var int nfp_r = na

//sortable arrays
var fomc_meet_ary = calendar.fomcMeetings()
var fomc_min_ary = calendar.fomcMinutes()
var ppi_ary = calendar.ppiReleases()
var cpi_ary = calendar.cpiReleases()
var csi_ary = calendar.csiReleases()
var cci_ary = calendar.cciReleases()
var nfp_ary = calendar.nfpReleases()

//Legend table
var tbl = table.new(position=legend_loc, columns=3, rows=12, frame_width=1, border_width=2, border_color=color.new(color.black, 100))
 
// function to find where to put the line in relation to the date due to the way lines are added in pine
lineTime (input) =>
    int out = na
    if timeframe.isintraday 
        if timeframe.multiplier >= 60
            out := input - (timeframe.multiplier * u_minutes)
        else
            //Spelled out because its crazy complicated
            //get minutes left in input
            m_in_input = input - (int(input / u_days) * u_days)
            //get minutes left in bar time to create 0 point
            point_0 = time - (int(time / u_days) * u_days)
            //take point 0 from minutes left in input
            p_f_m = m_in_input - point_0
            //find out if input time occours in a whole portion of the selected timeframe.multiplier
            is_whole = (p_f_m % timeframe.multiplier == 0)
            //If it is not a whole portion then move the timeframe back by one bar so the line is drawn on the bar with a start time before the input happens
            out := is_whole ? input : input - (timeframe.multiplier * u_minutes)
    else
        out := timeframe.isdaily ? input - timeframe.multiplier*86400000 : timeframe.isweekly ? input - timeframe.multiplier*604800000 : timeframe.ismonthly ? input - timeframe.multiplier*2592000000 : input
    out

// Things that don't change after initial lines are drawn
if barstate.isfirst

    // Row counter
    tbl_r = 0

    //Creates margins for the table when set to top
    if str.contains(legend_loc,"top") and add_top_margins
        table.cell(tbl, 0, 1, '')
        table.cell(tbl, 0, 2, '')
        tbl_r := 2
        //Extra for left side top
        if str.contains(legend_loc,"left")
            table.cell(tbl, 0, 3, '')
            table.cell(tbl, 0, 4, '')
            tbl_r := 4
    
    //Per selection area
    if fomc_meet_en
        fomc_meet_r := tbl_r
        array.sort(fomc_meet_ary)
        for value in calendar.fomcMeetings()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=fomc_meet_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, fomc_meet_r, (short_names ? 'FEDMeet' : 'FOMC Meeting'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(fomc_meet_c, 10), text_size=size.small)
            tbl_r += 1
    
    if fomc_min_en
        fomc_min_r := tbl_r
        array.sort(fomc_min_ary)
        for value in calendar.fomcMinutes()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=fomc_min_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, fomc_min_r, (short_names ? 'FEDMins' : 'FOMC Minutes'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(fomc_min_c, 10), text_size=size.small)
            tbl_r += 1
    
    if ppi_en
        ppi_r := tbl_r
        array.sort(ppi_ary)
        for value in calendar.ppiReleases()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=ppi_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, ppi_r, (short_names ? 'PPI' : 'Producer Price Index (PPI)'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(ppi_c, 10), text_size=size.small)
            tbl_r += 1

    if cpi_en
        cpi_r := tbl_r
        array.sort(cpi_ary)
        for value in calendar.cpiReleases()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=cpi_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, cpi_r, (short_names ? 'CPI' : 'Consumer Price Index (CPI)'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(cpi_c, 10), text_size=size.small)
            tbl_r += 1

    if csi_en
        csi_r := tbl_r
        array.sort(csi_ary)
        for value in calendar.csiReleases()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=csi_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, csi_r, (short_names ? 'CSI' : 'Consumer Sentiment Index (CSI)'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(csi_c, 10), text_size=size.small)
            tbl_r += 1

    if cci_en
        cci_r := tbl_r
        array.sort(cci_ary)
        for value in calendar.cciReleases()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=cci_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, cci_r, (short_names ? 'CCI' : 'Consumer Confidence Index (CCI)'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(cci_c, 10), text_size=size.small)
            tbl_r += 1

    if nfp_en
        nfp_r := tbl_r
        array.sort(nfp_ary)
        for value in calendar.nfpReleases()
            line_time = lineTime(value)
            line.new(x1=line_time, y1=high, x2=line_time, y2=low, extend=extend.both,color=nfp_c, width=2, xloc=xloc.bar_time)
        if show_legend
            table.cell(tbl, name_col, nfp_r, (short_names ? 'NFP' : 'Non-Farm Payrolls (NFP)'), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(nfp_c, 10), text_size=size.small)
            tbl_r += 1

formatCountdown (format, input) =>
    time_left = input - timenow
    days = int(time_left / u_days)
    time_left := time_left - (days * u_days)
    hours = int(time_left / u_hours)
    time_left := time_left - (hours * u_hours)
    mins = int(time_left / u_minutes)
    switch format
        "DAYS:HOURS:MINS" => "T-" + str.tostring(days) + ":" + (hours <= 9 ? '0' + str.tostring(hours) : str.tostring(hours)) + ":" + (mins <= 9 ? '0' + str.tostring(mins) : str.tostring(mins))
        "DAYS:HOURS" => "T-" + str.tostring(days) + ":" + (hours <= 9 ? '0' + str.tostring(hours) : str.tostring(hours))
        "DAYS" => "T-" + str.tostring(days)

//Things that only need to run on the last bar
if barstate.islast
    //Next meeting section 
    if fomc_meet_en and fomc_meet_next_en and show_legend
        int fomc_meet_next = na
        array.sort(fomc_meet_ary)
        for value in fomc_meet_ary
            if value > timenow
                fomc_meet_next := value
                break
        table.cell(tbl, next_col, fomc_meet_r, (na(fomc_meet_next) ? "No Data" : formatCountdown (countdown_format, fomc_meet_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(fomc_meet_c, 10), text_size=size.small)

    if fomc_min_en and fomc_min_next_en and show_legend
        int fomc_min_next = na
        array.sort(fomc_min_ary)
        for value in fomc_min_ary
            if value > timenow
                fomc_min_next := value
                break
        table.cell(tbl, next_col, fomc_min_r, (na(fomc_min_next) ? "No Data" : formatCountdown(countdown_format, fomc_min_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(fomc_min_c, 10), text_size=size.small)

    if ppi_en and ppi_next_en and show_legend
        int ppi_next = na
        array.sort(ppi_ary)
        for value in ppi_ary
            if value > timenow
                ppi_next := value
                break
        table.cell(tbl, next_col, ppi_r, (na(ppi_next) ? "No Data" : formatCountdown(countdown_format, ppi_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(ppi_c, 10), text_size=size.small)

    if cpi_en and cpi_next_en and show_legend
        int cpi_next = na
        array.sort(cpi_ary)
        for value in cpi_ary
            if value > timenow
                cpi_next := value
                break
        table.cell(tbl, next_col, cpi_r, (na(cpi_next) ? "No Data" : formatCountdown(countdown_format, cpi_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(cpi_c, 10), text_size=size.small)
    
    if csi_en and csi_next_en and show_legend
        int csi_next = na
        array.sort(csi_ary)
        for value in csi_ary
            if value > timenow
                csi_next := value
                break
        table.cell(tbl, next_col, csi_r, (na(csi_next) ? "No Data" : formatCountdown(countdown_format, csi_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(csi_c, 10), text_size=size.small)

    if cci_en and cci_next_en and show_legend
        int cci_next = na
        array.sort(cci_ary)
        for value in cci_ary
            if value > timenow
                cci_next := value
                break
        table.cell(tbl, next_col, cci_r, (na(cci_next) ? "No Data" : formatCountdown(countdown_format, cci_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(cci_c, 10), text_size=size.small)

    if nfp_en and nfp_next_en and show_legend
        int nfp_next = na
        array.sort(nfp_ary)
        for value in nfp_ary
            if value > timenow
                nfp_next := value
                break
        table.cell(tbl, next_col, nfp_r, (na(nfp_next) ? "No Data" : formatCountdown(countdown_format, nfp_next)), text_halign=text.align_center, bgcolor=color.black, text_color=color.new(nfp_c, 10), text_size=size.small)

    //Table icon for alerts
    if fomc_meet_en and fomc_meet_alert_en and show_legend
        table.cell(tbl, alert_col, fomc_meet_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)
    if fomc_min_en and fomc_min_alert_en and show_legend
        table.cell(tbl, alert_col, fomc_min_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)    
    if ppi_en and ppi_alert_en and show_legend
        table.cell(tbl, alert_col, ppi_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)
    if cpi_en and cpi_alert_en and show_legend
        table.cell(tbl, alert_col, cpi_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)
    if csi_en and csi_alert_en and show_legend
        table.cell(tbl, alert_col, csi_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)    
    if cci_en and cci_alert_en and show_legend
        table.cell(tbl, alert_col, cci_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)
    if nfp_en and nfp_alert_en and show_legend
        table.cell(tbl, alert_col, nfp_r, "⏰", text_halign=text.align_center, bgcolor=color.new(color.black, 100), text_size=size.small)

//Alerts, untested
if barstate.isrealtime
    chk_time = switch alert_time_period
        "Mins" => timenow - (alert_time * u_minutes)
        "Hours" => timenow - (alert_time * u_hours)
        "Days" => timenow - (alert_time * u_days)

    if fomc_meet_en and fomc_meet_alert_en and array.includes(fomc_meet_ary, chk_time)
        alert('FOMC Meeting in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if fomc_min_en and fomc_min_alert_en and array.includes(fomc_min_ary, chk_time)
        alert('FOMC Minutes in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if ppi_en and ppi_alert_en and array.includes(ppi_ary, chk_time)
        alert('Producer Price Index in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if cpi_en and cpi_alert_en and array.includes(cpi_ary, chk_time)
        alert('Consumer Price Index in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if csi_en and csi_alert_en and array.includes(csi_ary, chk_time)
        alert('Consumer Sentiment Index in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if cci_en and cci_alert_en and array.includes(cci_ary, chk_time)
        alert('Consumer Confidence Index in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
    if nfp_en and nfp_alert_en and array.includes(nfp_ary, chk_time)
        alert('Non-Farm Payrolls in ' + str.tostring(alert_time) + ' ' + alert_time_period, alert.freq_once_per_bar)
        
        