Financials - Comparing Companies
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LonesomeTheBlue

type financials 
    float MarketCap
    float Close
    float EPS
    float PriceEarningsRatio
    float PriceSalesRatio
    float PriceBookRatio
    float ProfitMargin
    float DebttoEquity
    float CurrentRatio
    float SMA50
    float SMA200
    float SMA50_1
    float SMA200_1
    float Volume
    float AvgVolume
    float Lastday
    string Currency

Tooltips = array.from(
 "Closing Price",
 "The market value of a company’s equity. It is calculated by multiplying a company’s shares outstanding by its price per share",
 "Earnings Per Share (EP) is company's net profit divided by the number of common shares it has outstanding. if EPS is higher than 0 then the company",
 "the Price-to-Earnings ratio (P/E) is a metric measuring the price of a stock relative to its earnings per share (EPS). A high P/E ratio could mean that a stock's price is high relative to earnings and possibly overvalued",
 "Price-to-Sales (P/S) ratio is a valuation ratio that compares a company’s stock price to its revenues. A low ratio may indicate the stock is undervalued, while a ratio that is significantly above the average may suggest overvaluation",
 "Price-to-Book (P/B) ratio, by dividing a company's stock price by its book value per share. A lower P/B ratio could indicate that a stock is undervalued",
 "Profit Margin is a ratio of a company's profit (sales minus all expenses) divided by its revenue. The profit margin ratio compares profit to sales and tells you how well the company is handling its finances overall",
 "Debt-to-Equity ratio measures the company’s total debt relative to the amount originally invested by the owners and the earnings that have been retained over time. Although it varies from industry to industry, a debt-to-equity ratio of around 2 or 2.5 is generally considered good",
 "Current Ratio is a liquidity ratio that measures the capability of a business to meet its short-term obligations that are due within a year. A ratio under 1.00 indicates that the company’s debts due in a year or less are greater than its assets",
 "Simple Moving Average with the length 50",
 "Simple Moving Average with the length 200",
 "Current Volume",
 "Average Volume")

//@version=5
indicator("Financials", overlay = true)
symbol1 = input.symbol(defval = 'NASDAQ:MSFT')
symbol2 = input.symbol(defval = 'NASDAQ:GOOGL')
symbol3 = input.symbol(defval = 'NASDAQ:META')
symbol4 = input.symbol(defval = 'NASDAQ:NFLX')
symbol5 = input.symbol(defval = 'NASDAQ:ADP')
tableposy = input.string(defval='bottom', title='Chart Location', options=['bottom', 'middle', 'top'], inline='chartpos')
tableposx = input.string(defval='left', title='', options=['left', 'center', 'right'], inline='chartpos')
txtsize = input.string(defval = size.normal, title = "Text Size", options = [size.tiny, size.small, size.normal, size.large])
avgvollen = input.int(defval = 20, title = 'Period for Average Volume', minval = 3, maxval = 100)
movt1 = input.string(defval = "SMA", title = "Moving Average Type 1", options = ["SMA", "EMA"], inline ='MA1')
movlen1 = input.int(defval = 50, title = '', minval = 2, inline ='MA1')
movt2 = input.string(defval = "SMA", title = "Moving Average Type 2", options = ["SMA", "EMA"], inline ='MA2')
movlen2 = input.int(defval = 200, title = '', minval = 2, inline ='MA2')

if timeframe.in_seconds(timeframe.period) < 86400
    runtime.error("Please set the time frame 1Day or higher")

var MA1 = movt1 + str.tostring(movlen1)
var MA2 = movt2 + str.tostring(movlen2)

get_sma(source, length)=>
    sum = 0.0
    for i = 0 to length - 1
        sum := sum + source[i] / length
    sum

get_ema(source, length)=>
    alpha = 2 / (length + 1)
    sum = 0.0
    sum := na(sum[1]) ? source : alpha * source + (1 - alpha) * nz(sum[1])

get_ma(source, length, type)=>
    switch type
        "SMA"   => get_sma(source, length)
        "EMA"   => get_ema(source, length)

get_tech_indis()=>
    [close, close[1], get_ma(close, movlen1, movt1), get_ma(close, movlen2, movt2), volume, get_sma(volume, avgvollen), " " + syminfo.currency]

get_financials(financials [] AllFinancials, symbol)=>
    // get closing price and SMAs
    [Price, Lastday, ma1, ma2, Volume, AvgVolume, Currency] = request.security(symbol, timeframe.period, get_tech_indis())

    // Price to Earnings Ratio
    float EPS = request.financial(symbol, "EARNINGS_PER_SHARE", "TTM")
    float PriceEarningsRatio = Price / EPS

    // Price/Earnings-to-Growth (PEG) Ratio
    // PEG = request.financial(symbol, "PEG_RATIO", "FQ")

    // Price Sales Ratio
    float TSO = request.financial(symbol, "TOTAL_SHARES_OUTSTANDING", "FQ")
    float TR = request.financial(symbol, "TOTAL_REVENUE", "TTM")
    float MarketCap = TSO * Price
    float PriceSalesRatio = MarketCap / TR

    // Price to Book ratio
    float BVPS = request.financial(symbol, "BOOK_VALUE_PER_SHARE", "FQ")
    float PriceBookRatio = Price / BVPS

    // Net Profit Margin (TTM)
    float NetIncome = request.financial(symbol, "NET_INCOME", "TTM")
    //TR := request.financial(symbol, "TOTAL_REVENUE", "FQ")
    float ProfitMargin = 100 * NetIncome / TR

    // 	DEBT TO EQUITY
    float DebttoEquity = request.financial(symbol, "DEBT_TO_EQUITY", "FQ")

    // CURRENT RATIO
    float CurrentRatio = request.financial(symbol, "CURRENT_RATIO", "FQ")

    array.push(AllFinancials, financials.new(MarketCap, Price, EPS, PriceEarningsRatio, PriceSalesRatio, PriceBookRatio, ProfitMargin, DebttoEquity, CurrentRatio, ma1, ma2, ma1[1], ma2[1], Volume, AvgVolume, Lastday, Currency))

get_element(financials element, string object)=>
    switch object
        "EPS"                   => element.EPS
        "PriceEarningsRatio"    => element.PriceEarningsRatio
        "PriceSalesRatio"       => element.PriceSalesRatio
        "PriceBookRatio"        => element.PriceBookRatio
        "ProfitMargin"          => element.ProfitMargin
        "DebttoEquity"          => element.DebttoEquity
        "CurrentRatio"          => element.CurrentRatio
        "Volume"                => element.Volume
        "AvgVolume"             => element.AvgVolume
        "Lastday"               => element.Lastday
        MA1                     => element.SMA50
        MA2                     => element.SMA200
        
getTheValues(financials [] AllFinancials, float [] order, string object, int objnum, int [] sorting)=>
    tmp = array.new_float(0)
    for x = 0 to 4
        array.push(tmp, get_element(array.get(AllFinancials, x), object) * array.get(sorting, objnum))

    for x = array.size(tmp) - 1 to 0
        index = array.indexof(tmp, array.max(tmp))
        array.set(order, index, array.get(order, index) + x)
        array.set(tmp, index, -10e14)

get_ordered(financials [] AllFinancials, string [] objects, int [] sorting)=> 
    order = array.new_float(5, 0)
    for x = 0 to array.size(objects) - 1
        getTheValues(AllFinancials, order, array.get(objects, x), x, sorting)
    order

getthecolor(financials element, string object)=>
    color ret = color.green
    if object == "EPS"
        ret := element.EPS > 0 ?color.lime : color.red
    else if object == "PriceSalesRatio"
        ret := element.PriceSalesRatio < 1 ? color.lime : color.red
    else if object == "PriceBookRatio"
        ret := element.PriceBookRatio < 0 ? color.rgb(113, 115, 119) : element.PriceBookRatio < 1 ? color.lime : color.red
    else if object == "debttoEquity"
        ret := element.DebttoEquity < 1 ? color.lime : element.DebttoEquity <= 2.5 ? color.green : color.red
    else if object == "CurrentRatio"
        ret := element.CurrentRatio >= 1 ? color.lime : color.red
    else if object == "Lastday"
        ret := element.Lastday < element.Close ? color.green : color.red
    else if str.contains(object, "Volume")
        ret := color.rgb(180, 185, 230)
    else
        if object == MA1 
            ret := element.SMA50 > element.SMA50_1 ? color.green : element.SMA50 < element.SMA50_1 ? color.red : color.gray
        else if object == MA2 
            ret := element.SMA200 > element.SMA200_1 ? color.green : element.SMA200 < element.SMA200_1 ? color.red : color.gray
        else
            ret := color.rgb(183, 189, 211)
    ret

// Thanks to Tradingview for the following function
// details: https://www.tradingview.com/script/Lbxl9OqZ-Financials-on-Chart/
formatValue(series float value) =>   
    float digits = math.log10(math.abs(value))
    string result = switch 
        digits > 12 => str.tostring(value / 1e12, '#.#' + "  T")
        digits > 9  => str.tostring(value / 1e9,  '#.#' + "  B")
        digits > 6  => str.tostring(value / 1e6,  '#.#' + "  M")
        digits > 3  => str.tostring(value / 1e3,  '#.#' + "  K")
        =>             str.tostring(value,  "#" + '#')

AllFinancials = array.new<financials>(0)
get_financials(AllFinancials, symbol1)
get_financials(AllFinancials, symbol2)
get_financials(AllFinancials, symbol3)
get_financials(AllFinancials, symbol4)
get_financials(AllFinancials, symbol5)
if barstate.islast
    var financetable = table.new(position = tableposy + '_' + tableposx, columns = 20, rows= 10, frame_width = 1, frame_color = color.gray, border_width = 1, border_color = color.gray)
    
    var stocks = array.from(symbol1, symbol2, symbol3, symbol4, symbol5)
    var objects = array.from("EPS", "PriceEarningsRatio", "PriceSalesRatio", "PriceBookRatio", "ProfitMargin", "DebttoEquity", "CurrentRatio")
    var headers = array.from("EPS", "Price/Earning\nRatio", "Price/Sales\nRatio", "Price/Book\nRatio", "Profit\nMargin", "Debt\nto Equity", "Current\nRatio", movt1 + str.tostring(movlen1), movt2 + str.tostring(movlen2), "Volume", "AvgVolume")
    var sorting = array.from(1, -1, -1, -1, 1, -1, 1)
    
    order = get_ordered(AllFinancials, objects, sorting)

    table.cell(financetable, 0, 0, text = "#", text_color = color.white, bgcolor = color.rgb(84, 57, 204), text_size = txtsize)
    table.cell(financetable, 1, 0, text = "Stocks", text_color = color.white, bgcolor = color.rgb(84, 57, 204), text_size = txtsize)
    table.cell(financetable, 2, 0, text = "Price/Change", text_color = color.white, bgcolor = color.navy, tooltip = array.get(Tooltips, 0), text_size = txtsize)
    table.cell(financetable, 3, 0, text = "Market\nCap", text_color = color.white, bgcolor = color.navy, tooltip = array.get(Tooltips, 1), text_size = txtsize)
    for x = 0 to array.size(headers) - 1
        table.cell(financetable, 4 + x, 0, text = array.get(headers, x), text_color = color.white, bgcolor = color.navy, tooltip = array.get(Tooltips, x + 2), text_size = txtsize)
    
    minimum = array.min(order)
    for x = 0 to 4
        index = array.indexof(order, array.max(order))
        element = array.get(AllFinancials, index)
        array.set(order, index, minimum - 1)

        table.cell(financetable, 0, x + 1, text = str.tostring(x + 1), text_color = color.black, bgcolor = color.rgb(0, 255 - x * 15, 255 - x * 15), text_size = txtsize)
        table.cell(financetable, 1, x + 1, text = array.get(stocks, index), text_color = color.black, bgcolor = color.rgb(0, 255 - x * 15, 255 - x * 15), text_size = txtsize)
        table.cell(financetable, 2, x + 1, text = str.tostring(element.Close) + element.Currency + " (" + str.format("{0, number, #.##}", 100 * (element.Close - element.Lastday) / element.Lastday) + "%)", 
                   text_color = color.white, 
                   bgcolor = getthecolor(element, "Lastday"), text_size = txtsize)
        table.cell(financetable, 3, x + 1, text = formatValue(element.MarketCap) + element.Currency, text_color = color.black, bgcolor = color.rgb(226, 225, 208), text_size = txtsize)

        for y = 0 to array.size(headers) - 1
            arrayp = y < array.size(objects) ? objects : headers
            value = get_element(element, array.get(arrayp, y))
            bgcol = getthecolor(element, array.get(arrayp, y))
            table.cell(financetable, y + 4, x + 1, text = str.contains(array.get(arrayp, y), "Volume") ? formatValue(value) : str.tostring(value, '#.##'), text_color = color.black, bgcolor = bgcol, text_size = txtsize)

    table.merge_cells(financetable, 0, 6, 3, 6)
    table.merge_cells(financetable, 11, 6, 14, 6)
    table.cell(financetable, 0, 6, text = '', text_color = color.black, bgcolor = color.gray, text_size = txtsize)
    table.cell(financetable, 11, 6, text = '', text_color = color.black, bgcolor = color.gray, text_size = txtsize)

    table.cell(financetable, 4, 6, text = 'Higher is better' + '\n' + '>0 : Profitable', text_color = color.black, bgcolor = color.rgb(201, 205, 218), text_halign =text.align_left, text_size = txtsize)
    table.cell(financetable, 5, 6, text = 'Lower is better', text_color = color.black, bgcolor = color.rgb(179, 183, 196), text_halign =text.align_left, text_size = txtsize)
    table.cell(financetable, 6, 6, text = '<1 : Undervalued' + '\n' + '>1 : Overvalued', text_color = color.black, bgcolor = color.rgb(201, 205, 218), text_halign =text.align_left, text_size = txtsize)  
    table.cell(financetable, 7, 6, text = '<1 : Undervalued' + '\n' + '>1 : Overvalued' + '\n' + '<0 : No meaning', text_color = color.black, bgcolor = color.rgb(179, 183, 196), text_halign =text.align_left, text_size = txtsize)
    table.cell(financetable, 8, 6, text = 'Higher is better', text_color = color.black, bgcolor = color.rgb(201, 205, 218), text_halign =text.align_left, text_size = txtsize)
    table.cell(financetable, 9, 6, text = 'Lower is better' + '\n' + '<1 : Good' + '\n' + '<2.5 : Okey' + '\n' + '>2.5 : Not good', text_color = color.black, bgcolor = color.rgb(179, 183, 196), text_halign =text.align_left, text_size = txtsize)
    table.cell(financetable,10, 6, text = 'Higher is better' + '\n' + '<1 : Not good', text_color = color.black, bgcolor = color.rgb(201, 205, 218), text_halign =text.align_left, text_size = txtsize)
