Como usar FXMacroData com o Google Apps Script e Google Sheets banner image

Implementation

How-To Guides

Como usar FXMacroData com o Google Apps Script e Google Sheets

Extrair dados de anúncio macroeconômico ao vivo do FXMacroData diretamente para o Google Sheets usando Apps Script com manipulação de limite de taxa, gatilhos de atualização automática e normalização de linha limpa para painéis de múltiplos indicadores.

Também disponível em English

Google Sheets é o scratchpad do analista de macros: rápido de atualizar, fácil de compartilhar e já conectado ao resto do espaço de trabalho do Google. Google Apps Script O tempo de execução JavaScript incorporado no Google Sheet permite que você chame qualquer API REST de uma planilha sem sair do navegador. UrlFetchApp, lidar com limites de taxa e tentativas de reinicialização, normalizar respostas de múltiplos indicadores em linhas limpas e agendar atualizações automáticas para que o painel de controle de macro permaneça atualizado sem qualquer intervenção manual.

O que você vai construir

  • Um auxiliar de busca reutilizável liga para o FXMacroData através do UrlFetchApp com logia de tentativa de reinicialização e de retrocesso incorporada
  • Carregador de múltiplos indicadores recupera vários pares de moedas/indicadores e normaliza cada um numa linha plana de folha de cálculo
  • Um escritor de folhas cria ou redefine uma aba com nome, escreve cabeçalhos e adiciona linhas a cada execução
  • Um gatilho controlado pelo tempo actualiza automaticamente a folha todas as manhãs de dia útil

Requisitos prévios

  • Conta do Google qualquer conta com acesso a Google Sheets e Apps Script
  • Chave da API do FXMacroData Inscreva-se em / subscrever; muitos pontos finais de anúncio de USD são acessíveis ao público sem uma chave para testes iniciais
  • Não há software adicional Apps Script é executado inteiramente no navegador; não é necessário Node.js, Python ou ferramentas locais

- Passo 1 -

Passo 1 Crie uma planilha do Google e abra o editor de scripts Apps

Abre . folhas.google.com e criar uma nova planilha em branco. Dê-lhe um nome descritivo como Painel de controle FXMacroDataDepois abre o editor de scripts:

  1. Clique . Extensões na barra de menus de cima.
  2. Selecione Apps Script- Não .
  3. O editor abre numa nova aba com padrão Code.gs Ficheiro.
  4. Renomear o projeto (campo superior esquerdo) para FXMacroData Loader Para ser mais claro.

Todo o código que escreve aqui é executado no lado do servidor na infraestrutura do Google ele tem acesso ao completo UrlFetchApp O serviço pode ler e escrever a planilha através do SpreadsheetApp Serviço.

Dica: guarde sua chave de API como uma propriedade de script

Nunca codifique sua chave de API diretamente no script. Configurações do projeto → Propriedades do script → Adicionar propriedade e adicionar uma propriedade chamada FXMACRODATA_API_KEY As funções auxiliares abaixo lêem esta propriedade no tempo de execução através do PropertiesService.getScriptProperties()- Não .


- Passo 2 -

Passo 2 Escreva um auxiliar de busca com lógica de reintent

Coloque o seguinte código em Code.gs, substituindo a função de espaço reservado. UrlFetchApp.fetch com retrocesso exponencial, de modo que erros transitórios ou breves respostas de limite de taxa não matam a execução inteira.

/**
 * Fetches a FXMacroData endpoint with automatic retry and exponential back-off.
 *
 * @param {string} currency  - e.g. "usd", "eur", "chf"
 * @param {string} indicator - e.g. "policy_rate", "gdp", "inflation"
 * @returns {Object|null}    - Parsed JSON response, or null on permanent failure
 */
function fetchAnnouncement(currency, indicator) {
  const apiKey = PropertiesService.getScriptProperties()
                   .getProperty('FXMACRODATA_API_KEY') || '';
  const url = `https://fxmacrodata.com/api/v1/announcements/${currency}/${indicator}`
              + (apiKey ? `?api_key=${apiKey}` : '');

  const maxRetries = 4;
  let delay = 1000; // 1 second initial back-off

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
      const status = response.getResponseCode();

      if (status === 200) {
        return JSON.parse(response.getContentText());
      }

      if (status === 429) {
        // Rate limited — honour the back-off and retry
        Logger.log(`Rate limited on attempt ${attempt}. Waiting ${delay}ms.`);
        Utilities.sleep(delay);
        delay *= 2; // exponential back-off
        continue;
      }

      if (status === 404) {
        Logger.log(`No data for ${currency}/${indicator} (404). Skipping.`);
        return null;
      }

      // Other non-retryable errors
      Logger.log(`HTTP ${status} for ${currency}/${indicator}.`);
      return null;

    } catch (e) {
      Logger.log(`Network error on attempt ${attempt}: ${e.message}`);
      Utilities.sleep(delay);
      delay *= 2;
    }
  }

  Logger.log(`Permanently failed after ${maxRetries} attempts: ${currency}/${indicator}`);
  return null;
}

Algumas coisas que vale a pena notar neste auxiliar:

  • muteHttpExceptions: true impede Apps Script de jogar em códigos não-200 você recebe o código de status e pode decidir o que fazer.
  • http 429 (Too Many Requests) desencadeia uma reatendimento com o dobro do atraso.
  • http 404 - Não significa normalmente que o indicador ainda não está disponível para essa moeda o auxiliar retorna null Então, a fila é ignorada.
  • Logger.log A saída é visível em Visualização → Registros no editor de scripts Apps, tornando a depuração simples.

- Passo 3 -

Passo 3 Normalize as respostas JSON em linhas de planilha

O ponto final de anúncios FXMacroData retorna um único objeto por par de moedas/indicadores. Para criar uma planilha útil, você precisa nivelar uma lista de solicitações em uma estrutura de linha consistente. Adicione a seguinte função de normalização abaixo fetchAnnouncement- Não .

/**
 * Converts a FXMacroData announcement response object into a flat array
 * suitable for appending as a single Sheets row.
 *
 * Columns: Timestamp, Currency, Indicator, Value, Prior, Consensus,
 *          Announcement DateTime, Direction
 *
 * @param {Object} data - Parsed JSON from fetchAnnouncement()
 * @returns {Array}     - Flat row array
 */
function toRow(data) {
  if (!data) return null;

  const direction =
    data.val > data.prior  ? 'Beat'  :
    data.val < data.prior  ? 'Miss'  : 'In line';

  return [
    new Date().toISOString(),          // Run timestamp
    (data.currency  || '').toUpperCase(),
    (data.indicator || '').replace(/_/g, ' '),
    data.val        ?? '',
    data.prior      ?? '',
    data.consensus  ?? '',
    data.announcement_datetime || '',
    direction
  ];
}

O ... announcement_datetime O campo de FXMacroData tem uma precisão de segundo nível o momento exato em que o banco central ou a agência estatística publicou a leitura.

Sobre o ... consensus campo

Quando o campo está ausente, a API o omite do objeto de resposta, por isso data.consensus ?? '' escreve com segurança uma célula vazia em vez da cadeia "undefined"- Não .


- Passo 4 -

Passo 4 Escrever dados em uma guia do Google Sheets

Agora adicione a função de carregador principal que liga tudo: ele itera sobre uma lista de pares de moeda/indicador, chamadas fetchAnnouncement, converte cada resultado com toRow, e anexa as linhas a uma aba de folha dedicada.

/**
 * Defines the currency/indicator pairs to track.
 * Extend this list to cover additional signals for your strategy.
 */
const INDICATORS = [
  { currency: 'usd', indicator: 'policy_rate' },
  { currency: 'usd', indicator: 'inflation' },
  { currency: 'usd', indicator: 'non_farm_payrolls' },
  { currency: 'eur', indicator: 'policy_rate' },
  { currency: 'eur', indicator: 'inflation' },
  { currency: 'chf', indicator: 'gdp' },
  { currency: 'chf', indicator: 'consumer_confidence' },
  { currency: 'chf', indicator: 'gov_bond_10y' },
  { currency: 'gbp', indicator: 'policy_rate' },
  { currency: 'gbp', indicator: 'inflation' },
];

const SHEET_NAME = 'MacroData';
const HEADERS    = [
  'Run Timestamp', 'Currency', 'Indicator', 'Value',
  'Prior', 'Consensus', 'Announcement DateTime', 'Direction'
];

/**
 * Main entry point — fetches all configured indicators and
 * appends new rows to the MacroData sheet tab.
 */
function loadMacroData() {
  const ss    = SpreadsheetApp.getActiveSpreadsheet();
  let   sheet = ss.getSheetByName(SHEET_NAME);

  // Create the tab if it does not exist yet
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    sheet.appendRow(HEADERS);
    sheet.getRange(1, 1, 1, HEADERS.length)
         .setFontWeight('bold')
         .setBackground('#1a73e8')
         .setFontColor('#ffffff');
    sheet.setFrozenRows(1);
  }

  // Build a set of existing announcement_datetimes to avoid duplicates
  const lastRow  = sheet.getLastRow();
  const existing = new Set();
  if (lastRow > 1) {
    const dtCol = 7; // "Announcement DateTime" is column 7 (index 6, 1-based col 7)
    const values = sheet.getRange(2, dtCol, lastRow - 1, 1).getValues();
    values.forEach(([dt]) => { if (dt) existing.add(String(dt)); });
  }

  const newRows = [];

  INDICATORS.forEach(({ currency, indicator }) => {
    // Throttle requests — 200 ms between calls keeps well within rate limits
    Utilities.sleep(200);

    const data = fetchAnnouncement(currency, indicator);
    const row  = toRow(data);

    if (!row) return; // skip null / error responses

    const announcementDt = row[6]; // announcement_datetime column
    if (existing.has(announcementDt)) {
      Logger.log(`Skipping duplicate: ${currency}/${indicator} @ ${announcementDt}`);
      return;
    }

    newRows.push(row);
    existing.add(announcementDt); // guard against duplicates within the same run
  });

  if (newRows.length > 0) {
    sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, HEADERS.length)
         .setValues(newRows);
    Logger.log(`Appended ${newRows.length} new row(s) to "${SHEET_NAME}".`);
  } else {
    Logger.log('No new rows — all announcements already present.');
  }
}

Corram . loadMacroData manualmente do editor (clique ▶ CorraA primeira execução irá solicitar-lhe para autorizar o script clique Permissões de revisão → Permitir Para conceder acesso à planilha e às solicitações de rede externa.

Dica: adicionar uma função "Reset" para desenvolvimento

Durante o desenvolvimento é útil limpar a folha e re-executar a partir do zero.

function resetSheet() {
  const ss    = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEET_NAME);
  if (sheet) ss.deleteSheet(sheet);
  loadMacroData(); // recreates with fresh headers and data
}

- Passo 5 -

Passo 5 Limitações de taxa de gestão em grandes escalas de indicadores

O atraso de 200 ms entre solicitações no Passo 4 é suficiente para a lista de dez indicadores mostrada acima. Se você expandir para 50 ou mais pares cobrindo várias moedas em todo o catálogo de anúncios completo , você deve implementar um estrangulamento mais deliberado. Substitua o sono constante por uma pausa baseada em contador:

/**
 * Fetches a larger list of indicators with adaptive throttling.
 * Pauses for 1 second every 10 requests to respect rate limits.
 */
function loadMacroDataBulk(indicators) {
  const ss    = SpreadsheetApp.getActiveSpreadsheet();
  let   sheet = ss.getSheetByName(SHEET_NAME) || (() => {
    const s = ss.insertSheet(SHEET_NAME);
    s.appendRow(HEADERS);
    s.getRange(1, 1, 1, HEADERS.length)
     .setFontWeight('bold')
     .setBackground('#1a73e8')
     .setFontColor('#ffffff');
    s.setFrozenRows(1);
    return s;
  })();

  const newRows = [];
  let   count   = 0;

  indicators.forEach(({ currency, indicator }) => {
    count++;

    // Longer pause every 10 requests
    if (count % 10 === 0) {
      Logger.log(`Pausing after ${count} requests…`);
      Utilities.sleep(1500);
    } else {
      Utilities.sleep(150);
    }

    const data = fetchAnnouncement(currency, indicator);
    const row  = toRow(data);
    if (row) newRows.push(row);
  });

  if (newRows.length > 0) {
    sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, HEADERS.length)
         .setValues(newRows);
  }
  Logger.log(`Bulk load complete — ${newRows.length} rows appended.`);
}

O ponto final de anúncio do FXMacroData é rápido cada resposta normalmente retorna em menos de 100 ms de um ambiente de execução de script do Google Apps. Utilities.sleep é a maneira mais simples de ficar dentro dos limites do seu plano sem batch ou cache lógica.


- O passo 6 .

Passo 6 Agendar atualizações automáticas com um gatilho orientado pelo tempo

Aplicações do Script. Ativadores O assistente seguinte cria um gatilho de manhã de dia de semana programaticamente execute-o uma vez do editor para registrá-lo, em seguida, excluir o próprio assistente:

/**
 * Registers a time-driven trigger that runs loadMacroData()
 * every weekday between 07:00 and 08:00 UTC.
 *
 * Run this function ONCE from the Apps Script editor to set up the trigger.
 * You do not need to call it again — it persists in the project.
 */
function createWeekdayTrigger() {
  // Remove any existing triggers for loadMacroData to avoid duplicates
  ScriptApp.getProjectTriggers()
    .filter(t => t.getHandlerFunction() === 'loadMacroData')
    .forEach(t => ScriptApp.deleteTrigger(t));

  ScriptApp.newTrigger('loadMacroData')
    .timeBased()
    .everyWeeks(1)
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(7)
    .create();

  // Also register Tuesday through Friday
  [
    ScriptApp.WeekDay.TUESDAY,
    ScriptApp.WeekDay.WEDNESDAY,
    ScriptApp.WeekDay.THURSDAY,
    ScriptApp.WeekDay.FRIDAY,
  ].forEach(day => {
    ScriptApp.newTrigger('loadMacroData')
      .timeBased()
      .everyWeeks(1)
      .onWeekDay(day)
      .atHour(7)
      .create();
  });

  Logger.log('Weekday triggers registered for loadMacroData.');
}

Depois de correr . createWeekdayTrigger- Abre . Ativadores (icono de alarme-sino na barra lateral esquerda do editor) para confirmar cinco gatilhos aparecem um para cada dia da semana. Cada gatilho dispara entre 07:00 e 08:00 no fuso horário configurado para a sua conta do Google.

Alinhamento com o calendário de lançamento

Para uma abordagem mais cirúrgica, consulte o Ponto final do calendário de lançamento do FXMacroData No início de cada semana, para encontrar dias com anúncios de alto impacto programados, em seguida, apenas execute a varredura completa do indicador nesses dias.


- O passo 7 .

Passo 7 Pega no calendário de lançamento para pré-filtrar por dias de evento

O ... /v1/calendar/{currency} Endpoint retorna as próximas versões programadas para uma moeda. Use-o na segunda-feira para criar um conjunto de datas de anúncio para a semana, em seguida, pule a etapa de busca em dias sem eventos isso evita chamadas desnecessárias de API em semanas tranquilas.

/**
 * Returns a Set of date strings ("YYYY-MM-DD") for which at least one
 * high-impact announcement is scheduled this week for the given currency.
 *
 * @param {string} currency - e.g. "usd"
 * @returns {Set} */ função getAnnouncementDatesThisWeek(currency) { const apiKey = PropertiesService.getScriptProperties() .getProperty (('FXMACRODATA.getCode.getResponse.API_KEY') '; const url = https://fxmacrodata.com/api/v1/calendar/${currency}` + (apiKey ? `(((?api_key=${apiKee}` : ' ' '); const response = UrlFetch mute.Appfetch, {HttpExceptions: true }); if (response.getCalCode) == 200) = newset; const const retranslate = new); const Date fetches = JSON.Macdate.getFetchedData.getContent.getTexts (); const const const rest rest restores = new dates; const restores (data); constores (date); const restored (date) = data); const for today; const for $ (date).getData (date;data); restores; constores; restores de data); rest restored for today (date (date), *constores); const data const constores = data; const 'date); restored' (date'; restores 'date'; 'date;date); 'date'); const 'today); const;date (day); const (date}); const ('date); (date'): 'todays); 'todday); 'day); (day'); (date) (date=today;date; (date-day); ('today) (day) (d) (today). (date)). (date.events); (events) (event); (data) (de) (e) (s) (v.event) (D) (c) (this) (current) (a) (today) (ex.d) {events). (dete) (This) (new) (Event) (r) (n.d.d). (ev.d.) (d.e.d); (e.e) {d) } (d.) { (d} (d); 'e) }) (now) (no); (todays

Para utilizar este padrão, registe-se loadMacroDataCalendarAware como o manipulador do gatilho em vez de loadMacroData substituir a cadeia de nomes de funções em createWeekdayTrigger Em conformidade.


- Resumo -

Resumo

Agora você tem um pipeline completo, pronto para produção que conecta FXMacroData para Google Sheets através de Apps Script:

  • Um auxiliar de busca com tentativa de reinicialização e back-off exponencial que lida com erros de rede transitórios e respostas de limite de taxa com graça.
  • Uma função de normalização que converte cada resposta de anúncio em uma linha de planilha consistente e segura de deduplicação.
  • Um redator de folhas que cria cabeçalhos na primeira execução, anexa apenas novos lançamentos e pula os vistos anteriormente announcement_datetime valores.
  • A redução adaptativa para barragens a granel em dezenas de pares de moedas/indicadores.
  • Um gatilho de dia de semana para atualizações diárias totalmente automatizadas.
  • Uma pré-verificação de calendário opcional que evita chamadas de API redundantes em dias sem lançamentos agendados.

Próximos passos

  • Aumentar a lista de indicadores consulte o catálogo completo em /api-data-docs e adicionar pares relevantes para a sua estratégia (por exemplo chf/gov_bond_10y- Não . eur/pmi- Não . gbp/employment)
  • Adicionar formatação condicional realçar linhas onde Direção É ... Beat - Em verde e ... Miss em vermelho usando SpreadsheetAppÉ ... ConditionalFormatRuleBuilder para leitura de sinal de um olhar.
  • Alertas de push para Slack ou e-mail após adicionar linhas, utilizar MailApp.sendEmail ou uma chamada de webhook lá dentro . loadMacroData para notificar a sua equipa quando uma impressão de alta incidência chegar.
  • Acompanhe os valores históricos o Anúncios ponto final também aceita . start_date - Não . end_date Parâmetros executar um recheio único em um intervalo de datas mais longo para semear uma guia histórica ao lado da alimentação ao vivo.

Blogroll