Economic Calendar (Import from Spreadsheet)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © DojiEmoji


//@version=5
indicator("Economic Calendar [DojiEmoji]", overlay=true, max_lines_count=500, max_labels_count=500)

// Obtain source data:
//   ie. Step 1: By obtaining from: https://www.fxstreet.com/economic-calendar
//       Step 2: Using external tools (ie Excel, Python, etc.) to create a line of text that resembles 
//               calendar events.
// 

// This script uses the following standardized format:
//
//     Format = <timezone>;<date1>;<date2>; ... etc, up to the last event on <date_n>
//               Where: 'date' is expressed as: YYYY,MM,DD,hh,mm,ss
//

// -----------------------
// Settings:
// -----------------------
// {
var bool show_table = input.bool(true, title="Show table displaying legend for colors")
var string str_table_position = input.string(position.bottom_right, options=[position.bottom_right, position.top_right, position.bottom_left], title="Location of table")
var color bgcolor_table = input.color(color.new(color.black,90), title="Background", inline="ln1")
var string font_size = input.string(size.small, inline="ln1", title="Font size", options=[size.tiny, size.small, size.normal, size.large, size.huge])

var string GROUP_CAT1    = "--------------- Category 1 ---------------"
var string name_cat1     = input.string("Fed", title="", inline="ln1", group=GROUP_CAT1)
var bool show_cat_1      = input.bool(true, title="Show category:", inline="ln1", group=GROUP_CAT1)
var color linecolor_cat1 = input.color(color.new(color.red,50), title="", inline="ln2", group=GROUP_CAT1)
var string str_input_1   = input.text_area(title="Source Data, Category 1:", group=GROUP_CAT1, 
 tooltip="Pasted From external tools (ie. Spreadsheet). A template has been provided in the blog post associated with this script).",
 defval="GMT;2023,1,4,19,0,0;2023,1,10,14,0,0;2023,2,1,19,0,0;2023,2,1,19,0,0;2023,2,1,19,30,0;2023,2,7,17,40,0;2023,2,22,19,0,0;2023,3,7,15,0,0;2023,3,8,15,0,0;2023,3,22,18,0,0;2023,3,22,18,0,0;2023,3,22,18,0,0;2023,3,22,18,30,0;2023,4,12,18,0,0;2023,5,3,18,0,0;2023,5,3,18,0,0;2023,5,3,18,30,0;2023,5,19,15,0,0;2023,5,24,18,0,0;2023,6,14,18,0,0;2023,6,14,18,0,0;2023,6,14,18,0,0;2023,6,14,18,30,0;2023,6,21,14,0,0;2023,6,22,14,0,0;2023,6,28,13,30,0;2023,6,29,6,30,0;2023,7,5,18,0,0;2023,7,26,18,0,0;2023,7,26,18,0,0;2023,7,26,18,30,0;2023,8,16,18,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,20,18,30,0;2023,10,11,18,0,0;")
var int linewidth_cat1   = input.int(2, title="Width", minval=1, maxval=5, inline="ln2", group=GROUP_CAT1)

var string GROUP_CAT2    = "--------------- Category 2 ---------------"
var string name_cat2     = input.string("PMI", title="", inline="ln1", group=GROUP_CAT2)
var bool show_cat_2      = input.bool(true, title="Show category:", inline="ln1", group=GROUP_CAT2)
var color linecolor_cat2 = input.color(color.new(color.blue,50), title="", inline="ln2", group=GROUP_CAT2)
var string str_input_2   = input.text_area(title="Source Data, Category 2:", group=GROUP_CAT2, 
 tooltip="Pasted From external tools (ie. Spreadsheet). A template has been provided in the blog post associated with this script).", 
 defval="GMT;2023,1,4,15,0,0;2023,1,6,15,0,0;2023,2,1,15,0,0;2023,2,3,15,0,0;2023,3,1,15,0,0;2023,3,3,15,0,0;2023,4,3,14,0,0;2023,4,5,14,0,0;2023,5,1,14,0,0;2023,5,3,14,0,0;2023,6,1,14,0,0;2023,6,5,14,0,0;2023,7,3,14,0,0;2023,7,6,14,0,0;2023,7,24,13,45,0;2023,7,24,13,45,0;2023,8,1,14,0,0;2023,8,3,14,0,0;2023,8,23,13,45,0;2023,8,23,13,45,0;2023,9,1,14,0,0;2023,9,4,14,0,0;2023,9,22,13,45,0;2023,9,22,13,45,0;2023,10,2,14,0,0;2023,10,4,14,0,0;2023,10,24,13,45,0;2023,10,24,13,45,0;")
var int linewidth_cat2   = input.int(2, title="Width", minval=1, maxval=5, inline="ln2", group=GROUP_CAT2)

var string GROUP_CAT3    = "--------------- Category 3 ---------------"
var string name_cat3     = input.string("Retail Sales", title="", inline="ln1", group=GROUP_CAT3)
var bool show_cat_3      = input.bool(true, title="Show category:", inline="ln1", group=GROUP_CAT3)
var color linecolor_cat3 = input.color(color.new(color.orange,50), title="", inline="ln2", group=GROUP_CAT3)
var string str_input_3   = input.text_area(title="Source Data, Category 3:", group=GROUP_CAT3, 
 tooltip="Pasted From external tools (ie. Spreadsheet). A template has been provided in the blog post associated with this script).", 
 defval="GMT;2023,1,18,13,30,0;2023,1,18,13,30,0;2023,2,15,13,30,0;2023,2,15,13,30,0;2023,3,15,12,30,0;2023,3,15,12,30,0;2023,4,14,12,30,0;2023,4,14,12,30,0;2023,5,16,12,30,0;2023,5,16,12,30,0;2023,6,15,12,30,0;2023,6,15,12,30,0;2023,7,18,12,30,0;2023,7,18,12,30,0;2023,8,15,12,30,0;2023,8,15,12,30,0;2023,9,14,12,30,0;2023,9,14,12,30,0;2023,10,17,12,30,0;2023,10,17,12,30,0;")
var int linewidth_cat3   = input.int(2, title="Width", minval=1, maxval=5, inline="ln2", group=GROUP_CAT3)

var string GROUP_CAT4    = "--------------- Category 4 ---------------"
var string name_cat4     = input.string("CPI", title="", inline="ln1", group=GROUP_CAT4)
var bool show_cat_4      = input.bool(true, title="Show category:", inline="ln1", group=GROUP_CAT4)
var color linecolor_cat4 = input.color(color.new(color.green,50), title="", inline="ln2", group=GROUP_CAT4)
var string str_input_4   = input.text_area(title="Source Data, Category 4:", group=GROUP_CAT4, 
 tooltip="Pasted From external tools (ie. Spreadsheet). A template has been provided in the blog post associated with this script).", 
 defval="GMT;2023,1,12,13,30,0;2023,1,12,13,30,0;2023,2,14,13,30,0;2023,2,14,13,30,0;2023,3,14,12,30,0;2023,3,14,12,30,0;2023,4,12,12,30,0;2023,4,12,12,30,0;2023,5,10,12,30,0;2023,5,10,12,30,0;2023,6,13,12,30,0;2023,6,13,12,30,0;2023,7,12,12,30,0;2023,7,12,12,30,0;2023,7,12,12,30,0;2023,7,12,12,30,0;2023,8,10,12,30,0;2023,8,10,12,30,0;2023,8,10,12,30,0;2023,8,10,12,30,0;2023,9,13,12,30,0;2023,9,13,12,30,0;2023,9,13,12,30,0;2023,9,13,12,30,0;2023,10,12,12,30,0;2023,10,12,12,30,0;2023,10,12,12,30,0;2023,10,12,12,30,0;")
var int linewidth_cat4   = input.int(2, title="Width", minval=1, maxval=5, inline="ln2", group=GROUP_CAT4)

var string GROUP_CAT5    = "--------------- Category 5 ---------------"
var string name_cat5     = input.string("Others", title="", inline="ln1", group=GROUP_CAT5)
var bool show_cat_5      = input.bool(true, title="Show category:", inline="ln1", group=GROUP_CAT5)
var color linecolor_cat5 = input.color(color.new(color.teal,50), title="", inline="ln2", group=GROUP_CAT5)
var string str_input_5   = input.text_area(title="Source Data, Category 5:", group=GROUP_CAT5, 
 tooltip="Pasted From external tools (ie. Spreadsheet). A template has been provided in the blog post associated with this script).", 
 defval="GMT;2023,1,5,13,15,0;2023,1,6,13,30,0;2023,1,12,13,30,0;2023,1,13,15,0,0;2023,1,26,13,30,0;2023,1,26,13,30,0;2023,1,26,13,30,0;2023,2,1,13,15,0;2023,2,3,13,30,0;2023,2,8,2,0,0;2023,2,10,15,0,0;2023,2,23,13,30,0;2023,2,27,13,30,0;2023,2,27,13,30,0;2023,3,8,13,15,0;2023,3,10,13,30,0;2023,3,13,13,0,0;2023,3,17,14,0,0;2023,3,22,18,0,0;2023,3,22,18,0,0;2023,3,22,18,0,0;2023,3,22,18,0,0;2023,3,24,12,30,0;2023,3,24,12,30,0;2023,3,30,12,30,0;2023,4,5,12,15,0;2023,4,7,12,30,0;2023,4,14,14,0,0;2023,4,26,12,30,0;2023,4,26,12,30,0;2023,4,27,12,30,0;2023,5,3,12,15,0;2023,5,5,12,30,0;2023,5,12,14,0,0;2023,5,25,12,30,0;2023,5,26,12,30,0;2023,5,26,12,30,0;2023,6,1,12,15,0;2023,6,2,12,30,0;2023,6,14,18,0,0;2023,6,14,18,0,0;2023,6,14,18,0,0;2023,6,14,18,0,0;2023,6,16,14,0,0;2023,6,28,20,30,0;2023,6,29,12,30,0;2023,6,30,12,30,0;2023,6,30,12,30,0;2023,7,6,12,15,0;2023,7,7,12,30,0;2023,7,7,12,30,0;2023,7,7,12,30,0;2023,7,13,12,30,0;2023,7,14,14,0,0;2023,7,27,12,30,0;2023,7,28,12,30,0;2023,7,28,12,30,0;2023,8,2,12,15,0;2023,8,4,12,30,0;2023,8,4,12,30,0;2023,8,4,12,30,0;2023,8,11,12,30,0;2023,8,11,14,0,0;2023,8,24,12,30,0;2023,8,31,12,30,0;2023,8,31,12,30,0;2023,9,1,12,30,0;2023,9,1,12,30,0;2023,9,1,12,30,0;2023,9,6,12,15,0;2023,9,14,12,30,0;2023,9,15,14,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,20,18,0,0;2023,9,28,12,30,0;2023,9,29,12,30,0;2023,9,29,12,30,0;2023,10,4,12,15,0;2023,10,6,12,30,0;2023,10,6,12,30,0;2023,10,6,12,30,0;2023,10,11,12,30,0;2023,10,13,14,0,0;2023,10,26,12,30,0;2023,10,27,12,30,0;2023,10,27,12,30,0;")
var int linewidth_cat5   = input.int(2, title="Width", minval=1, maxval=5, inline="ln2", group=GROUP_CAT5)

// }

// @function : _adjust
//             To adjust for potential timing differences.
//             Original Unix time might cause lines to offset when chart is viewed on other timeframes (ie 12hour).
//
// Thanks to @jdehorty for pointing this out and letting me use his logic for converting.
// https://www.tradingview.com/script/HLYDwa0N-Economic-Calendar-Events-FOMC-CPI-and-more/
_adjust(int t) => 
    switch 
        timeframe.isdaily and timeframe.multiplier > 1 => t - timeframe.multiplier*86400000 // -n days
        timeframe.isweekly => t - timeframe.multiplier*604800000 // -n week(s)
        timeframe.ismonthly => t - timeframe.multiplier*2592000000 // -n month(s)
        timeframe.isminutes and timeframe.multiplier > 60 => t - timeframe.multiplier*60000 // -n minutes
        => t

// @function : get_timestamps 
// @returns array of int : Date of Events in Unix time, sorted.
get_timestamps(string str_input) =>
    
    string[] tokens = str.split(str_input,";")
    string timezone = array.get(tokens, 0)

    int[] return_arr_time = array.new_int()
    for i=1 to array.size(tokens)-1 // start from i=1; First element is reserved for 'timezone'

        string[] subtokens = str.split(array.get(tokens,i), ",")
        if array.size(subtokens) > 0
            // Parse
            int yyyy     = int(str.tonumber(array.get(subtokens, 0)))
            int mmm      = int(str.tonumber(array.get(subtokens, 1)))
            int dd       = int(str.tonumber(array.get(subtokens, 2)))
            int hh       = int(str.tonumber(array.get(subtokens, 3)))
            int mm       = int(str.tonumber(array.get(subtokens, 4)))
            int ss       = int(str.tonumber(array.get(subtokens, 5)))
            // Store
            array.push(return_arr_time, _adjust(timestamp(timezone, yyyy, mmm, dd, hh, mm, ss)))

    array.sort(return_arr_time, order.ascending)
    return_arr_time

var tbl = table.new(str_table_position, 1, 5, frame_color=na, frame_width=0, border_width=0, border_color=na)
put_table(string text_size=size.small, color bgcolor) =>
    table.cell(tbl, 0, 0, name_cat1, text_halign = text.align_left, text_color=linecolor_cat1, bgcolor = bgcolor, text_size = text_size)
    table.cell(tbl, 0, 1, name_cat2, text_halign = text.align_left, text_color=linecolor_cat2, bgcolor = bgcolor, text_size = text_size)
    table.cell(tbl, 0, 2, name_cat3, text_halign = text.align_left, text_color=linecolor_cat3, bgcolor = bgcolor, text_size = text_size)
    table.cell(tbl, 0, 3, name_cat4, text_halign = text.align_left, text_color=linecolor_cat4, bgcolor = bgcolor, text_size = text_size)
    table.cell(tbl, 0, 4, name_cat5, text_halign = text.align_left, text_color=linecolor_cat5, bgcolor = bgcolor, text_size = text_size)

// MAIN:
// {

var bool is_done = false

var string[] name_cat  = array.from(name_cat1,      name_cat2,      name_cat3,      name_cat4,      name_cat5)
var bool[] show_cat    = array.from(show_cat_1,     show_cat_2,     show_cat_3,     show_cat_4,     show_cat_5)
var string[] str_input = array.from(str_input_1,    str_input_2,    str_input_3,    str_input_4,    str_input_5)
var color[] linecolor  = array.from(linecolor_cat1, linecolor_cat2, linecolor_cat3, linecolor_cat4, linecolor_cat5)
var int[] linewidth    = array.from(linewidth_cat1, linewidth_cat2, linewidth_cat3, linewidth_cat4, linewidth_cat5)

if barstate.islast and not is_done

    for i=0 to array.size(name_cat)-1
        
        string _input = array.get(str_input, i)
        _input := str.replace_all(_input, " ", "")
            
        if array.get(show_cat, i) and _input != ""
            if _input == ""
                runtime.error(message = "Source data is empty: " + array.get(name_cat,i ))
            _timestamps = get_timestamps(_input)

            // Assert -> int[] timestamps sorted by chronological order
            
            // Due to PineScript limitations - max. 500 lines on chart
            // We should only show the most recent 
            max_lines = 100     // 100 lines for each category (total = 5 categories)

            _n = array.size(_timestamps)
            _lncol = array.get(linecolor, i) 
            _lnwidth = array.get(linewidth, i)
            for j=math.max(0, _n - max_lines) to _n-1
                t = array.get(_timestamps, j)
                line.new(t, close, t, close*1.0001, xloc.bar_time, extend.both, _lncol, width=_lnwidth)

    if show_table
        put_table(font_size, bgcolor_table)
    is_done := true
// }
