Skip to content

Instantly share code, notes, and snippets.

@danperrout
Last active January 8, 2026 20:46
Show Gist options
  • Select an option

  • Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.

Select an option

Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
API Função TESOURODIRETO Google Sheets
/*
* @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.");
}
@kleitonramires
Copy link

@andreluistosato agradeço por ter compartilhado seu conhecimento. Resolveu aqui. Vida longa a você.

@nozeedd
Copy link

nozeedd commented Sep 11, 2025

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"

@diegodemorais
Copy link

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!

@mathsebastiany
Copy link

mathsebastiany commented Sep 19, 2025

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);
  }
}

@hugobrilhante
Copy link

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();
  }
}

@nogueiradario
Copy link

nogueiradario commented Dec 8, 2025

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.

@afalcaoneto88
Copy link

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();
  }
}

Maravilha @hugobrilhante ! Só não estou conseguindo puxar o preço do Tesouro Renda+ Aposentadoria Extra 2045.
Já tentei colocando das duas formas:
=TESOURODIRETO("Tesouro Renda+ 2045")
=TESOURODIRETO("Tesouro Renda+ Aposentadoria Extra 2045")

Tem alguma solução?
Muito obrigado!

@e-pke
Copy link

e-pke commented Dec 9, 2025

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();
  }
}

Maravilha @hugobrilhante ! Só não estou conseguindo puxar o preço do Tesouro Renda+ Aposentadoria Extra 2045. Já tentei colocando das duas formas: =TESOURODIRETO("Tesouro Renda+ 2045") =TESOURODIRETO("Tesouro Renda+ Aposentadoria Extra 2045")

Tem alguma solução? Muito obrigado!

Essa função pega a coluna da data de vencimento para extrair o ano do título. Na primeira coluna desse arquivo .csv os títulos não tem o ano no nome.

Eu sugiro escrever: =TESOURODIRETO("Tesouro Renda+ Aposentadoria Extra 2064")
Veja se funciona.

Esse título (2045) vence em 2064, por isso acho que deve funcionar.

@gabrielgasp
Copy link

AVISO A TODOS QUE USAM A MINHA API

O domínio da API foi alterado de tesouro.gabrielgaspar.com.br para tesouro.gabriso.com.

O domínio original continuará funcionando por meio de redirect até 09/09/2026, quando será desativado. Recomendo que todos realizem a atualização antes dessa data.

Obs.: O conteúdo da API não foi alterado.

@adelitofarias
Copy link

Pessoal, tudo em boa paz?
Estou com dificuldade para encontrar os dados do dia 30/12/2025.
Nem no gráfico do Tesouro eles listam, aparecendo o valor zerado.
Alguém sabe com recuperar esses dados?
Grato pela cortesia desde já.

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