COT Report Indicator
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingNerd

//@version=5
indicator('COT Report Indicator', format=format.volume, precision=0)

// Variables {
i_tableAlign = input.string(position.middle_right, 'Align Table', [position.middle_right, position.middle_left])
table t = table.new(i_tableAlign, 7, 6, color.rgb(19, 23, 34), color.white, 2, color.white, 2)
color textCl = color.white
color clCom     = color.gray
color clNoncom  = color.blue
color clSpec    = color.orange
// }

// INPUTS {
i_dataSource = input.string('Futures Only', 'Data', ['Futures Only', 'Futures and Options'])
i_showCom = input.bool(true, 'Show Commercials')
// }

// Functions {
f_getCl(value) =>
    if value > 0
        color.lime
    else if value < 0
        color.red
    else
        color.white 
// }

// Request {
sHead = 'QUANDL:CFTC/'
sData = i_dataSource == 'Futures Only' ? '_F_L_ALL|' : '_FO_L_ALL|'

// Financial Markets (Commercial)
comColumnLong = '4'
comColumnShort = '5'

// Non-Commercials (large Speculators)
noncomColumnLong = '1'
noncomColumnShort = '2'

// Non-Reportable (small Speculators)
specColumnLong = '8'
specColumnShort = '9'

// CFTC Market Codes
// Codes from https://data.nasdaq.com/data/CFTC-commodity-futures-trading-commission-reports

f_getCode(pair) =>
    string code = switch pair
        'USD' => '098662'       // USD Index
        'EUR' => '099741'
        'AUD' => '232741'
        'GBP' => '096742'
        'CAD' => '090741'
        'JPY' => '097741'
        'CHF' => '092741'
        'NZD' => '112741'
        'BTC' => '133741'
        'GOLD' => '088691'
        'SILVER' => '084691'
        'PLATINUM' => '076651'
        'PALLADIUM' => '075651'
        'ALUMINUM' => '191651'
        'COPPER' => '085692'
        'CRUDE OIL' => 'T'
        'SOYBEAN OIL' => '007601'
        'S&P 500' => '13874P'
        'S&P 500 Mini' => '13874A'
        'Dow Jones' => '12460P'
        'NIKKEI' => '240741'
        'NASDAQ' => '20974P'
        'RUSSELL 2000' => '239777'
        'Volatility S&P 500' => '1170E1'
        => ''
    code

f_getTicker() =>
    switch syminfo.ticker
        'GOLD' => 'GOLD'
        'GOLD1!' => 'GOLD'
        'XAU' => 'GOLD'
        'XAUUSD' => 'GOLD'
        'SILVER' => 'SILVER'
        'SILVER1!' => 'SILVER'
        'PLATINUM' => 'PLATINUM'
        'PALLADIUM' => 'PALLADIUM'
        'COPPER' => 'COPPER'
        'ALUMINUM' => 'ALUMINUM'
        'OIL' => 'CRUDE OIL'
        'CL1!' => 'CRUDE OIL'
        'SOYUSD' => 'SOYBEAN OIL'
        'ZL1!' => 'SOYBEAN OIL'
        'SPX' => 'S&P 500'
        'ES1!' => 'S&P 500'
        'XSP' => 'S&P 500 Mini'
        'NKD' => 'NIKKEI'
        'NKD1!' => 'NIKKEI'
        'NK225' => 'NIKKEI'
        'NK2251!' => 'NIKKEI'
        'DJI' => 'Dow Jones'
        'US30' => 'Dow Jones'
        'NASDAQ' => 'NASDAQ'
        'US100' => 'NASDAQ'
        'NDX' => 'NASDAQ'
        'RUT' => 'RUSSELL 2000'
        'RTY' => 'RUSSELL 2000'
        'VIX' => 'Volatility S&P 500'
        => ''

f_getCurrency(curr) =>
    if curr == ''
        f_getTicker()
    else
        curr

f_getCurrencyAB(base, quote) => 
    retBase = f_getCurrency(base)
    retQuote = f_getCurrency(quote)

    // find a correlating currency for that ticker
    if retBase == retQuote or retQuote == ''
        retQuote := switch retBase
            'S&P 500' => 'USD'
            'S&P 500 Mini' => 'USD'
            => quote

    [retBase, retQuote]

// Data functions
// Commercials
dLong(asCode) =>
    request.security(sHead + asCode + sData + comColumnLong, 'W', close, lookahead=barmerge.lookahead_on)
dShort(asCode) =>
    request.security(sHead + asCode + sData + comColumnShort, 'W', close, lookahead=barmerge.lookahead_on)

// large - Non Commercials
dLong2(asCode2) =>
    request.security(sHead + asCode2 + sData + noncomColumnLong, 'W', close, lookahead=barmerge.lookahead_on)
dShort2(asCode2) =>
    request.security(sHead + asCode2 + sData + noncomColumnShort, 'W', close, lookahead=barmerge.lookahead_on)

// small - Non-Reportable
dLong3(asCode3) =>
    request.security(sHead + asCode3 + sData + specColumnLong, 'W', close, lookahead=barmerge.lookahead_on)
dShort3(asCode3) =>
    request.security(sHead + asCode3 + sData + specColumnShort, 'W', close, lookahead=barmerge.lookahead_on)

f_getData(code) =>
    comLong = dLong(code)
    comShort = dShort(code)
    noncomLong = dLong2(code)
    noncomShort = dShort2(code)
    specLong = dLong3(code)
    specShort = dShort3(code)
    
    [comLong, comShort, noncomLong, noncomShort, specLong, specShort]

f_calcNet(comLong, comShort, noncomLong, noncomShort, specLong, specShort) =>
    netCom = comLong - comShort
    netNoncom = noncomLong - noncomShort
    netSpec = specLong - specShort
    
    [netCom, netNoncom, netSpec]
// }


// Calculations {
[currencyA, currencyB] = f_getCurrencyAB(syminfo.basecurrency, syminfo.currency)

codeA = f_getCode(currencyA)
codeB = f_getCode(currencyB)

[pairA_coms_long, pairA_coms_short, pairA_noncoms_long, pairA_noncoms_short, pairA_spec_long, pairA_spec_short] = f_getData(codeA)
[pairB_coms_long, pairB_coms_short, pairB_noncoms_long, pairB_noncoms_short, pairB_spec_long, pairB_spec_short] = f_getData(codeB)


// Calc net
[net_comA, net_noncomA, net_specA] = f_calcNet(pairA_coms_long, pairA_coms_short, pairA_noncoms_long, pairA_noncoms_short, pairA_spec_long, pairA_spec_short)
[net_comB, net_noncomB, net_specB] = f_calcNet(pairB_coms_long, pairB_coms_short, pairB_noncoms_long, pairB_noncoms_short, pairB_spec_long, pairB_spec_short)

net_comApercentLong = pairA_coms_long / (pairA_coms_long + pairA_coms_short) * 100
net_comBpercentLong = pairB_coms_long / (pairB_coms_long + pairB_coms_short) * 100

net_noncomApercentLong = pairA_noncoms_long / (pairA_noncoms_long + pairA_noncoms_short) * 100
net_noncomBpercentLong = pairB_noncoms_long / (pairB_noncoms_long + pairB_noncoms_short) * 100

net_specApercentLong = pairA_spec_long / (pairA_spec_long + pairA_spec_short) * 100
net_specBpercentLong = pairB_spec_long / (pairB_spec_long + pairB_spec_short) * 100

// --------------


// // percent calculation
symbol_com_sum = pairA_coms_long + pairA_coms_short + pairB_coms_long + pairB_coms_short
symbol_com_long = (pairA_coms_long + pairB_coms_short) / symbol_com_sum * 100
symbol_com_short = (pairA_coms_short + pairB_coms_long) / symbol_com_sum * 100

symbol_noncom_sum = pairA_noncoms_long + pairA_noncoms_short + pairB_noncoms_long + pairB_noncoms_short
symbol_noncom_long = (pairA_noncoms_long + pairB_noncoms_short) / symbol_noncom_sum * 100
symbol_noncom_short = (pairA_noncoms_short + pairB_noncoms_long) / symbol_noncom_sum * 100


symbol_spec_sum = pairA_spec_long + pairA_spec_short + pairB_spec_long + pairB_spec_short
symbol_spec_long = (pairA_spec_long + pairB_spec_short) / symbol_spec_sum * 100
symbol_spec_short = (pairA_spec_short + pairB_spec_long) / symbol_spec_sum * 100
// }


// Plots {
plot_com = i_showCom ? symbol_com_long : na

plotStyle = if (timeframe.isweekly or timeframe.ismonthly) 
    plot.style_line 
else
    plot.style_stepline

plot(plot_com, "Commercials", clCom, 3, plotStyle, histbase=0)
plot(symbol_noncom_long, "Non-Commercials", clNoncom, 3, plotStyle, histbase=0)
plot(symbol_spec_long, "Retail Traders", clSpec, 3, plotStyle, histbase=0)

// Labels
if i_showCom
    label comLabel = label.new(bar_index+1, symbol_com_long, 'Commercials', xloc.bar_index, yloc.price, clCom, label.style_label_left)
    label.delete(comLabel[1])

label noncomLabel = label.new(bar_index+1, symbol_noncom_long, 'Non-Commercials', xloc.bar_index, yloc.price, clNoncom, label.style_label_left)
label specLabel = label.new(bar_index+1, symbol_spec_long, 'Retail Traders', xloc.bar_index, yloc.price, clSpec, label.style_label_left)

label.delete(noncomLabel[1])
label.delete(specLabel[1])

// Table
f_textCell(col, row, txt) =>
    table.cell(t, col, row, txt, text_color=textCl)

f_fillCell(col, row, value) =>
    valueStr = str.tostring(value, format.volume)

    cl = f_getCl(value)
    table.cell(t, col, row, valueStr, text_color=cl)
    
f_fillPercent(col, row, value, isLong) =>
    valueStr = str.tostring(value, format.volume) + '%'

    cl = if isLong and value > 50
        color.lime
    else if not isLong and value > 50
        color.red
    else
        color.white
    table.cell(t, col, row, valueStr, text_color=cl)

// Merge cells
table.merge_cells(t, 1, 0, 3, 0)
table.merge_cells(t, 4, 0, 6, 0)

f_textCell(1, 0, currencyA)
f_textCell(4, 0, currencyB)

f_textCell(1, 1, 'Net Contracts')
f_textCell(2, 1, 'Long')
f_textCell(3, 1, 'Short')

f_textCell(4, 1, 'Net Contracts')
f_textCell(5, 1, 'Long')
f_textCell(6, 1, 'Short')

if i_showCom
    table.cell(t, 0, 2, 'Commercials', text_color=textCl)
    f_fillCell(1, 2, net_comA)
    f_fillCell(4, 2, net_comB)
    f_fillPercent(2, 2, net_comApercentLong, true)
    f_fillPercent(3, 2, 100-net_comApercentLong, false)
    f_fillPercent(5, 2, net_comBpercentLong, true)
    f_fillPercent(6, 2, 100-net_comBpercentLong, false)

table.cell(t, 0, 3, 'Institutional', text_color=textCl)
f_fillCell(1, 3, net_noncomA)
f_fillCell(4, 3, net_noncomB)

f_fillPercent(2, 3, net_noncomApercentLong, true)
f_fillPercent(3, 3, 100-net_noncomApercentLong, false)
f_fillPercent(5, 3, net_noncomBpercentLong, true)
f_fillPercent(6, 3, 100-net_noncomBpercentLong, false)

table.cell(t, 0, 4, 'Retail Traders', text_color=textCl)
f_fillCell(1, 4, net_specA)
f_fillCell(4, 4, net_specB)

f_fillPercent(2, 4, net_specApercentLong, true)
f_fillPercent(3, 4, 100-net_specApercentLong, false)
f_fillPercent(5, 4, net_specBpercentLong, true)
f_fillPercent(6, 4, 100-net_specBpercentLong, false)


hplot0 = hline(0)
hplot50 = hline(50)
hplot100 = hline(100)

color clShort = color.new(color.red, 90)
color clLong = color.new(color.green, 90)

fill(hplot0, hplot50, clShort)
fill(hplot50, hplot100, clLong)

// }

