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


// ###### Revision Note ################################################################ //
// 1. Released 
// 2. Add more items
//--------------------------------------------------------------------------------------//
// 3. 2022-Dec-17 : 
//  - add 2 financial item. 
//  - add,  eps diluted.
//  - re-arrange code. 
//--------------------------------------------------------------------------------------//
// 4. 2023-Jan-22
//  - Add more items. 
//  - re-arranged code. 

//@version=5
indicator("Financial Growth",overlay=false, format = format.volume)

// TEXT SIZE +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
text_size_AUTO    =       size.auto     ,   text_size_TINY    =       size.tiny 
text_size_SMALL   =       size.small    ,   text_size_NORMAL  =       size.normal 
text_size_LARGE   =       size.large    ,   text_size_HUGE    =       size.huge

// TABLE POSITION ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tb_pos_POS01 =    position.top_left         ,   tb_pos_POS02 =    position.top_center     
tb_pos_POS03 =    position.top_right        ,   tb_pos_POS04 =    position.middle_left    
tb_pos_POS05 =    position.middle_center    ,   tb_pos_POS06 =    position.middle_right   
tb_pos_POS07 =    position.bottom_left      ,   tb_pos_POS08 =    position.bottom_center  
tb_pos_POS09 =    position.bottom_right   

// Financial Items +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// Income Statement
_revenue            =   "Revenue"                   ,_revenue_id         =   'TOTAL_REVENUE'    
_net_income         =   "Net Income"                ,_net_income_id      =   'NET_INCOME'            
_ebitda             =   "EBITDA"                    ,_ebitda_id          =   "EBITDA"
_eps                =   "EPS"                       ,_eps_id             =   "EARNINGS_PER_SHARE_BASIC"
_eps_diluted        =   "EPS, Diluted"              ,_eps_diluted_id     =   'EARNINGS_PER_SHARE_DILUTED'
_dvps               =   "Dividend per share"        ,_dvps_id            =   'DPS_COMMON_STOCK_PRIM_ISSUE'        

// Balance Sheet
_total_asset        = "Total Assets",       _total_asset_id = "TOTAL_ASSETS"
_total_liability    = "Total Liabilities",  _total_liability_id = "TOTAL_LIABILITIES"

_total_debt         =   "Total Debt"                ,_total_debt_id      =   'TOTAL_DEBT' // Interest Bearing Debt
_short_term_debt    =   "ST Debt",      _short_term_debt_id = "SHORT_TERM_DEBT"
_long_term_debt     =   "LT Debt",      _long_term_debt_id  = "LONG_TERM_DEBT"
_net_debt           = "Net Debt",       _net_debt_id = "NET_DEBT"
_interest_expense_on_debt = "Interest Expense",     _interest_expense_on_debt_id = "INTEREST_EXPENSE_ON_DEBT"
_total_euity        = "Total Equity",   _total_equity_id = "TOTAL_EQUITY"

// Cash Flow 
_free_cash_flow     =   "Free Cash Flow"            ,_free_cash_flow_id  =   'FREE_CASH_FLOW'          

// Statistics and Ratios 
_de                 = "D/E",                _de_id = "DEBT_TO_EQUITY"
_interest_coverage  = "Coverage Ratio",     _interest_coverage_id = "INTERST_COVER"
_gross_margin       =   "Gross Margin"              ,_gross_margin_id    =   'GROSS_MARGIN'       
_operating_margin   =   "Operating Margin"          ,_operating_margin_id =     "OPERATING_MARGIN"     
_ebitda_margin      =   "EBITDA Margin"             ,_ebitda_margin_id   =   'EBITDA_MARGIN'        
_net_margin         =   "Net Margin"                ,_net_margin_id      =   'NET_MARGIN'              
_roe                =   "ROE"                       ,_roe_id             =   'RETURN_ON_EQUITY'                     
_roa                =   "ROA"                       ,_roa_id             =   'RETURN_ON_ASSETS' 

// Calculated Items
           
_pbv                =   "P/BV"                      //,_pbv_id             =   'BOOK_VALUE_PER_SHARE'                    
_pe                 =   "P/E"                       //,_pe_id              =   "P/E" // Calculated                     
_ps                 =   "P/S"                       //,_ps_id              =   "P/S"        // Price to Sales Total outstanding share (FQ),  * close / Total Revenue (TTM)           
_pfcf               =   "P/FCF"                     //,_pfcf_id            =   "P/FCF"     // Price to Free Cash Flow,  Total outstanding share (FQ)  * close / Free Cash Flow (FQ)

price_to_earning        = close / request.financial(syminfo.tickerid, "EARNINGS_PER_SHARE_DILUTED",'TTM', ignore_invalid_symbol = true) 
price_to_book_value     = close / request.financial(syminfo.tickerid, "BOOK_VALUE_PER_SHARE", 'FQ', ignore_invalid_symbol = true)
price_to_sales          = (
     (request.financial(syminfo.tickerid, "TOTAL_SHARES_OUTSTANDING",   "FQ",   ignore_invalid_symbol = true   ) * close) /
     request.financial(syminfo.tickerid, "TOTAL_REVENUE",              "TTM",  ignore_invalid_symbol = true   )
     )
price_to_free_cash_flow = (
     (request.financial(syminfo.tickerid, "TOTAL_SHARES_OUTSTANDING",   "FQ", ignore_invalid_symbol = true ) * close) / 
     request.financial(syminfo.tickerid, "FREE_CASH_FLOW",             "FQ", ignore_invalid_symbol = true )
     )
published_date() =>
    // Get published date of financial report
    _ticker = 'ESD_FACTSET:'+syminfo.prefix+';'+syminfo.ticker+';EARNINGS'
    published_date = request.security(_ticker, 'D',time,gaps=barmerge.gaps_on, ignore_invalid_symbol=true, lookahead=barmerge.lookahead_on)

// 3. INPUT ============================================================================= 
group4                  ='Input'
group_item_01           = "Items"
group_item_01_line_01   = "line 01"
group_item_01_line_02   = "line 02"
group_item_01_line_03   = "line 03"
group_item_02           = "Group 02"

financial_item_selection_01 =  input.string(    _revenue,  "Item 1 / 2",      options=[
     "===== Income ======", 
         _revenue,_net_income, _ebitda, _eps, _eps_diluted, _dvps,          
     "== Balance Sheet ==",
         _total_asset, 
         _total_liability, 
             _total_debt,
             _net_debt, 
             _long_term_debt, 
             _short_term_debt,
             _interest_expense_on_debt ,
         _total_euity,
     "==== Cash Flow ====",
         _free_cash_flow, 

     "===== Margin =====",
         _gross_margin, _ebitda_margin, _operating_margin, _net_margin, 
     "===== Ratio =====",
         _roe, _roa, _pbv, _pe, _ps, _pfcf, _de, _interest_coverage], 
     inline = group_item_01_line_01,      group = group_item_01)

financial_item_selection_02 =  input.string(    _net_income,      "",      options=[
     "===== Income ======", 
         _revenue, 
         _net_income, _ebitda, _eps, _eps_diluted, _dvps,          
     "== Balance Sheet ==",
         _total_asset, 
         _total_liability, 
             _total_debt,
             _net_debt, 
             _long_term_debt, 
             _short_term_debt,
             _interest_expense_on_debt ,
         _total_euity,
     "==== Cash Flow ====",
         _free_cash_flow, 

     "===== Margin =====",
         _gross_margin, _ebitda_margin, _operating_margin, _net_margin, 
     "===== Ratio =====",
         _roe, _roa, _pbv, _pe, _ps, _pfcf, _de, _interest_coverage     ],
     inline = group_item_01_line_01,      group = group_item_01     )

period_selection            =   input.string('FQ', 'Period / Number of Period', options=['FQ', 'FY', 'TTM'], inline = group_item_01_line_02, group=group_item_01)
data_size_selection         =   input.int(4, '', minval = 4,                        inline = group_item_01_line_02, group=group_item_01) +5
// Future Plan 
    // report_date =   input.string(defval = "Period Ended", title = "Report Date", options = ["Period Ended", "Published Date"], inline = group_item_01_line_03, group = group_item_01)

group_table_01      = 'Table' 
show_table          = input.bool(true,      'Show Table',   inline='01', group=group_table_01)
is_qoq              = input.bool(true,      "Show QoQ",     inline='02', group=group_table_01)
is_yoy              = input.bool(true,      "Show YoY",     inline='02', group=group_table_01)
is_cagr             = input.bool(false,     "Show CAGR",    inline='02', group=group_table_01)

table_text_color =   input.color(color.black, 'Text/BG/Head/Body',  inline = group_table_01, group = group_table_01)
table_bg_color   =   input.color(color.new(#979797, 0),  '',        inline = group_table_01, group = group_table_01)
table_body_color =   input.color(color.new(#fffffa, 0),  '',        inline = group_table_01, group = group_table_01)
table_header_color=  input.color(color.new(#f0af8a, 0),  '',        inline = group_table_01, group = group_table_01)


table_text_size          =   input.string(text_size_NORMAL,    'Text Size/Postion',        options=[text_size_AUTO, text_size_TINY, text_size_SMALL, text_size_NORMAL, text_size_LARGE, text_size_HUGE], inline='02', group=group_table_01)
table_position_selection =   input.string(tb_pos_POS09,     '',        options=[tb_pos_POS01,tb_pos_POS02, tb_pos_POS03, tb_pos_POS04, tb_pos_POS05, tb_pos_POS06, tb_pos_POS07, tb_pos_POS08, tb_pos_POS09], inline='02', group=group_table_01)

group_plot_01 = "Plotting"
group_plot_line_01 = "plotting line"
show_plot   = input.bool(false, "Show plot", inline =  group_plot_01,  group = group_plot_01)
show_plot_label = input.bool(false, "Show plot labels", inline =  group_plot_01,  group = group_plot_01)

label_text_color = input.color(color.new(color.black, 0), 'Text',   inline=group_plot_line_01, group=group_plot_01)
label_bg_color   = input.color(color.new(color.aqua, 25), 'Bg Color', inline=group_plot_line_01, group=group_plot_01)

// 2. FUNCTION ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// Function for Financial  

is_calculated_data(_financial_item)=>
    bool logic = (_financial_item == _pbv or _financial_item == _pe or _financial_item == _pfcf or _financial_item == _ps)

get_financial_ratio(_financial_ratio)=>    
    switch _financial_ratio
        _pe     => price_to_earning 
        _pbv    => price_to_book_value
        _ps     => price_to_sales
        _pfcf   => price_to_free_cash_flow
        => na

get_financial_id(_financial_item)=>
    // Get financial id from financial item
    switch _financial_item
    // income statement
        _revenue          => _revenue_id       
        _net_income       => _net_income_id    
        _ebitda           => _ebitda_id        
        _eps              => _eps_id           
        _eps_diluted      => _eps_diluted_id   
        _dvps             => _dvps_id          
    // balance sheet
        _total_asset       =>_total_asset_id    
        _total_liability       =>_total_liability_id 
        // Debt
        _total_debt         =>_total_debt_id
        _long_term_debt     => _long_term_debt_id
        _short_term_debt    => _short_term_debt_id
        _net_debt           => _net_debt_id
        _interest_expense_on_debt => _interest_expense_on_debt_id

        _total_euity       =>_total_equity_id    
    
    // cash flow
        _free_cash_flow   =>_free_cash_flow_id 

    // statistic and ratio
        _gross_margin     =>_gross_margin_id   
        _ebitda_margin    =>_ebitda_margin_id  
        _operating_margin => _operating_margin_id
        _net_margin       =>_net_margin_id     
        _roe              =>_roe_id            
        _roa              =>_roa_id     
        _de                 => _de_id
        _interest_coverage  => _interest_coverage_id     
          
        // _pbv              =>_pbv_id            
        // _pe               =>_pe_id             
        // _ps               =>_ps_id             
        // _pfcf             =>_pfcf_id   

retreive_financial_data(_financial_item, _period)=> 
    // Retreive financial data from finanancial id.
        // _id Financial id
        // _period     period, FQ, FY, TTM
    _id = get_financial_id(_financial_item = _financial_item)
    financial_data = request.financial(syminfo.tickerid, _id, _period, barmerge.gaps_on, ignore_invalid_symbol = true)

create_array(arrayId, val)    => 
    // Function to get financial data from TradingView.
    array.unshift(arrayId, val) // append value to an array
    array.pop(arrayId)
        //    array.push(arrayId, val) 
        //    array.shift(arrayId)


get_value_formating(_financial_item)=>
    // return format, divider and suffix of value. 
    value_divider = 0       ,    value_decimal = "",        value_suffix  = ""
    is_true = (
     _financial_item == _revenue or _financial_item ==_net_income or 
     _financial_item == _ebitda or 
     
     _financial_item == _free_cash_flow or 
     _financial_item == _total_asset or 
     _financial_item == _total_liability or 
     // Debt
     _financial_item == _total_debt or
     _financial_item == _long_term_debt or 
     _financial_item == _short_term_debt or 
     _financial_item == _interest_expense_on_debt or 
     _financial_item == _net_debt or 
     
     _financial_item == _total_euity      ) 

    value_divider       := is_true ? 1000000 : 1
    value_suffix        := is_true ? " M" : ""
    value_decimal       := is_true ?  "#,##0" : "#,##0.000"

    [value_divider, value_suffix, value_decimal]

get_cagr(_data_array, _id, _idmax)=> 
    // Get compound annual growth rate
    _current_value  = array.get(_data_array, _id)
    _start_value    = array.get(_data_array, _idmax)
    cagr            = (math.pow((_current_value/math.abs(_start_value)), 1/_idmax)-1)*100
 
// Table ---------------------------------------------------------------------------- 
header_column(  _table, _col, _row, _value) => 
    table.cell(table_id = _table , column= _col , row= _row ,text = _value, text_color = table_text_color, text_size = table_text_size ,bgcolor = table_header_color)
date_column(    _table, _col, _row, _data_array, _id) => 
    _date_value = str.format('{0, date, yyyy-MMM-dd}', array.get(_data_array, _id))
    table.cell( table_id = _table,  column  = _col,row = _row, text = _date_value, text_color  = table_text_color, text_size = table_text_size, bgcolor = table_body_color )

value_column(   _table, _col, _row, _data_array, _id, _financial_item)=>
    [value_divider, value_suffix, value_decimal] = get_value_formating(_financial_item)
    val     = array.get(_data_array, _id)/ value_divider
    table.cell(table_id=   _table,   column = _col, row =   _row,text =   str.tostring(val, value_decimal) + value_suffix, text_color = table_text_color, text_size = table_text_size, bgcolor = table_body_color)

qoq_column(     _table, _col, _row, _data_array, _id, _period)=> 
    _compare = (_period == "FQ" or _period == "TTM") ? 1 : na
    _current_value      = array.get(_data_array, _id),     _previous_value     = array.get(_data_array, _id+_compare)
    _different_value    = ((_current_value-_previous_value)/ math.abs(_previous_value))*100
    _symbol             = _different_value >= 0 ?  ' 😍' : ' 😡'
    _text = str.tostring(_different_value, '#,##0.0') + _symbol
    table.cell(table_id = _table, column = _col, row = _row,text = _text,text_halign = text.align_right,text_color = table_text_color,text_size = table_text_size, bgcolor = color.new(_different_value >= 0 ? color.green : color.red, 75))

yoy_column(     _table, _col, _row, _data_array, _id, _period)=> 
    _compare = (_period == "FQ" or _period == "TTM") ? 4 : 1
    _current_value      = array.get(_data_array, _id),     _previous_value     = array.get(_data_array, _id+_compare)
    _different_value    = ((_current_value-_previous_value)/ math.abs(_previous_value))*100
    _symbol = _different_value >= 0 ?  ' 😍' : ' 😡'
    _text = str.tostring(_different_value, '#,##0.0') + _symbol
    table.cell( table_id = _table, column = _col, row = _row, text = _text, text_halign = text.align_right,text_color = table_text_color , text_size = table_text_size, bgcolor = color.new(_different_value >= 0 ? color.green : color.red, 75))

cagr_column(    _table, _col, _row, _data_array, _id, _idmax)=>
    _value = get_cagr(_data_array, _id, _idmax)
    _symbol = _value >= 0 ?  ' 😍' : ' 😡'
    _text = str.tostring(_value , '#,##0.0')+_symbol
    _bgcolor = color.new(_value >= 0 ? color.green : color.red, 75)
    table.cell( table_id = _table, column = _col, row = _row, text = _text, text_halign = text.align_right, text_color = table_text_color, text_size = table_text_size, bgcolor = _bgcolor)

//
// 4. Calculation ======================================================================= 

reference_revenue = request.financial(symbol = syminfo.tickerid, financial_id = 'TOTAL_REVENUE', period= period_selection,     gaps= barmerge.gaps_on,      ignore_invalid_symbol  =true     ) 


temp_financial_data_01   = retreive_financial_data(_financial_item = financial_item_selection_01, _period = period_selection)
temp_financial_data_02   = retreive_financial_data(_financial_item = financial_item_selection_02, _period = period_selection)

financial_data_01 = is_calculated_data(financial_item_selection_01) ? get_financial_ratio(financial_item_selection_01) : temp_financial_data_01
financial_data_02 = is_calculated_data(financial_item_selection_02) ? get_financial_ratio(financial_item_selection_02) : temp_financial_data_02

var date_array                  = array.new_int(    data_size_selection)  // Date
var financial_data_array        = array.new_float(  data_size_selection)  // EPS
var financial_data_array_02     = array.new_float(  data_size_selection)  // EPS

if reference_revenue
    create_array(date_array,                time)
    create_array(financial_data_array,      financial_data_01)
    create_array(financial_data_array_02,   financial_data_02)

// 5. TABLE ================================================================================
var table t = table.new(
     position = table_position_selection, columns = 10, rows = data_size_selection,    
     bgcolor = table_bg_color,      frame_color    = color.rgb(156, 156, 156),      
     frame_width    = 2,            border_color   = color.rgb(229, 226, 229),      border_width   = 1 )

// Show & Hide Column 
bool qoq_column     = (period_selection == "FQ" or period_selection == "TTM" ) and  is_qoq
bool yoy_column     = (period_selection == "FQ" or period_selection == "TTM" or period_selection == "FY") and is_yoy
bool cagr_column    = (period_selection == "FY" or period_selection == "TTM") and is_cagr

if barstate.islast and show_table
    period_header = period_selection == "FY" ? "Year" : "Quarter"
    header_column(_table = t, _col = 0, _row = 0, _value = period_header               )
    header_column(_table = t, _col = 1, _row = 0, _value = financial_item_selection_01 )
    header_column(_table = t, _col = 5, _row = 0, _value = financial_item_selection_02 )

    if qoq_column
        header_column(_table = t, _col = 2,_row = 0,  _value = "QoQ")
        header_column(_table = t, _col = 6,_row = 0,  _value = "QoQ")

    if yoy_column
        header_column(_table = t, _col = 3,_row = 0,_value = 'YoY')
        header_column(_table = t, _col = 7,_row = 0,_value = 'YoY')
    if cagr_column
        header_column(_table = t, _col = 4,_row = 0,_value = 'CAGR') 
        header_column(_table = t, _col = 8,_row = 0,_value = 'CAGR') 
   
for _irow = 0 to data_size_selection - 5
    if barstate.islast and show_table

        date_column( _table = t,_col = 0 ,_row = _irow + 1, _data_array    = date_array,             _id     = _irow)
        value_column(_table = t,_col = 1 ,_row = _irow + 1, _data_array    = financial_data_array,   _id     = _irow, _financial_item = financial_item_selection_01)
        value_column(_table = t,_col = 5 ,_row = _irow + 1, _data_array    = financial_data_array_02,_id     = _irow, _financial_item = financial_item_selection_02)
        
        if qoq_column
            qoq_column(_table = t,_col = 2,_row = _irow+1,_data_array = financial_data_array,   _id = _irow, _period = period_selection)
            qoq_column(_table = t,_col = 6,_row = _irow+1,_data_array = financial_data_array_02,_id = _irow, _period = period_selection)
        
        if yoy_column
            yoy_column(_table = t, _col = 3, _row = _irow+1,_data_array = financial_data_array,_id = _irow, _period = period_selection)
            yoy_column(_table = t,_col = 7,_row = _irow+1,_data_array = financial_data_array_02,_id = _irow,_period = period_selection)
        
        if cagr_column
            cagr_column(_table = t,_col = 4,_row = _irow+1,_data_array = financial_data_array,_id = _irow,_idmax = data_size_selection-5    )
            cagr_column(_table = t,_col = 8,_row = _irow+1,_data_array = financial_data_array_02,_id = _irow,_idmax = data_size_selection-5 )

// Ploting ========================================================================================================//

plot_data_01 = show_plot ? financial_data_01 : na
plot_data_02 = show_plot ? financial_data_02 : na

plot(plot_data_01, "Plot 01", style = plot.style_circles, linewidth = 2, join = true, color = color.rgb(255, 2, 158))
plot(plot_data_02, "Plot 02", style = plot.style_circles, linewidth = 2, join = true, color = color.rgb(25, 0, 255))

// Labels
if show_plot and show_plot_label and reference_revenue
    [val_divide, val_suffix, val_decimal] = get_value_formating(financial_item_selection_01)
    label_01 = label.new(
         x = bar_index, y = plot_data_01, 
         text = str.tostring(plot_data_01/val_divide, val_decimal + val_suffix)
         , color = label_bg_color
         , textcolor = label_text_color)

    [val_divide2, val_suffix2, val_decimal2] = get_value_formating(financial_item_selection_02)
    label_02 = label.new(
         x = bar_index,y = plot_data_02, 
         text = str.tostring(plot_data_02/val_divide2, val_decimal2+val_suffix2)
         , color = label_bg_color
         , textcolor = label_text_color)