Google Sheets ist das Scratchpad des Makroanalytikers: schnell zu aktualisieren, einfach zu teilen und bereits mit dem Rest des Google-Arbeitsraums verbunden. Google Apps Script Die eingebaute JavaScript-Laufzeit von Sheets ermöglicht es Ihnen, jede REST-API aus einer Tabelle aufzurufen, ohne den Browser zu verlassen. Diese Anleitung führt Sie durch das Ziehen von FXMacroData-Ankündigungsdaten in einen Tab Sheets mit UrlFetchApp, die Bearbeitung von Geschwindigkeitslimits und Wiederholungen, die Normalisierung von Multi-Indikator-Antworten in saubere Zeilen und die Planung automatischer Aktualisierungen, damit Ihr Makrodashboard ohne manuelle Eingriffe aktuell bleibt.
Was du bauen wirst
- Ein wiederverwendbarer Abrufhelfer ruft FXMacroData über
UrlFetchAppmit integrierter Logik für erneute Versuche und Rückschaltvorgänge - Ein Lader mit mehreren Anzeigen holt mehrere Währungs-/Indikatorpaare ab und normalisiert jedes in eine flache Tabellenreihenfolge
- Ein Schriftsteller von Sheets erstellt oder setzt eine benannte Registerkarte zurück, schreibt Kopfzeilen und fügt Zeilen bei jedem Lauf hinzu
- Ein zeitgesteuertes Auslöser aktualisiert das Blatt automatisch jeden Wochentagmorgen
Voraussetzungen
- Google-Konto jedes Konto mit Zugriff auf Google Sheets und Apps Script
- FXMacroData-API-Schlüssel melden Sie sich an /abonnieren; viele USD-Ankündigungsendpunkte sind öffentlich zugänglich ohne einen Schlüssel für die Erstprüfung
- Keine zusätzliche Software Apps Script läuft vollständig im Browser; keine Node.js, Python oder lokale Tools sind erforderlich
- Schritt 1 -
Schritt 1 Erstellen Sie ein Google-Blatt und öffnen Sie den Apps Script Editor
- Mach auf . Die Daten werden von der Website der Kommission übermittelt. und erstellen Sie eine neue leere Tabelle. Geben Sie ihm einen beschreibenden Namen wie FXMacroData-DashboardDann öffnen Sie den Skript-Editor:
- Klicken Sie Erweiterungen in der oberen Menüleiste.
- Wählen Sie aus Apps Skript- Ich weiß .
- Der Editor öffnet sich standardmäßig in einem neuen Tab .
Code.gs- Ich habe eine Akte. - Das Projekt (oberst links) wird umbenannt auf FXMacroData-Lader Ich habe es nicht gesagt.
Alle Codes, die Sie hier schreiben, laufen auf der Serverseite auf der Infrastruktur von Google sie haben Zugriff auf die vollständige UrlFetchApp Sie können die Datenbank über die
SpreadsheetApp Dienstleistung.
Tipp: Speichern Sie Ihren API-Schlüssel als Script-Eigenschaft
Schreiben Sie niemals Ihren API-Schlüssel direkt im Skript.
Projekt-Einstellungen → Skript-Eigenschaften → Eigenschaft hinzufügen und fügen Sie eine Eigenschaft namens hinzu
FXMACRODATA_API_KEY Die Hilffunktion unten liest diese Eigenschaft zur Laufzeit über PropertiesService.getScriptProperties()- Ich weiß .
- Schritt 2 -
Schritt 2 Schreiben Sie einen Abrufs-Helfer mit Wiederholungslogik
Fügen Sie den folgenden Code ein Code.gs, die Platzhalterfunktion ersetzt. UrlFetchApp.fetch Die Ergebnisse der Studie werden in der Folge in der folgenden Folgegruppe ermittelt:
/**
* 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;
}
Ein paar Dinge , die man in diesem Hilfsprogramm beachten sollte:
muteHttpExceptions: trueverhindert Apps Script von werfen auf nicht-200-Codes Sie erhalten den Statuscode und können entscheiden, was zu tun ist.- Ich habe eine Frage . 429 (Zu viele Anfragen) löst einen erneuten Versuch mit verdoppelter Verzögerung aus. FXMacroData erzwingt pro Schlüssel Rate Limits; eine bescheidene Back-off-Strategie hält das Skript innerhalb des Budgets über einen vollen Indikator-Sweep.
- Ich habe eine Frage . 404, bedeutet in der Regel , dass der Indikator für diese Währung noch nicht verfügbar ist der Helfer gibt zurück
nullAlso ist die Reihe sauber übersprungen. Logger.logAusgang ist unter Anzeige → Protokolle in der Apps Script Editor, so dass Debugging einfach ist.
- Schritt 3 -
Schritt 3 Normalisierung von JSON-Antworten in Tabellenreihen
Der Endpunkt der Ankündigungen von FXMacroData gibt ein einziges Objekt pro Währungs-/Indikatorpaar zurück. Um eine nützliche Tabelle zu erstellen, müssen Sie eine Liste von Anfragen in eine konsistente Zeilenstruktur flach machen. Fügen Sie die folgende Normalisierungsfunktion hinzu fetchAnnouncement- Ich weiß .
/**
* 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
];
}
Die ... announcement_datetime Das Feld von FXMacroData enthält eine zweite Präzision den genauen Zeitpunkt, zu dem die Zentralbank oder die statistische Behörde den Messwert veröffentlicht hat. Dieser Zeitstempel ist ideal als Deduplikationsschlüssel: Sie können überprüfen, ob eine Zeile mit diesem Zeitstempel bereits im Blatt vor der Beilage existiert, um Doppelzeilen bei wiederholten Ausführungen zu vermeiden.
Was den ... consensus Feld
Nicht alle Indikatoren haben einen Konsens-/Vorhersagewert. Wenn das Feld fehlt, wird es von der API aus dem Antwortobjekt weggelassen, so dass data.consensus ?? '' Schreibt sicher eine leere Zelle anstelle der Zeichenfolge "undefined"- Ich weiß .
- Schritt 4 -
Schritt 4 Daten in einen Google-Blatt-Tab schreiben
Jetzt fügen Sie die Haupt-Loader-Funktion hinzu, die alles miteinander verbindet: Sie iteriert über eine Liste von Währungs-/Indikatorpaaren, Anrufe fetchAnnouncement, konvertiert jedes Ergebnis mit
toRow, und fügt die Zeilen zu einem speziellen Tab der Tabelle hinzu.
/**
* 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.');
}
}
Lauf . loadMacroData manuell vom Editor (klicken Sie auf ▶ LaufenDer erste Auslauf wird Sie auffordern, das Skript zu autorisieren klicken
Überprüfen → Zulassen Zugriff auf die Tabelle und auf die Anfragen im externen Netzwerk zu gewähren.
Tipp: Fügen Sie eine "Reset" -Funktion für die Entwicklung
Während der Entwicklung ist es nützlich, das Blatt zu löschen und von Grund auf neu zu starten.
function resetSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
if (sheet) ss.deleteSheet(sheet);
loadMacroData(); // recreates with fresh headers and data
}
- Schritt 5 -
Schritt 5 Überwachen Sie die Ratengrenzen für größere Indikatorübersichten
Die 200 ms Inter-Request-Verzögerung in Schritt 4 reicht für die oben gezeigte Liste mit zehn Indikatoren aus. Wenn Sie auf 50 oder mehr Paare erweitern mehrere Währungen im gesamten Ankündigungskatalog abdecken sollten Sie eine bewusstere Drosselung umsetzen. Ersetzen Sie den ständigen Schlaf durch eine auf einem Zähler basierende Pause:
/**
* 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.`);
}
Der Endpunkt der Ankündigung von FXMacroData ist schnell jede Antwort kehrt in der Regel in weniger als 100 ms aus einer Google Apps Script-Ausführungsumgebung zurück. Utilities.sleep ist der einfachste Weg, innerhalb der Grenzen Ihres Plans zu bleiben, ohne Batching- oder Caching-Logik.
- Schritt 6 -
Schritt 6 Planen Sie automatische Aktualisierungen mit einem zeitgesteuerten Auslöser
Apps Script ist Auslöser Das System ermöglicht es Ihnen, jede Funktion ohne einen dedizierten Server zu laufen.
/**
* 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.');
}
Nach dem Laufen . createWeekdayTrigger- Ich bin nicht hier . Auslöser (Alarm-Glocke-Symbol in der linken Seitenleiste des Editors) um zu bestätigen, dass fünf Auslöser erscheinen einer für jeden Wochentag. Jeder Auslöse wird zwischen 07:00 und 08:00 Uhr in der für Ihr Google-Konto konfigurierten Zeitzone ausgelöst.
Anpassung an den Veröffentlichungskalender
Für einen chirurgischeren Ansatz, fragen Sie die Endpunkt des Kalenders für die Veröffentlichung von FXMacroData Die Anzeige wird an den ersten Tagen der Woche durchgeführt, um Tage mit geplanten, wirkungsvollen Ankündigungen zu finden, und dann nur an diesen Tagen den vollständigen Indikator-Sweep auszuführen.
- Schritt 7 -
Schritt 7 Holen Sie den Release-Kalender zum Vorfiltern nach Veranstaltungstagen
Die ... /v1/calendar/{currency} Endpoint gibt die bevorstehenden geplanten Versionen für eine Währung zurück. Verwenden Sie es am Montag, um eine Reihe von Ankündigungsdaten für die Woche zu erstellen, und überspringen Sie dann den Abrufschritt an Tagen ohne Ereignisse Dies vermeidet unnötige API-Aufrufe in ruhigen Wochen.
/**
* 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. */ Funktion loadMacroDataCalendarAware() { const today = new Date().toISOString().slice(0, 10); const currencies = ['usd', 'eur', 'chf', 'gbp']; const hasEvents = currencies.some(c => { const dates = getAnnouncementDatesThisWeek(c); return dates.has(today); }); if (!hasEvents) { Logger.log`No scheduled releases for today (${today}).Skipping fetch.`); return; } Loggerlog.log゙Release events found for ${todays}. Running full macro fetch≤.`; fetroMacrodata; }
Um dieses Muster zu verwenden, registrieren Sie sich loadMacroDataCalendarAware als Auslöser-Handler statt loadMacroData die Funktionsnamenzeichenfolge in createWeekdayTrigger
dementsprechend.
Zusammenfassung
Zusammenfassung
Sie haben jetzt eine vollständige, produktionsfertige Pipeline, die FXMacroData über Apps Script mit Google Sheets verbindet:
- Ein Abrufs-Helfer mit Wiederholungsversuch und exponentiellen Back-off, der vorübergehende Netzwerkerröre und Geschwindigkeitsbegrenzungsantworten anmutig behandelt.
- Eine Normalisierungsfunktion, die jede Ankündigung in eine konsistente, nicht zu duplizierende Tabellenreihenreihe umwandelt.
- Ein Blatt-Reporter, der beim ersten Lauf Überschriften erstellt, nur neue Versionen hinzufügt und zuvor gesehenes überspringt.
announcement_datetimeWerte. - Adaptive Drosselung für Massenübertragungen über Dutzende von Währungs-/Indikatorpaaren.
- Ein arbeitstagszeitgesteuerter Auslöser für voll automatisierte tägliche Aktualisierungen.
- Eine optionale Kalendervorprüfung, die redundante API-Aufrufe an Tagen ohne geplante Releases vermeidet.
Nächste Schritte
-
Erweiterung der Indikatorenliste den vollständigen Katalog unter
/api-daten-docs
und fügen Sie Paare hinzu, die für Ihre Strategie relevant sind (z.B.
chf/gov_bond_10y- Ich weiß .eur/pmi- Ich weiß .gbp/employment) -
Zusatz von bedingter Formatierung Zeilen hervorheben, wo Ausrichtung Ist es ?
Beatin grün undMissin rot mitSpreadsheetApp- Das ist ...ConditionalFormatRuleBuilderfür die Signallesung auf einen Blick. -
Push-Benachrichtigungen auf Slack oder E-Mail nach dem Hinzufügen der Zeilen
MailApp.sendEmailoder ein Webhook-Anruf drinnen .loadMacroDataum Ihr Team zu benachrichtigen, wenn ein hocheffektiver Abdruck eintrifft. -
Verfolgung historischer Werte die
Ankündigungen Endpunkt
Auch akzeptiert .
start_date- Ich weiß .end_dateDie Daten werden in einem Datenbereich mit einer längeren Dauer aufbereitet.