Google Sheets es el scratchpad del analista de macros: rápido de actualizar, fácil de compartir y ya conectado al resto del espacio de trabajo de Google. Google Apps Script El tiempo de ejecución JavaScript incorporado de Sheets le permite llamar a cualquier API REST desde una hoja de cálculo sin salir del navegador. UrlFetchApp, manejar límites de velocidad y reintentos, normalizar respuestas de múltiples indicadores en filas limpias y programar actualizaciones automáticas para que su panel de control de macro permanezca actualizado sin ninguna intervención manual.
Lo que construirás
- Un ayudante de búsqueda reutilizable llama a FXMacroData a través de
UrlFetchAppcon un sistema de prueba de reinicio y de retroceso integrado - Un cargador de varios indicadores busca varios pares de divisas/indicadores y normaliza cada uno en una fila plana de hoja de cálculo
- Un escritor de hojas crea o restablece una pestaña con nombre, escribe encabezados y añade filas con cada ejecución
- Un disparador controlado por el tiempo actualiza la hoja automáticamente cada mañana de los días laborables
Los requisitos previos
- Cuenta de Google cualquier cuenta con acceso a Google Sheets y Apps Script
- La clave de la API de FXMacroData inscribirse en / suscribirse; muchos puntos finales de anuncio de USD son accesibles al público sin una clave para pruebas iniciales
- No hay software adicional Apps Script se ejecuta completamente en el navegador; no se requiere Node.js, Python o herramientas locales
- ¿ Qué pasa ?
Paso 1 Crear una hoja de Google y abrir el editor de guiones de Apps
Abierto . Las páginas. google. com y crear una nueva hoja de cálculo en blanco. El panel de control de FXMacroDataLuego abre el editor de guiones:
- Haga clic Las extensiones en la barra de menús superior.
- Selecciona Escriptura de aplicaciones- ¿ Qué ?
- El editor se abre en una nueva pestaña por defecto .
Code.gs- ¿Qué es esto? - Cambiar el nombre del proyecto (campo de arriba a la izquierda) a El cargador de FXMacroData para que quede claro.
Todo el código que escribe aquí se ejecuta en el lado del servidor en la infraestructura de Google tiene acceso a la completa UrlFetchApp El servicio de información y puede leer y escribir la hoja de cálculo a través de la
SpreadsheetApp servicio.
Consejo: almacena tu clave de API como una propiedad de script
Nunca codifique su clave API directamente en el script.
Configuración del proyecto → Propiedades del script → Añadir propiedad y añadir una propiedad llamada
FXMACRODATA_API_KEY Las funciones auxiliares de abajo leen esta propiedad en tiempo de ejecución a través de PropertiesService.getScriptProperties()- ¿ Qué ?
- ¿ Qué pasa ?
Paso 2 Escriba un ayudante de búsqueda con lógica de reintentos
Pegar el siguiente código en Code.gs, sustituyendo la función de marcador de posición. UrlFetchApp.fetch con retroceso exponencial, por lo que los errores transitorios o las respuestas breves de límite de velocidad no matan toda la ejecución.
/**
* 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;
}
Algunas cosas que vale la pena señalar en este ayudante:
muteHttpExceptions: trueevita que Apps Script lance códigos no-200 obtiene el código de estado y puede decidir qué hacer.- ¿ Qué pasa ? 429 - - - (Demasiadas solicitudes) activa una reintentación con un retraso del doble. FXMacroData impone límites de tasa por clave; una estrategia de retroceso modesta mantiene el script dentro del presupuesto a través de un barrido completo del indicador.
- ¿ Qué pasa ? - ¿Qué es eso? el ayudante devuelve
nullAsí que la fila se salta limpiamente. Logger.logla salida es visible bajo Ver → Registros en el editor de Apps Script, haciendo que la depuración sea sencilla.
- ¿ Qué pasa ?
Paso 3 Normaliza las respuestas JSON en filas de hoja de cálculo
El punto final de anuncios de FXMacroData devuelve un solo objeto por par de divisas/indicadores. Para construir una hoja de cálculo útil, debe aplanar una lista de solicitudes en una estructura de fila consistente. Añada la siguiente función de normalización a continuación fetchAnnouncement¿ Qué ?
/**
* 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
];
}
El announcement_datetime El campo de FXMacroData tiene una precisión de segundo nivel el momento exacto en que el banco central o la agencia estadística publicaron la lectura.
Sobre el ... consensus campo
No todos los indicadores tienen un valor de consenso/pronóstico. data.consensus ?? '' escribe con seguridad una celda vacía en lugar de la cadena "undefined"- ¿ Qué ?
- ¿ Qué pasa ?
Paso 4 Escriba datos en una pestaña de Hojas de cálculo de Google
Ahora añadir la función de cargador principal que une todo: se repite en una lista de pares de divisas / indicadores, llamadas fetchAnnouncement, convierte cada resultado con
toRow, y añade las filas a una pestaña de hoja 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.');
}
}
¡ Corren ! loadMacroData manual desde el editor (clic ▶ CorrerLa primera ejecución le pedirá que autorice el script haga clic
Permisos de revisión → Permitir Para conceder acceso a la hoja de cálculo y a las solicitudes de red externa.
Consejo: añadir una función "Reset" para el desarrollo
Durante el desarrollo es útil borrar la hoja y volver a correr desde cero.
function resetSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
if (sheet) ss.deleteSheet(sheet);
loadMacroData(); // recreates with fresh headers and data
}
- ¿ Qué pasa ?
Paso 5 Manejar los límites de tasa en las barridas de indicadores más grandes
El retraso de 200 ms entre solicitudes en el Paso 4 es suficiente para la lista de diez indicadores mostrados anteriormente. Si se expande a 50 o más pares que cubren múltiples monedas en todo el catálogo de anuncios completo debe implementar un estrangulamiento más deliberado.
/**
* 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.`);
}
El punto final de anuncio de FXMacroData es rápido cada respuesta normalmente devuelve menos de 100 ms desde un entorno de ejecución de Google Apps Script. Utilities.sleep es la forma más sencilla de mantenerse dentro de los límites de su plan sin batch o caché lógica.
- ¿ Qué pasa ?
Paso 6 Programe actualizaciones automáticas con un activador basado en el tiempo
Las aplicaciones de Script. Activadores El siguiente ayudante crea un activador de mañana de día laborable programáticamente ejecuta una vez desde el editor para registrarlo, luego elimina el ayudante en sí:
/**
* 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.');
}
Después de correr . createWeekdayTrigger- ¿ Qué ? Activadores (icono de campana de alarma en la barra lateral izquierda del editor) para confirmar que aparecen cinco disparadores uno para cada día de la semana. Cada disparador se dispara entre las 07:00 y las 08:00 en la zona horaria configurada para su cuenta de Google.
Alineación con el calendario de lanzamiento
Para un enfoque más quirúrgico, consulta el Punto final del calendario de liberación de FXMacroData Al final de la semana, el usuario puede seleccionar el día de la prueba para encontrar los días con anuncios de alto impacto programados, y luego ejecutar el barrido completo del indicador solo en esos días.
- ¿ Qué pasa ?
Paso 7 Busque el calendario de lanzamiento para pre-filtrar por días de evento
El /v1/calendar/{currency} Endpoint devuelve las próximas versiones programadas para una moneda. Úsala el lunes para crear un conjunto de fechas de anuncio para la semana, luego omita el paso de búsqueda en días sin eventos esto evita llamadas innecesarias de API en 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} */ función 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 rest rest; fetch Date = set; fetches JSON.Macdate.getFetchedData.getContent.getTexts; const const const; fetchedates Date = new dates; const; //data=date; //date; const for today; //date; //exitdate; *exit dates); //exitsdate; Date; //enddate;//date; } (exit date); //date); //end of the week; //events; //evenement; //today; //visions.getDate;//events.getData; //new release dates.getShow more; //release_date; /events) {date; date; //day; //no date; }; //getData = date; {date); {date=date); } (date; {data=date); //data; //forgetdate; ~date; 'date; #date;}; //now; //readdate; = date); {data = date} (date); *data; }); //no; //runsetcustdate; (date) }; {events); //getdate); 'date); /date; <date; today; } } (data; 'today); //now); //event; // (date = 'date = today; 'event); //todays; ' (date'; 'todays); 'todday); 'events'; 'date'; {date = {date'; }); 'no); 'data; {day); }); {event date; 'no; 'd
Para utilizar este patrón, regístrese loadMacroDataCalendarAware como el manipulador del gatillo en lugar de loadMacroData sustituir la cadena de nombres de la función en createWeekdayTrigger
En consecuencia.
- Resumen -
Resumen de las actividades
Ahora tiene una tubería completa y lista para la producción que conecta FXMacroData a Google Sheets a través de Apps Script:
- Un ayudante de búsqueda con reintento y retroceso exponencial que maneja los errores de red transitorios y las respuestas de límite de velocidad con gracia.
- Una función de normalización que convierte cada respuesta de anuncio en una fila de hoja de cálculo consistente y segura para la deduplicación.
- Un editor de hojas que crea encabezados en la primera ejecución, añade sólo nuevas versiones, y se salta vistas anteriormente
announcement_datetimelos valores. - El control de presión adaptativo para las barridas a granel a través de docenas de pares de divisas/indicadores.
- Un disparador de tiempo de día de semana para actualizaciones diarias totalmente automatizadas.
- Una verificación previa opcional del calendario que evita llamadas de API redundantes en días sin lanzamientos programados.
Los siguientes pasos
-
Ampliación de la lista de indicadores puede consultar el catálogo completo en
/api-datos-doc
y añadir pares relevantes para su estrategia (por ejemplo,
chf/gov_bond_10y¿ Qué ?eur/pmi¿ Qué ?gbp/employment) -
Añadir formato condicional resaltar las filas donde Dirección Es ...
Beaten verde yMissen rojo usandoSpreadsheetAppEs ...ConditionalFormatRuleBuilderpara la lectura de señales de un vistazo. -
Push alertas a Slack o correo electrónico después de añadir las filas, utilizar
MailApp.sendEmailo una llamada de webhook dentro.loadMacroDatapara notificar a su equipo cuando llegue una impresión de alto impacto. -
Seguimiento de los valores históricos el
Anuncios punto final
También acepta .
start_date- ¿ Qué ?end_dateParámetros ejecutar un relleno de una sola vez en un rango de fechas más largo para sembrar una pestaña histórica junto con la alimentación en vivo.