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
UrlFetchAppcom 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:
- Clique . Extensões na barra de menus de cima.
- Selecione Apps Script- Não .
- O editor abre numa nova aba com padrão
Code.gsFicheiro. - 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 .
- 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: trueimpede 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
nullEntão, a fila é ignorada. Logger.logA 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}
*/
function getAnnouncementDatesThisWeek(currency) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('FXMACRODATA_API_KEY') || '';
const url = `https://fxmacrodata.com/api/v1/calendar/${currency}`
+ (apiKey ? `?api_key=${apiKey}` : '');
const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() !== 200) return new Set();
const releases = JSON.parse(response.getContentText());
const today = new Date();
const weekEnd = new Date(today);
weekEnd.setDate(today.getDate() + 7);
const dates = new Set();
(releases || []).forEach(event => {
if (!event.release_date) return;
const d = new Date(event.release_date);
if (d >= today && d <= weekEnd) {
dates.add(event.release_date.slice(0, 10));
}
});
return dates;
}
/**
* Calendar-aware version of loadMacroData. * Only runs the full indicator fetch if today has scheduled releases. */ função loadMacroDataCalendarAware() { const hoje = nova Date().toISOString().slice(0, 10); const moedas = ['usd', 'eur', 'chf', 'gbp']; const hasEvents = currencies.some(c => { const dates = getAnnouncementDatesThisWeek(c); retornar dates.has(today); }); if (!hasEvents) { Logger.log`No scheduled releases for today (${today}).Skipping fetch.`); return; } Logger .log`Release events found for ${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_datetimevalores. - 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 ...Missem vermelho usandoSpreadsheetAppÉ ...ConditionalFormatRuleBuilderpara leitura de sinal de um olhar. -
Alertas de push para Slack ou e-mail após adicionar linhas, utilizar
MailApp.sendEmailou uma chamada de webhook lá dentro .loadMacroDatapara 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_dateParâ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.