谷歌表格是宏分析师的剪贴板:更新快,共享简单,并且已经连接到谷歌工作空间的其他部分. Google Apps Script 页面内置的JavaScript运行时间 可以让您在不离开浏览器的情况下从电子表格调用任何 REST API. 本指南将FXMacroData公告数据拉到页面选项卡中,并使用 UrlFetchApp处理速度限制和重试,将多指标响应正常化为清洁行,并安排自动更新,以便您的宏仪表板在没有手动干预的情况下保持更新.
你将要建造什么
- 可重复使用的获取助手 通过FXMacroData进行调用
UrlFetchApp具有内置的重试和后置逻辑 - 一个多指示器装载器 获取几个货币/指标对,并将每个货币对正常化为平面电子表格行
- 一位"页面"作家 创建或重置一个命名的选项卡,写头条,并添加每次运行行列
- 时间驱动的触发器 每周早上自动更新表格
预先要求
- 谷歌帐户 任何访问 Google 页面和应用程序脚本的帐户
- 汇率数据API键 登记在 订阅许多美元公告终点是公开访问的,没有初步测试的密钥.
- 没有额外的软件 应用程序脚本完全运行在浏览器中;不需要Node.js,Python或本地工具
现在我们要做什么?
步骤1 创建一个Google表格,打开应用程序脚本编辑器
打开 页面.谷歌.com 给它一个描述性的名称,例如 汇率数据仪表板然后打开脚本编辑器:
- 按下 扩展 在顶部菜单中.
- 选择 应用程序脚本现在我们要做什么?
- 编辑器以默认方式在新选项卡中打开.
Code.gs文件. - 改名项目 (左上方字段) 到 货币数据加载器 为了更明确.
您写的所有代码都在谷歌基础设施上运行, UrlFetchApp 通过 阅读和写表格
SpreadsheetApp 服务.
提示:将您的API密钥存储为脚本属性
永远不要直接在脚本中硬编码API密钥.
项目设置 → 脚本属性 → 添加属性 并且添加一个名为
FXMACRODATA_API_KEY 通过下面的辅助函数在运行时读取此属性 PropertiesService.getScriptProperties()现在我们要做什么?
现在我们要做什么?
步骤2 写一个带有重试逻辑的获取辅助器
贴上下面的代码 Code.gs取代了位符函数. 这个助手包裹 UrlFetchApp.fetch 并且具有指数式回落,因此短暂的错误或短暂速度限制反应不会杀死整个运行.
/**
* 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;
}
值得注意的一些事物:
muteHttpExceptions: true防止应用程序脚本在非-200代码上投放你得到状态代码,可以决定要做什么.- 没有任何问题. 其他 (过多请求) 触发一次复试,延迟翻倍.FXMacroData强制执行每键的速率限制;一个适度的后退策略使脚本在整个指标扫描中保持在预算范围内.
- 没有任何问题. 没有 通常意味着指标尚未用于该货币助手返回
null所以排列是清晰的跳过. Logger.log输出可以看到下面 查看 → 日志 在Apps脚本编辑器中,使调试简单.
现在我们要做什么?
步骤3 将JSON响应标准化为电子表格行
汇率/指标对返回一个单个对象.为了构建有用的电子表格,您需要将请求列表平坦化成一致的行结构. 添加下面的正常化函数 fetchAnnouncement没有人知道.
/**
* 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
];
}
没有什么. announcement_datetime 时间是作为一个除重键的理想选择:在添加之前,您可以检查表格中是否已经存在一个带有这个时间的行,防止重复运行时重复行.
关于 consensus 场地
并非所有指标都具有共识/预测值.当该字段缺失时,API将其省略在响应对象中,因此 data.consensus ?? '' 安全地写一个空的单元格而不是字符串. "undefined"现在我们要做什么?
现在我们要做什么?
步骤4 写入数据到Google表单选项卡
现在添加主要加载函数,将所有东西联系在一起:它在货币/指标对列表上代, fetchAnnouncement,将每个结果转换为
toRow,并将行添加到一个专用页面选项卡.
/**
* 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.');
}
}
现在就跑吧. loadMacroData 通过编辑器手动 (点击 ▶ 跑步首先运行会提示您授权脚本点击
检查权限 → 允许 允许访问电子表格和外部网络请求.
提示:为开发添加一个"重置"功能
在开发过程中,清除页面并从头开始重新运行是有用的. 添加一个小助手:
function resetSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
if (sheet) ss.deleteSheet(sheet);
loadMacroData(); // recreates with fresh headers and data
}
五步 没有
步骤5 处理较大的指标扫描的利率限制
在步骤4中,200ms的请求间延迟对于上述十个指标列表是足够的.如果您扩展到50个或更多的对涵盖整个公告目录中的多种货币,您应该实施更有意义的缩.用基于计数器的暂停取代持续睡眠:
/**
* 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.`);
}
FXMacroData的公告终点速度快 每个响应通常从Google Apps脚本执行环境中返回不到100ms. 大扫描的主要瓶是每个键请求预算而不是延迟; 间隔与 Utilities.sleep 是最简单的方法, 保持在计划的限制范围内, 没有批量或缓存逻辑.
现在我们要做什么?
步骤6 计划自动更新,并使用时间驱动的触发
应用程序脚本 触发器 系统允许您在没有专用服务器的时间表上运行任何函数. 下面的辅助器程序化地创建一个平日早晨触发器 从编辑器运行一次以注册它,然后删除辅助程序本身:
/**
* 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.');
}
在跑步之后 createWeekdayTrigger打开 触发器 (编辑器左侧侧中显示的警钟图标) 确认五个触发器出现,每周一都有一个.每个触发机在您的Google帐户配置的时区中在7点到8点之间发射.
与发布日程相匹配
对于更严格的手术方法,请查询 时间表终点 在每个星期的开始找到有计划的高影响力公告的日子,然后只在这些日子内运行完整的指标扫描. 这使执行时间短,API使用量低于平静的日历周.
现在我们要做什么?
步骤 7 获取发布日历以事件日预先过
没有什么. /v1/calendar/{currency} 终点返回货币的即将发布的计划. 周一使用它来构建一组本周的公告日期,然后在没有事件的日子跳过获取步骤 这避免了安静的星期中不必要的API调用.
/**
* 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} */函数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.Appfetchurl, {HttpExceptions: true }); if (response.getCalCode) == 200) return = new; fetchdate = set; fetcheddate = new); fetcheddata = set (MacData.getData.date.date; fetcher=set); fetchdata = new (Macdata.date); fetcher = fetcheddatetable; fetchesdate.data = load (MacDate); fetchesdata = fetchdate; fetchingdate (Macdate); forgetdata.data.context = fetches;date); *getdata (data); fetching dates); fetcheatdate (data) = load;date (date);); fetchs); fetchaineddate (no); fetchet (date) = new)); fetes (date (day); fetedates); fetshut;date) {date); date (date;date; date); date); run (date=today); date;date=date);date); (date).date);date (date)).
为了使用这个模式,注册 loadMacroDataCalendarAware 作为触发器处理器而不是 loadMacroData 取代函数名称字符串 createWeekdayTrigger
根据情况.
总结:
总结
您现在有一个完整的,生产准备的管道, 通过应用程序脚本将FXMacroData连接到Google表格:
- 一个带有重试和指数式后置的获取辅助器, 能优雅地处理短暂网络错误和速度限制响应.
- 一个将每个公告响应转换成一个一致的,安全的电子表格行.
- 一个页面编辑器,在第一次运行时创建标题,添加只有新版本,并跳过以前看到的
announcement_datetime价值观. - 适应性缩对于数十种货币/指标对的大量扫描.
- 完全自动更新的每日更新.
- 选项日历预先检查,避免在没有计划发布的日期中冗余的API调用.
下一步
-
扩大指标清单 查看全文目录 在
没有任何信息.
并添加与您的策略相关的对 (例如:
chf/gov_bond_10y没有人知道.eur/pmi没有人知道.gbp/employment没有. -
添加条件格式 突出行,其中 方向 现在是
Beat绿色和Miss红色使用SpreadsheetApp现在我已经ConditionalFormatRuleBuilder对于一眼看的信号读取. -
推送Slack或电子邮件的警报 在添加行后,使用
MailApp.sendEmail或是网上打电话.loadMacroData通知您的团队, 当高影响力打印到达时. -
追踪历史价值观 没有
宣布终点
现在我也接受了.
start_date现在我们要做什么?end_date参数在更长的日期范围内运行一次后填,并与实时料一起播种历史标签.