-
-
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."); | |
| } |
Pessoal, funcionou perfeitamente! Muitíssimo obrigado @gabrielgasp
Fiz um pequeno ajuste pra retornar somente o Juros pós-fixado, sem o texto "Selic" ou "IPCA".
Assim:
function GET_TITULO_TESOURO(bondName) {
const srcURL = `https://tesouro.gabrielgaspar.com.br/bonds/${bondName}`;
const jsondata = UrlFetchApp.fetch(srcURL);
if (jsondata.getResponseCode() !== 200) {
throw new Error("Erro ao buscar título");
}
const parsedData = JSON.parse(jsondata.getContentText());
const rate = parsedData.bond.annual_investment_rate; // Ex: "SELIC + 0,05%"
// Extrai o primeiro número com sinal de % da string
const match = rate.match(/-?\d+,\d+%/);
return match ? match[0] : "";
}
Abraços!
Fala, pessoal!
Fiz adaptações para incluir algumas funções que eu uso na minha planilha e vou disponibilizar para vocês aqui:
txcompra(bondName) → taxa de compra (annual_investment_rate)
txvenda(bondName) → taxa de venda (annual_redemption_rate)
pucompra(bondName) → preço de compra (unitary_investment_value)
puvenda(bondName) → preço de venda (unitary_redemption_value)
function txcompra(bondName) {
var tesouro = getCachedTesouro();
if (!tesouro) updateTesouroCache();
tesouro = getCachedTesouro();
for (let bond of tesouro.bonds) {
if (bond.name.toLowerCase() === bondName.toLowerCase()) {
return extrairTaxaNumerica(bond.annual_investment_rate);
}
}
return "Título não encontrado";
}
function txvenda(bondName) {
var tesouro = getCachedTesouro();
if (!tesouro) updateTesouroCache();
tesouro = getCachedTesouro();
for (let bond of tesouro.bonds) {
if (bond.name.toLowerCase() === bondName.toLowerCase()) {
return extrairTaxaNumerica(bond.annual_redemption_rate);
}
}
return "Título não encontrado";
}
/**
* Extrai apenas a parte numérica da taxa"
*/
function extrairTaxaNumerica(taxa) {
if (!taxa) return "";
var partes = taxa.split("+");
return partes.length > 1 ? partes[1].trim() : taxa.trim();
}
function puvenda(bondName) {
var tesouro = getCachedTesouro();
if (!tesouro) {
Logger.log("No cached tesouro info found");
updateTesouroCache();
}
tesouro = getCachedTesouro();
console.log(tesouro);
for (let bond of tesouro.bonds) {
console.log(bond);
if (bond.name.toLowerCase() === bondName.toLowerCase())
return bond.unitary_redemption_value;
}
return 0;
}
function pucompra(bondName) {
var tesouro = getCachedTesouro();
if (!tesouro) {
Logger.log("No cached tesouro info found");
updateTesouroCache();
}
tesouro = getCachedTesouro();
console.log(tesouro);
for (let bond of tesouro.bonds) {
console.log(bond);
if (bond.name.toLowerCase() === bondName.toLowerCase())
return bond.unitary_investment_value;
}
return 0;
}
/**
* Função para buscar o tesouro armazenado em cache.
* @return {Object} O objeto do tesouro armazenado.
*/
function getCachedTesouro() {
var cached = cache.get("tesouro");
if (cached) {
try {
var tesouro = JSON.parse(cached);
return tesouro;
} catch (e) {
Logger.log("Error while parsing tesouro from cache");
}
}
return null;
}
/**
* Função para atualizar o cache com as informações mais recentes do Tesouro Direto.
*/
function updateTesouroCache() {
Logger.log("Updating tesouro cached information");
var url = "https://tesouro.gabrielgaspar.com.br/bonds";
var response = UrlFetchApp.fetch(url);
console.log(JSON.parse(response.getContentText()));
var content = response.getContentText();
try {
var data = JSON.parse(content);
cache.put("tesouro", JSON.stringify(data), 21600);
} catch (e) {
Logger.log("Error while parsing response from tesouro: " + content);
}
}
Criei essa versão que busca só o preço. Demora um pouco por conta do tamanho do arquivo mas funciona.
/**
* Função auxiliar para converter data DD/MM/AAAA para um formato comparável (AAAA-MM-DD).
* @param {string} dateString Data no formato DD/MM/AAAA.
* @return {string} Data no formato AAAA-MM-DD.
*/
function parseDateForComparison(dateString) {
if (!dateString) return '';
const parts = dateString.split('/');
if (parts.length === 3) {
// Retorna YYYY-MM-DD, que pode ser comparado como string
return `${parts[2]}-${parts[1]}-${parts[0]}`;
}
return '';
}
/**
* Busca o preço de um título do Tesouro.
*
* @param {string} nomeDoTitulo O nome do título a ser buscado (Ex: "Tesouro IPCA+ 2029").
* @return {number|string} O preço de investimento do título ou uma mensagem de erro.
* @customfunction
*/
function TESOURODIRETO(nomeDoTitulo) {
const URL_CSV =
"https://www.tesourotransparente.gov.br/ckan/dataset/df56aa42-484a-4a59-8184-7676580c81e3/resource/796d2059-14e9-44e3-80c9-2d9e30b405c1/download/precotaxatesourodireto.csv";
const options = {
muteHttpExceptions: true
};
const termoBuscado = nomeDoTitulo.trim().toLowerCase();
let latestMatch = null; // Armazenará {dateSortable: string, columns: array}
try {
const response = UrlFetchApp.fetch(URL_CSV, options);
const csvContent = response.getContentText();
if (response.getResponseCode() !== 200) {
return "ERRO: Não foi possível baixar o CSV. Código: " + response.getResponseCode();
}
const linhas = csvContent.split('\n');
for (let i = 1; i < linhas.length; i++) {
const linha = linhas[i];
if (linha.trim() === "") continue;
const colunas = linha.split(';');
if (colunas.length < 8) continue;
// Colunas: [0]Tipo Titulo, [1]Data Vencimento, [2]Data Base, [5]PU Compra Manha
const tipoTitulo = colunas[0].trim();
const dataVencimento = colunas[1].trim();
const dataBase = colunas[2].trim();
const anoVencimento = dataVencimento.slice(-4);
const nomeCompletoNoArquivo = `${tipoTitulo} ${anoVencimento}`.trim().toLowerCase();
if (nomeCompletoNoArquivo === termoBuscado) {
const sortableDate = parseDateForComparison(dataBase);
if (sortableDate) {
if (!latestMatch || sortableDate > latestMatch.dateSortable) {
latestMatch = {
dateSortable: sortableDate,
columns: colunas
};
}
}
}
}
if (latestMatch) {
let precoTexto = latestMatch.columns[5].trim();
// Limpa e converte o preço para número
precoTexto = precoTexto
.replace("R$", "")
.replace(/\./g, "")
.replace(",", ".")
.trim();
return parseFloat(precoTexto);
}
return `Título "${nomeDoTitulo}" não encontrado. Verifique o Tipo Título e o Ano de Vencimento.`;
} catch (e) {
return "ERRO: " + e.toString();
}
}Criei essa versão que busca só o preço. Demora um pouco por conta do tamanho do arquivo mas funciona.
/** * Função auxiliar para converter data DD/MM/AAAA para um formato comparável (AAAA-MM-DD). * @param {string} dateString Data no formato DD/MM/AAAA. * @return {string} Data no formato AAAA-MM-DD. */ function parseDateForComparison(dateString) { if (!dateString) return ''; const parts = dateString.split('/'); if (parts.length === 3) { // Retorna YYYY-MM-DD, que pode ser comparado como string return `${parts[2]}-${parts[1]}-${parts[0]}`; } return ''; } /** * Busca o preço de um título do Tesouro. * * @param {string} nomeDoTitulo O nome do título a ser buscado (Ex: "Tesouro IPCA+ 2029"). * @return {number|string} O preço de investimento do título ou uma mensagem de erro. * @customfunction */ function TESOURODIRETO(nomeDoTitulo) { const URL_CSV = "https://www.tesourotransparente.gov.br/ckan/dataset/df56aa42-484a-4a59-8184-7676580c81e3/resource/796d2059-14e9-44e3-80c9-2d9e30b405c1/download/precotaxatesourodireto.csv"; const options = { muteHttpExceptions: true }; const termoBuscado = nomeDoTitulo.trim().toLowerCase(); let latestMatch = null; // Armazenará {dateSortable: string, columns: array} try { const response = UrlFetchApp.fetch(URL_CSV, options); const csvContent = response.getContentText(); if (response.getResponseCode() !== 200) { return "ERRO: Não foi possível baixar o CSV. Código: " + response.getResponseCode(); } const linhas = csvContent.split('\n'); for (let i = 1; i < linhas.length; i++) { const linha = linhas[i]; if (linha.trim() === "") continue; const colunas = linha.split(';'); if (colunas.length < 8) continue; // Colunas: [0]Tipo Titulo, [1]Data Vencimento, [2]Data Base, [5]PU Compra Manha const tipoTitulo = colunas[0].trim(); const dataVencimento = colunas[1].trim(); const dataBase = colunas[2].trim(); const anoVencimento = dataVencimento.slice(-4); const nomeCompletoNoArquivo = `${tipoTitulo} ${anoVencimento}`.trim().toLowerCase(); if (nomeCompletoNoArquivo === termoBuscado) { const sortableDate = parseDateForComparison(dataBase); if (sortableDate) { if (!latestMatch || sortableDate > latestMatch.dateSortable) { latestMatch = { dateSortable: sortableDate, columns: colunas }; } } } } if (latestMatch) { let precoTexto = latestMatch.columns[5].trim(); // Limpa e converte o preço para número precoTexto = precoTexto .replace("R$", "") .replace(/\./g, "") .replace(",", ".") .trim(); return parseFloat(precoTexto); } return `Título "${nomeDoTitulo}" não encontrado. Verifique o Tipo Título e o Ano de Vencimento.`; } catch (e) { return "ERRO: " + e.toString(); } }
Sensacional, funcionou perfeitamente, muito obrigado @hugobrilhante! e creio que sua solução vai ser duradoura. E pra quem quiser buscar outros valores é só trocar o 5 no "let precoTexto = latestMatch.columns[5].trim();" que voce vai ter outros resultados, o 5 por padrão é o valor de compra, como está comentado " [0]Tipo Titulo, [1]Data Vencimento, [2]Data Base, [5]PU Compra Manha" e acrescento que o 6 é o valor de venda. Não testei, mas creio que 3 e 4 são as taxas de compra e venda, respectivamente, vale o teste para quem quiser.
Era o que eu precisava. Um tanto básico, peguei a API do @gabrielgasp e importei pelo Excel > Power Query
Converter a Lista para tabela, Renomear coluna, Expandir para diversas colunas.
let Source = Json.Document(Web.Contents("https://tesouro.gabrielgaspar.com.br/bonds")), bonds1 = Source[bonds], #"Converted to Table" = Table.FromList(bonds1, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Renamed Columns" = Table.RenameColumns(#"Converted to Table",{{"Column1", "TD"}}), #"Expanded TD" = Table.ExpandRecordColumn(#"Renamed Columns", "TD", {"name", "investable", "annual_investment_rate", "unitary_investment_value", "minimum_investment_amount", "annual_redemption_rate", "unitary_redemption_value", "maturity"}, {"TD.name", "TD.investable", "TD.annual_investment_rate", "TD.unitary_investment_value", "TD.minimum_investment_amount", "TD.annual_redemption_rate", "TD.unitary_redemption_value", "TD.maturity"}) in #"Expanded TD"