Skip to content

Instantly share code, notes, and snippets.

@danperrout
Last active March 20, 2025 18:00
Show Gist options
  • Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
API Função TESOURODIRETO Google Sheets
/*
* @return Retorna a cotação atual de um título específico do Tesouro Direto.
* Fonte: https://www.tesourodireto.com.br/titulos/precos-e-taxas.htm
**/
function TESOURODIRETO(bondName, argumento="r") {
let srcURL = "https://api.radaropcoes.com/bonds.json";
let jsondata = UrlFetchApp.fetch(srcURL);
let parsedData = JSON.parse(jsondata.getContentText()).response;
for(let bond of parsedData.TrsrBdTradgList) {
let currBondName = bond.TrsrBd.nm;
if (currBondName.toLowerCase() === bondName.toLowerCase())
if(argumento == "r")
return bond.TrsrBd.untrRedVal;
else
return bond.TrsrBd.untrInvstmtVal;
}
throw new Error("Título não encontrado.");
}
@romulocampelo
Copy link

Olá Renato! Não sei se funciona para todos os títulos eu usei os seguintes nomes nas células "A81:A86":
Selic 2027
Selic 2029
Ipca+ 2035
Ipca+ 2029
Ipca+ 2026

Funcionou tudo perfeito. Transcrevo a seguir a fórmula novamente, espero que funcione para você:
=IMPORTXML("https://www.tesouroinfo.com/"; "//table[@id='table_resgatar']//tr[td='" & A81 & "']/td[4]/text()")

@danperrout
Copy link
Author

danperrout commented Aug 26, 2024

Essa fórmula esta funcionando agora, mas acho que eventualmente irão derrubar, pois eles possuem uma API de consulta:

=IMPORTXML("https://www.tesouroinfo.com/"; "//table[@id='table_resgatar']//tr[td='" & PROPER(TRIM(CLEAN(A2))) & "']/td[4]/text()")

@cristianoscaranci
Copy link

@danperrout , tentei agora com o nome do título e retornou N/A. Parou de novo ou errei algo?
=IMPORTXML("https://www.tesouroinfo.com/"; "//table[@id='table_resgatar']//tr[td='" & PROPER(TRIM("Tesouro Prefixado 2029")) & "']/td[4]/text()")
#N/A

@mateusguenter
Copy link

@danperrout , tentei agora com o nome do título e retornou N/A. Parou de novo ou errei algo? =IMPORTXML("https://www.tesouroinfo.com/"; "//table[@id='table_resgatar']//tr[td='" & PROPER(TRIM("Tesouro Prefixado 2029")) & "']/td[4]/text()") #N/A

tem que ser com o nome do titulo que está na página. Nesse caso seria "Prefixado 2029"

@renatofig
Copy link

=IMPORTXML("https://www.tesouroinfo.com/"; "//table[@id='table_resgatar']//tr[td='" & PROPER(TRIM(CLEAN(A2))) & "']/td[4]/text()")

Romulo, muito obrigado, depois de umas 2 horas o IPCA+ 2029 apareceu ok, mas se mudo para Ipca+ 2035 dá o mesmo erro e fica pendurado carregando, muito estranho.

@renatofig
Copy link

após um bom tempo rodou...Obrigado!

@rafaelgp23
Copy link

rafaelgp23 commented Sep 2, 2024

Antes de discutir formulas pra puxar os valores (< isso tá facil), o principal é discutir que essa fonte realmente é confiável e com sincronia real-time. Antes tinhamos uma API oficial do tesourodireto, agora estamos todos nos baseando num site terceiro, não sei se dá pra garantir que é uma boa fonte de informação.

No momento que posto este comentário:

https://www.tesourodireto.com.br/titulos/precos-e-taxas.htm - Última atualização: 02/09/2024 • 09h26min
https://www.tesouroinfo.com/ - Última atualização: | 30/Ago/2024 15:36

Como suspeitava, não dá pra confiar 100% no site de terceiro. Talvez seja mais um workaround. O ideal seria reestabelecer acesso ao API oficial.

@mateusguenter
Copy link

mateusguenter commented Sep 2, 2024

Acessei agora e os dois estão sincronizados.
Tenho acompanhado os dois e estão sempre sincronizados, as vezes demora algumas horas, mas me parece que da pra utilizar.
Eu vou utilizar..

@Nauahbanda
Copy link

o site https://www.tesouroinfo.com/ está fora do ar? ou é só na minha máquina?

@thiago-oc
Copy link

o site https://www.tesouroinfo.com/ está fora do ar? ou é só na minha máquina?

aqui também está fora

@vitroz
Copy link

vitroz commented Sep 2, 2024

Aqui esta fora tambem.
Po galera, aquele csv do tesouro transparente nao é confiavel?

É uma merda usar um arquivo de 11mb cheio de lixo, mas achei que fosse uma opcao viavel.
Vi que eles atualizaram ele hoje, mas abrindo o csv nao achei nenhum registro com data base 02/09/2024

Eu literalmente implementei um codigo ontem que filtra ele e pega os valores atualizados dos titulos, alguem sabe se é comum esse csv nao ser atualizado direito?

@epeixoto85
Copy link

Para atualização dos títulos existentes do Tesouro Direto, e dos preços e taxas de venda e compra, utilizava os seguintes serviços:

"https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json"
"https://www.tesourodireto.com.br/b3/tesourodireto/pricesAndFeesHistory?codigo=&periodo="

Ambos estão retornando erro 403, conforme algumas pessoas relataram anteriormente.

Alguém encontrou alguma alternativa?

@wroliveira1
Copy link

Aqui esta fora tambem. Po galera, aquele csv do tesouro transparente nao é confiavel?

É uma merda usar um arquivo de 11mb cheio de lixo, mas achei que fosse uma opcao viavel. Vi que eles atualizaram ele hoje, mas abrindo o csv nao achei nenhum registro com data base 02/09/2024

Eu literalmente implementei um codigo ontem que filtra ele e pega os valores atualizados dos titulos, alguem sabe se é comum esse csv nao ser atualizado direito?

atualizando uma vez por dia pra mim já está bom.
fuçando achei este, não sei se serve: https://ghostnetrn.github.io/bot-tesouro-direto/

@tuliopascoal
Copy link

Será que não valeria a pena entrar em contato com o criador do https://github.com/ghostnetrn/bot-tesouro-direto e perguntar como que ele ainda tá conseguindo utilizar a API do TesouroDireto (URL_API=https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json)??

@brvfr1
Copy link

brvfr1 commented Sep 6, 2024

Alegria de pobre dura pouco, o link não atualiza…

@alexbenfica
Copy link

Colocaram o Cloudflare na frente ou já tinha mas ativaram para que bots não possam acessar, exigindo aquela verificação de que é humano. :(

o 403 é do Cloudflare, tanto que funciona no navegador mas com scripts falha sempre.

Como eles não fornecem a chave de API para pessoas físicas, talvez a solução é baixar manualmente o arquivo quando se quiser ver a atualização. Alguma outra ideia?

@BDonadelli
Copy link

BDonadelli commented Sep 6, 2024

Alegria de pobre dura pouco, o link não atualiza…

aqui ainda funciona (com o selenium)

@tuliopascoal
Copy link

Coloquei o caminho "https://raw.githubusercontent.com/ghostnetrn/bot-tesouro-direto/714896cbcfc4100e241006cd29fef1bc3fcbcc65/tesouro.json" no script do Google Sheets e funcionou...

Eu coloquei esse link e funcionou no Google Sheets, mas o preço do título veio diferente.

O problema é que esse link é de um JSON estático. Se não me engano dia dia 05/09/2024.
Então ele sempre irá repetir os valores desse dia.

Alguém saberia alguma forma de pegar o JSON para o dia atual?

@danperrout
Copy link
Author

danperrout commented Oct 18, 2024

Pessoal, implementei minha própria abstração do json do Tesouro Direto. Na verdade, é uma cópia exata do JSON fornecidos por eles que pode ser acessado em:
Oficial: https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json
Mirror: https://api.radaropcoes.com/bonds.json

Esse json é atualizado de 15 em 15 minutos, mas o próprio TD só atualiza de hora em hora, por isso as datas podem estar "atrasadas" em algumas horas.

Fiquem a vontade para utilizar, além disso, no site Radar Opções, incluí a possibilidade de COPIAR o nome do Título para a área de transferência:

image

@danperrout
Copy link
Author

danperrout commented Oct 21, 2024

Alguém saberia alguma forma de pegar o JSON para o dia atual?

Código atualizado com o novo endereço de json: https://api.radaropcoes.com/bonds.json

@WadjoResende
Copy link

Alguém sabe explicar como resolver o seguinte erro:

Erro TypeError: Cannot read properties of undefined (reading 'toLowerCase')
TESOURODIRETO @ TESOURODIRETO.gs:12

@ArturJuan
Copy link

O erro é por conta do comentário que declara a função, segue a versão corrigida:

/**
 * Retorna a cotação atual de um título específico do Tesouro Direto.
 * 
 * @param {string} nome do título
 * @return Retorna a cotação atual de um título específico do Tesouro Direto.
 * @customfunction 
 * Fonte: https://www.tesourodireto.com.br/titulos/precos-e-taxas.htm
*/
function TESOURODIRETO(bondName="Tesouro IPCA+ 2029", argumento="r") {
    let srcURL = "https://api.radaropcoes.com/bonds.json";
    let jsondata = UrlFetchApp.fetch(srcURL);
    let parsedData = JSON.parse(jsondata.getContentText()).response;

    for(let bond of parsedData.TrsrBdTradgList) {
        let currBondName = bond.TrsrBd.nm;
        console.log(typeof(currBondName))
        if (currBondName.toLowerCase() === bondName.toLowerCase())
            if(argumento == "r")
                return bond.TrsrBd.untrRedVal;
            else
                return bond.TrsrBd.untrInvstmtVal;
    }
    throw new Error("Título não encontrado.");
}

@WadjoResende
Copy link

Perfeito Artur! Valeu!

@kleitonramires
Copy link

Os valores unitário de cada título está certo para vcs? o meu não mostra no google sheets (c/script) igual a do site do Tesouro.

@danperrout
Copy link
Author

Os valores unitário de cada título está certo para vcs? o meu não mostra no google sheets (c/script) igual a do site do Tesouro.

O script consegue acessar dois valores, se vc colocar:
=TESOURODIRETO("Nome do Titulo") retorna os valores da aba Resgate
=TESOURODIRETO("Nome do Titulo";"i") retorna os valores da aba Investimento

@kleitonramires
Copy link

Os valores unitário de cada título está certo para vcs? o meu não mostra no google sheets (c/script) igual a do site do Tesouro.

O script consegue acessar dois valores, se vc colocar: =TESOURODIRETO("Nome do Titulo") retorna os valores da aba Resgate =TESOURODIRETO("Nome do Titulo";"i") retorna os valores da aba Investimento

Putz, valeu irmão. Aprendi contigo no seu canal a colocar os dados no script e agora mais essa, ajudou muito. Tudo de bom para vc.

@diegob-ciandt
Copy link

pessoal,
aparentemente o endpoint oficial (https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json) voltou a funcionar para as chamadas a partir do google app script.
[]'s

@edermcastro
Copy link

edermcastro commented Mar 20, 2025

Aqui está a função do IPCA para quem quisar utilizar.

function IPCA(strBegin, strEnd){
    let srcURL = "https://servicodados.ibge.gov.br/api/v1/conjunturais?&d=s&user=ibge&t=1737&v=2266&p="+ strBegin + "," + strEnd+"&ng=1(1)&c=";    
    let jsondata = UrlFetchApp.fetch(srcURL);
    let parsedData = JSON.parse(jsondata)
    
    let dDiv = 1;
    let dDiv2 = 1;
    for (let tag of parsedData){
      if (tag["p_cod"] == strBegin) {
        dDiv = tag["v"];        
      }else
        dDiv2 = tag["v"]; 
    }
   return (dDiv2/dDiv) - 1;
}

Pra quem está precisando de uma função no backend, php laravel,

use Illuminate\Support\Facades\Http; ////<<<<<<<< declare no seu Helper

public static function IPCA($inicio='202501',$fim='202502'){
    try {
        $params = [
            'd' => 's',
            'user' => 'ibge',
            't' => '1737',
            'v' => '2266',
            'p' => Functions::somenteNumeros($inicio).','.Functions::somenteNumeros($fim),
            'ng' => '1(1)',
            'c' => ''
        ];

        $response = Http::timeout(120)
            ->withoutVerifying()
            ->withHeaders(['Accept' => 'application/json'])
            ->get('https://servicodados.ibge.gov.br/api/v1/conjunturais', $params);

        $dDiv = 1; $dDiv2 = 1; $data = $response->json();
        
        foreach ($data as $tag) {
            if ($tag['p_cod'] == $inicio) {
                $dDiv = floatval($tag['v']);
            } else {
                $dDiv2 = floatval($tag['v']);
            }
        }
        
        $ret = ($dDiv2/$dDiv) - 1;

        return response()->json(['ipca' => $ret, 'ipca-data'=>$response->json() ?? ($response->json()['message']) ]);
    } catch (\Exception $e){
        return response()->json(['error'=>[$e->getMessage(), $e->getFile(), $e->getLine()]]);
    }
}

public static function somenteNumeros($string){
    return preg_replace("/[^0-9]/", "", $string);
}

@tuliopascoal
Copy link

Aqui está a função do IPCA para quem quisar utilizar.

function IPCA(strBegin, strEnd){
    let srcURL = "https://servicodados.ibge.gov.br/api/v1/conjunturais?&d=s&user=ibge&t=1737&v=2266&p="+ strBegin + "," + strEnd+"&ng=1(1)&c=";    
    let jsondata = UrlFetchApp.fetch(srcURL);
    let parsedData = JSON.parse(jsondata)
    
    let dDiv = 1;
    let dDiv2 = 1;
    for (let tag of parsedData){
      if (tag["p_cod"] == strBegin) {
        dDiv = tag["v"];        
      }else
        dDiv2 = tag["v"]; 
    }
   return (dDiv2/dDiv) - 1;
}

Pra quem está precisando de uma função no backend, php laravel,

use Illuminate\Support\Facades\Http; ////<<<<<<<< declare no seu Helper

public static function IPCA($inicio='202501',$fim='202502'){
    try {
        $params = [
            'd' => 's',
            'user' => 'ibge',
            't' => '1737',
            'v' => '2266',
            'p' => Functions::somenteNumeros($inicio).','.Functions::somenteNumeros($fim),
            'ng' => '1(1)',
            'c' => ''
        ];

        $response = Http::timeout(120)
            ->withoutVerifying()
            ->withHeaders(['Accept' => 'application/json'])
            ->get('https://servicodados.ibge.gov.br/api/v1/conjunturais', $params);

        $dDiv = 1; $dDiv2 = 1; $data = $response->json();
        
        foreach ($data as $tag) {
            if ($tag['p_cod'] == $inicio) {
                $dDiv = floatval($tag['v']);
            } else {
                $dDiv2 = floatval($tag['v']);
            }
        }
        
        $ret = ($dDiv2/$dDiv) - 1;

        return response()->json(['ipca' => $ret, 'ipca-data'=>$response->json() ?? ($response->json()['message']) ]);
    } catch (\Exception $e){
        return response()->json(['error'=>[$e->getMessage(), $e->getFile(), $e->getLine()]]);
    }
}

public static function somenteNumeros($string){
    return preg_replace("/[^0-9]/", "", $string);
}

Legal a idéia.
Qual seria o endpoint para resgatar informações mais básicas do IPCA.
Por exemplo, esses três dessa página do IBGE: https://www.ibge.gov.br/explica/inflacao.php
IPCA do último mês, IPCA acumulado de 12 meses, e INPC do último mês.

Teria como ter uma versão modificada?

Grato,

@edermcastro
Copy link

edermcastro commented Mar 20, 2025

podes usar o Curl pra isso

function obterConteudoElementoJson($url, $elementSelector) {
// Inicializa o cURL
$curl = curl_init();

// Configura as opções do cURL
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // Desativa a verificação SSL (não recomendado em produção)
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // Desativa a verificação do host SSL (não recomendado em produção)

// Executa a requisição cURL
$html = curl_exec($curl);

// Verifica se houve erro
if ($html === false) {
    echo 'Erro cURL: ' . curl_error($curl);
    curl_close($curl);
    return false;
}

// Fecha a conexão cURL
curl_close($curl);

// Cria um objeto DOMDocument para analisar o HTML
$dom = new DOMDocument();
@$dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);  // Supressão de erros de parsing (ex: tags não fechadas)

// Cria um objeto DOMXPath para executar consultas XPath
$xpath = new DOMXPath($dom);

// Executa a consulta XPath para encontrar o elemento desejado
 $elementList = $xpath->query('//*[contains(@id, "' . str_replace('#', '', $elementSelector) . '")]');

if ($elementList->length > 0) {
     $element = $elementList->item(0);

    // Extrai os dados relevantes do elemento
    $data = [];
    $listItems = $xpath->query('./li', $element); // Seleciona todos os <li> dentro do elemento

    foreach ($listItems as $listItem) {
        $titulo = $xpath->query('./h3[@class="variavel-titulo"]/text()', $listItem)->item(0)->textContent;
        $dado = $xpath->query('./p[@class="variavel-dado"]/text()', $listItem)->item(0)->textContent;
        $periodo = $xpath->query('./p[@class="variavel-periodo"]/text()', $listItem)->item(0)->textContent;

        $data[] = [
            'titulo' => trim($titulo),
            'dado' => trim($dado),
            'periodo' => trim($periodo),
        ];
    }


    // Converte o array para JSON
    return json_encode($data, JSON_UNESCAPED_UNICODE);
} else {
    return false; // Elemento não encontrado
}
}

// Exemplo de uso:
$url = 'https://www.ibge.gov.br/explica/inflacao.php';
$elementSelector = '#dadoBrasil';

$json = obterConteudoElementoJson($url, $elementSelector);

if ($json !== false) {
header('Content-Type: application/json'); // Define o cabeçalho para JSON
echo $json;
} else {
echo "Não foi possível obter o conteúdo do elemento '$elementSelector' do site '$url'.";
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment