-
-
Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
/* | |
* @return Acesse radaropcoes.com Retorna a cotação atual de um título específico do Tesouro Direto. | |
* API: https://radaropcoes.com/ | |
* 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."); | |
} |
Perfeito Artur! Valeu!
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.
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
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.
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
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);
}
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,
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'.";
}
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,
Para o IPCA, utilizo essa função do Apps Script que puxa o IPCA acumulado em um período, então para puxar do mês atual e dos últimos 12 meses é só selecionar o período correto ao enviar:
/**
Calcula o IPCA acumulado entre duas datas usando a API do Banco Central.
@param {string} startDate - Data inicial no formato "dd/mm/yyyy"
@param {string} endDate - Data final no formato "dd/mm/yyyy"
@return {number} IPCA acumulado no período (ex.: 0.035 = 3,5%)
@customfunction
*/
function getIPCA(startDate, endDate) {
const fStartDate = startDate instanceof Date ? startDate.toLocaleDateString("pt-BR") : startDate;
const fEndDate = endDate instanceof Date ? endDate.toLocaleDateString("pt-BR") : endDate;
const url = https://api.bcb.gov.br/dados/serie/bcdata.sgs.433/dados?formato=json&dataInicial=${fStartDate}&dataFinal=${fEndDate};
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
if (data.length === 0) {
throw new Error("Nenhum dado encontrado para o período informado.");
}
// Calcular o IPCA acumulado com juros compostos
let fator = 1;
data.forEach(entry => {
const valor = parseFloat(entry.valor.replace(",", "."));
fator *= 1 + (valor / 100);
});
const acumulado = fator - 1;
return acumulado;
}
Opa pessoal, somente comigo ou o endpoint do TesouroDireto está indisponível novamente (error 404)?
Desde quinta-feira ou sexta-feira...
https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json
Grato.
Olá pessoal, o endpoint do TD está fora novamente?
Só recebo error 404 desde sexta-feira 15/08/2025.
https://www.tesourodireto.com.br/json/br/com/b3/tesourodireto/service/api/treasurybondsinfo.json
Mais alguém?
Grato.
Comigo também está fora do ar!
Será que eles descontinuaram esse acesso?
Pra mim tbm está fora do ar
Achei, mas tive que mudar "um pouco" o código.
`
/**
- =TESOURODIRETO("Tesouro IPCA+ 2029"; "ti")
- argumentos suportados:
-
- "ti" -> taxa para investir (ex.: "7,02")
-
- "tr" -> taxa para resgatar
-
- "min" -> aporte mínimo (número em R$)
-
- "venc"-> data de vencimento (string ISO)
-
- "tipo"-> U/S/M (sem juros, com juros, mensal - Renda+/Educa+)
-
- "raw" -> objeto completo do título (JSON)
- retrocompat:
-
- "i" -> retorna aporte mínimo (PU não está no novo endpoint)
-
- "r" -> retorna aporte mínimo (PU não está no novo endpoint)
- Precisa buscar em https://www.tesourodireto.com.br/o/rentabilidade/resgatar
*/
function TESOURODIRETO(bondName, argumento) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();
var url = "https://www.tesourodireto.com.br/o/rentabilidade/investir";
var resp = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
followRedirects: true,
headers: {
"Accept": "application/json, text/plain, /",
"User-Agent": "Mozilla/5.0 (compatible; AppsScriptFetcher/1.0)",
"Referer": "https://www.tesourodireto.com.br/produtos/dados-sobre-titulos/rendimento-dos-titulos"
}
});
var code = resp.getResponseCode();
if (code !== 200) {
throw new Error("HTTP " + code + " ao acessar o endpoint. Corpo: " + resp.getContentText().slice(0, 300));
}
var text = resp.getContentText("UTF-8");
var data;
try {
data = JSON.parse(text);
} catch (e) {
throw new Error("Falha ao parsear JSON do endpoint: " + e + " | trecho: " + text.slice(0, 300));
}
if (!Array.isArray(data)) {
throw new Error("Formato inesperado: esperado array na raiz.");
}
// Procura o título por nome (case-insensitive); tenta match exato e depois "contém"
var titulo = data.find(function (o) {
return (o.treasuryBondName || "").toLowerCase() === bondName.toLowerCase();
}) || data.find(function (o) {
return (o.treasuryBondName || "").toLowerCase().indexOf(bondName.toLowerCase()) >= 0;
});
if (!titulo) {
throw new Error("Título não encontrado: " + bondName);
}
function parsePercentFromIndexer(str) {
if (!str) return null;
var m = str.match(/([+-]?\d+(?:[.,]\d+)?)\s*%/);
if (!m) return null;
return Number(m[1].replace(",", "."));
}
switch (argumento) {
case "ti":
return parsePercentFromIndexer(titulo.investmentProfitabilityIndexerName);
case "tr":
return parsePercentFromIndexer(titulo.redemptionProfitabilityFeeIndexerName);
case "ti_str":
return titulo.investmentProfitabilityIndexerName || "";
case "tr_str":
return titulo.redemptionProfitabilityFeeIndexerName || "";
case "min":
return Number(titulo.investmentBondMinimumValue);
case "venc":
return titulo.maturityDate || "";
case "tipo":
return titulo.typeReceiptInterest || "";
case "raw":
return JSON.stringify(titulo);
case "i":
case "r":
return (Number(titulo.investmentBondMinimumValue))*100;
default:
return titulo.investmentProfitabilityIndexerName || "";
}
}
`
boa tarde, na verdade preciso pegar o PU e não a taxa, alguém conseguiu depois dessa mudança?
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download.
Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse:
=ARRAYFORMULA(SPLIT(C2:C70; ";"))
Achei, mas tive que mudar "um pouco" o código.
` /**
- =TESOURODIRETO("Tesouro IPCA+ 2029"; "ti")
- argumentos suportados:
- "ti" -> taxa para investir (ex.: "7,02")
- "tr" -> taxa para resgatar
- "min" -> aporte mínimo (número em R$)
- "venc"-> data de vencimento (string ISO)
- "tipo"-> U/S/M (sem juros, com juros, mensal - Renda+/Educa+)
- "raw" -> objeto completo do título (JSON)
- retrocompat:
- "i" -> retorna aporte mínimo (PU não está no novo endpoint)
- "r" -> retorna aporte mínimo (PU não está no novo endpoint)
- Precisa buscar em https://www.tesourodireto.com.br/o/rentabilidade/resgatar
*/
function TESOURODIRETO(bondName, argumento) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();var url = "https://www.tesourodireto.com.br/o/rentabilidade/investir";
var resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true, followRedirects: true, headers: { "Accept": "application/json, text/plain, /", "User-Agent": "Mozilla/5.0 (compatible; AppsScriptFetcher/1.0)", "Referer": "https://www.tesourodireto.com.br/produtos/dados-sobre-titulos/rendimento-dos-titulos" } });
var code = resp.getResponseCode(); if (code !== 200) { throw new Error("HTTP " + code + " ao acessar o endpoint. Corpo: " + resp.getContentText().slice(0, 300)); }
var text = resp.getContentText("UTF-8"); var data; try { data = JSON.parse(text); } catch (e) { throw new Error("Falha ao parsear JSON do endpoint: " + e + " | trecho: " + text.slice(0, 300)); }
if (!Array.isArray(data)) { throw new Error("Formato inesperado: esperado array na raiz."); }
// Procura o título por nome (case-insensitive); tenta match exato e depois "contém" var titulo = data.find(function (o) { return (o.treasuryBondName || "").toLowerCase() === bondName.toLowerCase(); }) || data.find(function (o) { return (o.treasuryBondName || "").toLowerCase().indexOf(bondName.toLowerCase()) >= 0; });
if (!titulo) { throw new Error("Título não encontrado: " + bondName); }
function parsePercentFromIndexer(str) { if (!str) return null; var m = str.match(/([+-]?\d+(?:[.,]\d+)?)\s*%/); if (!m) return null; return Number(m[1].replace(",", ".")); }
switch (argumento) { case "ti": return parsePercentFromIndexer(titulo.investmentProfitabilityIndexerName); case "tr": return parsePercentFromIndexer(titulo.redemptionProfitabilityFeeIndexerName); case "ti_str": return titulo.investmentProfitabilityIndexerName || ""; case "tr_str": return titulo.redemptionProfitabilityFeeIndexerName || ""; case "min": return Number(titulo.investmentBondMinimumValue); case "venc": return titulo.maturityDate || ""; case "tipo": return titulo.typeReceiptInterest || ""; case "raw": return JSON.stringify(titulo); case "i": case "r": return (Number(titulo.investmentBondMinimumValue))*100; default: return titulo.investmentProfitabilityIndexerName || ""; } }
`
Obrigado por postar essa solução!
Porém, tentei aqui mas só recebo Error 406, vide abaixo.
"
Error
Error: HTTP 406 ao acessar o endpoint. Corpo:
"
Está funcionando normalmente para você?
Grato.
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download. Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir: =IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse: =ARRAYFORMULA(SPLIT(C2:C70; ";"))
Obrigado pela dica.
Existe uma solução similar a esse (i.e., utilizando formulas GoogleSheets) para resgatar o "ti" (taxa para investir)?
Grato.
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download. Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir: =IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse: =ARRAYFORMULA(SPLIT(C2:C70; ";"))Obrigado pela dica. Existe uma solução similar a esse (i.e., utilizando formulas GoogleSheets) para resgatar o "ti" (taxa para investir)?
Grato.
O CSV trás essa informação. Você precisa tratar os dados por causa da formatação de vírgula e ponto e vírgula. Mas, está tudo lá.
É possível usar também uma automação no N8N para já entregar os dados tratados, mas ainda não tive tempo pra fazer isso.
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'.
Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar:
Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir:
Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'.
Obrigado.
Adaptei o script do App Script para Google Planilha e está funcionando para meu objetivo de pegar o valor de resgate.
/**
- Retorna informações de um título do Tesouro Direto a partir do CSV oficial de resgate.
- @param {string} bondName – Nome do título, ex.: "Tesouro IPCA+ com Juros Semestrais 2035"
- @param {string} argumento – "Taxa de Investimento" → rendimento anual do título
-
"Valor Unitario de Recompra" → preço unitário de resgate
-
"Vencimento" → data de vencimento
- @return {number|string} – Retorna o valor correspondente ao argumento solicitado
*/
function TESOURODIRETO(bondName, argumento = 'Taxa de Investimento') {
// Acessa o cache do script para evitar múltiplas requisições desnecessárias
const cache = CacheService.getScriptCache();
const cacheKey =${bondName}_${argumento}
; // Chave única para cache
// Tenta obter o resultado do cache
let cachedResult = cache.get(cacheKey);
if (cachedResult) return JSON.parse(cachedResult); // Se existe no cache, retorna imediatamente
// URL do CSV oficial de resgate do Tesouro Direto
const url = 'https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true';
// Faz a requisição e obtém o conteúdo do CSV como texto
const csvText = UrlFetchApp.fetch(url).getContentText('UTF-8');
// Separa o CSV em linhas, removendo espaços extras e linhas vazias
const linhas = csvText.split(/\r?\n/).map(l => l.trim()).filter(l => l);
// Extrai o cabeçalho (primeira linha) e cria um índice para localizar colunas pelo nome
const headers = linhas[0].split(';').map(h => h.trim());
const idx = {};
headers.forEach((h, i) => idx[h] = i);
// Procura a linha que corresponde ao título solicitado
// Faz uma busca aproximada (case-insensitive) para evitar erros de grafia
const linhaTitulo = linhas.slice(1).find(l => {
const t = l.split(';')[idx['Título']].trim().toLowerCase();
return t.includes(bondName.trim().toLowerCase());
});
// Lança erro caso o título não seja encontrado
if (!linhaTitulo) throw new Error('Título não encontrado no CSV: ' + bondName);
// Divide a linha do título em colunas individuais
const colunas = linhaTitulo.split(';');
let resultado;
// Retorna o valor conforme o argumento solicitado
switch (argumento) {
case 'Taxa de Investimento':
// Retorna o rendimento anual do título (string)
resultado = colunas[idx['Rendimento anual do título']].trim();
break;
case 'Valor Unitario de Recompra':
// Retira 'R$', pontos e converte vírgula para ponto antes de transformar em float
resultado = parseFloat(
colunas[idx['Preço unitário de resgate']]
.replace('R$', '')
.replace(/./g, '')
.replace(',', '.')
.trim()
);
break;
case 'Vencimento':
// Retorna a data de vencimento do título
resultado = colunas[idx['Vencimento do Título']].trim();
break;
default:
throw new Error('Argumento inválido para CSV de resgate.');
}
// Salva o resultado no cache por 1 hora (3600 segundos)
cache.put(cacheKey, JSON.stringify(resultado), 3600);
return resultado;
}
// --- Função de teste ---
function teste() {
// Exemplo de retorno da taxa anual do título
const taxa = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Taxa de Investimento');
Logger.log('Taxa de Investimento: ' + taxa);
// Exemplo de valor unitário de recompra
const valorRecompra = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Valor Unitario de Recompra');
Logger.log('Valor Unitario de Recompra: ' + valorRecompra);
// Exemplo de data de vencimento
const vencimento = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Vencimento');
Logger.log('Vencimento: ' + vencimento);
// Exemplo de argumento inválido (mostra que o erro é tratado)
try {
const venc = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Vencimentooooooooooo');
Logger.log('Vencimento: ' + venc);
} catch (e) {
Logger.log('Erro esperado: ' + e.message);
}
}
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'. Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar: Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir: Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'. Obrigado.
É por causa da separação por vírgulas. Você precisa tratar os dados.
Nos exemplos que você pegou:
Resgatar = R$ 3.434 | 69 = 3.434,69
Investir = R$ 3.448 | 90 = 3.448,90
Coloque a linha =vlookup(****;IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true";";");4;0)
onde *** é célula com o nome do título na célula do Google Sheets, que já aparece o valor do título.. se mudar o número 4 do final da fórmula, altera-se os dados a serem carregados do csv...
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'. Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar: Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir: Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'. Obrigado.É por causa da separação por vírgulas. Você precisa tratar os dados. Nos exemplos que você pegou: Resgatar = R$ 3.434 | 69 = 3.434,69 Investir = R$ 3.448 | 90 = 3.448,90
Obrigado pela resposta.
Talvez eu referenciei a variável errada, mas os valores que eu estou buscando é a taxa de juros paga no momento.
Por exemplo, para Tesouro IPCA+ 2029 é IPCA + 7,70% (18/08/2025).
Mas o resultado do GoogleSheets não parece ser tão preciso (IPCA +7) ao invés de 7,70.
A variável que eu buscava anteriorment era: 'anulInvstmtRate'.
Grato.
Segue uma versão usando os CSVs, assim conseguimos os dados de investimento e de resgate:
`function TESOURODIRETO(bondName, argumento, urlCsv) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();
var urlInvestir = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true";
var urlResgatar = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true";
if (!urlCsv || urlCsv === "") {
if (argumento === "ti" || argumento === "ti_txt" || argumento === "i" || argumento === "i_txt" || argumento === "min") {
urlCsv = urlInvestir;
} else if (argumento === "tr" || argumento === "tr_txt" || argumento === "r" || argumento === "r_txt") {
urlCsv = urlResgatar;
} else {
urlCsv = urlInvestir;
}
}
var resp = UrlFetchApp.fetch(urlCsv, { muteHttpExceptions: true, followRedirects: true });
if (resp.getResponseCode() !== 200) {
throw new Error("HTTP " + resp.getResponseCode() + " ao acessar CSV: " + urlCsv);
}
var text = resp.getContentText("UTF-8");
var rows = Utilities.parseCsv(text, ";");
if (!rows || rows.length < 2) throw new Error("CSV vazio ou sem dados.");
var header = rows[0].map(function(s){ return String(s||"").trim(); });
var investirLayout = header.length >= 5 && header[2].toLowerCase().indexOf("investimento") >= 0;
var IDX = {};
if (investirLayout) {
IDX = { titulo:0, taxa:1, minimo:2, puInvest:3, venc:4, puResg:null };
} else {
IDX = { titulo:0, taxa:1, minimo:null, puInvest:null, venc:3, puResg:2 };
}
var lower = bondName.toLowerCase();
var data = rows.slice(1);
var row = data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase() === lower; }) ||
data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase().indexOf(lower) >= 0; });
if (!row) throw new Error("Título não encontrado: " + bondName);
var taxaTxt = row[IDX.taxa] || "";
var vencTxt = row[IDX.venc] || "";
var minimoTxt = (IDX.minimo != null) ? (row[IDX.minimo] || "") : "";
var puInvTxt = (IDX.puInvest != null) ? (row[IDX.puInvest] || "") : "";
var puResTxt = (IDX.puResg != null) ? (row[IDX.puResg] || "") : "";
var taxaNum = extractPercentNumber(taxaTxt);
var minimo = toNumberCurrency(minimoTxt);
var puInv = toNumberCurrency(puInvTxt);
var puRes = toNumberCurrency(puResTxt);
switch (argumento) {
case "ti": return taxaNum;
case "ti_txt": return taxaTxt;
case "i": return puInv;
case "i_txt": return puInvTxt;
case "min": return minimo;
case "tr": return taxaNum;
case "tr_txt": return taxaTxt;
case "r": return puRes;
case "r_txt": return puResTxt;
case "venc": return vencTxt;
case "raw": return JSON.stringify(objectFromRow(header, row));
default: return taxaNum;
}
}
function toNumberCurrency(brStr){
if (!brStr) return NaN;
var s = String(brStr).replace(/[^\d,.-]/g,"").replace(/./g,"").replace(",",".");
var n = Number(s);
return isNaN(n) ? NaN : n;
}
function extractPercentNumber(txt){
if (!txt) return NaN;
var m = String(txt).match(/(-?\d+(?:,\d+)?)\s*%/);
return m ? Number(m[1].replace(",", ".")) : NaN;
}
function objectFromRow(header, row){
var o = {};
for (var i=0;i<header.length;i++) o[header[i]] = row[i];
return o;
}`
bom dia, qual argumento passa para pegar o PU de resgate? obrigado.
Segue uma versão usando os CSVs, assim conseguimos os dados de investimento e de resgate:
`function TESOURODIRETO(bondName, argumento, urlCsv) { bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim(); argumento = (argumento || "ti").toString().trim().toLowerCase(); var urlInvestir = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true"; var urlResgatar = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true"; if (!urlCsv || urlCsv === "") { if (argumento === "ti" || argumento === "ti_txt" || argumento === "i" || argumento === "i_txt" || argumento === "min") { urlCsv = urlInvestir; } else if (argumento === "tr" || argumento === "tr_txt" || argumento === "r" || argumento === "r_txt") { urlCsv = urlResgatar; } else { urlCsv = urlInvestir; } }
var resp = UrlFetchApp.fetch(urlCsv, { muteHttpExceptions: true, followRedirects: true }); if (resp.getResponseCode() !== 200) { throw new Error("HTTP " + resp.getResponseCode() + " ao acessar CSV: " + urlCsv); }
var text = resp.getContentText("UTF-8");
var rows = Utilities.parseCsv(text, ";"); if (!rows || rows.length < 2) throw new Error("CSV vazio ou sem dados.");
var header = rows[0].map(function(s){ return String(s||"").trim(); });
var investirLayout = header.length >= 5 && header[2].toLowerCase().indexOf("investimento") >= 0;
var IDX = {}; if (investirLayout) { IDX = { titulo:0, taxa:1, minimo:2, puInvest:3, venc:4, puResg:null }; } else { IDX = { titulo:0, taxa:1, minimo:null, puInvest:null, venc:3, puResg:2 }; }
var lower = bondName.toLowerCase(); var data = rows.slice(1); var row = data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase() === lower; }) || data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase().indexOf(lower) >= 0; });
if (!row) throw new Error("Título não encontrado: " + bondName);
var taxaTxt = row[IDX.taxa] || ""; var vencTxt = row[IDX.venc] || ""; var minimoTxt = (IDX.minimo != null) ? (row[IDX.minimo] || "") : ""; var puInvTxt = (IDX.puInvest != null) ? (row[IDX.puInvest] || "") : ""; var puResTxt = (IDX.puResg != null) ? (row[IDX.puResg] || "") : ""; var taxaNum = extractPercentNumber(taxaTxt); var minimo = toNumberCurrency(minimoTxt); var puInv = toNumberCurrency(puInvTxt); var puRes = toNumberCurrency(puResTxt);
switch (argumento) { case "ti": return taxaNum; case "ti_txt": return taxaTxt; case "i": return puInv; case "i_txt": return puInvTxt; case "min": return minimo; case "tr": return taxaNum; case "tr_txt": return taxaTxt; case "r": return puRes; case "r_txt": return puResTxt; case "venc": return vencTxt; case "raw": return JSON.stringify(objectFromRow(header, row)); default: return taxaNum; } }
function toNumberCurrency(brStr){ if (!brStr) return NaN; var s = String(brStr).replace(/[^\d,.-]/g,"").replace(/./g,"").replace(",","."); var n = Number(s); return isNaN(n) ? NaN : n; } function extractPercentNumber(txt){ if (!txt) return NaN; var m = String(txt).match(/(-?\d+(?:,\d+)?)\s*%/); return m ? Number(m[1].replace(",", ".")) : NaN; } function objectFromRow(header, row){ var o = {}; for (var i=0;i<header.length;i++) o[header[i]] = row[i]; return o; }`
Perfeito! Muito obrigado!!
Testei buscando PU com o parâmetro r e retorna valor zerado. Mas se peço com o parâmetro r_txt vêm o valor de resgate em formato texto. Deve ter um erro na função to NumberCurrency mas não sei identificar.
Mudei a função toNumberCurrency e resolveu o retorno do PU em forma de valor (parâmetro r):
function toNumberCurrency(brStr){
if (!brStr) return NaN;
var s = brStr.replace("R$", "").replace(/./g, "").replace(",", ".");
var n = parseFloat(s);
return isNaN(n) ? NaN : n;
}
E adicionei o 4º parâmetro na função principal (updt):
function TESOURODIRETO(bondName, argumento, urlCsv, updt) {
updt é 1 célula com caixa de seleção e quando clico atualiza.
O erro é por conta do comentário que declara a função, segue a versão corrigida: