GitHub – Interest-Rate-Decision/google-sheets: Central bank interest rates in Google Sheets: =POLICY_RATE (“fed”), =NEXT_DECISION (“ecb”), =RATE_HISTORY (“boe”), =ODDS (“fed …
Custom functions for Google Sheets that put the policy rate of ten central banks, the time of their next decision, every decision since 2008, odds from the record and a position size calculator in a cell. No account, no API key, no add-on to install: make a copy of the template, or paste one file into Apps Script.
=POLICY_RATE("fed") the Fed's rate today, in percent
=NEXT_DECISION("ecb") the date and time of the ECB's next decision
=RATE_HISTORY("boe") a table: date, time, rate, change in basis points
=ODDS("fed") a table: meeting, hike, hold, cut
=POSITION_SIZE(10000, 1, 20, "EURUSD", "USD") 0.5 (lots)
The banks: fed, ecb, boe, boj, boc, rba, rbnz, snb, norges, riksbank. A currency works too ("USD", "EUR", "JPY"…), and so does a name ("Bank of England", "FOMC").
The template (easiest). Open the template and choose File → Make a copy. The copy has the functions and these tabs: Rates, Next, History, Odds, Position size and Sources. (The owner publishes the template link: see “For the owner” below.)
In your own sheet. Extensions → Apps Script, delete what is there, paste src/Code.gs, save. Back in the sheet, type =POLICY_RATE("fed"). Reload the sheet for the Interest rates menu, which builds the template’s tabs (Build the template) or only the Sources tab.
The first time a function fetches, Google asks you to allow the script to “connect to an external service”. That is the fetch of the public data files listed below; the script reads nothing else and sends nothing.
| Function | Returns | Source |
|---|---|---|
POLICY_RATE(bank, [field]) |
The rate in percent (the Fed: the top of its range). field: low, name, ratename, lastchange, nextdecision, page |
/data/rates.json |
ALL_RATES() |
Every bank: name, rate name, rate, last change, next decision, page | /data/rates.json |
NEXT_DECISION([bank], [field]) |
The time of the next decision, shown in the sheet’s time zone. No bank: the next of any bank. field: bank, name, page, ratebefore, pressconference, tentative, days |
/data/next.json, then rates.json |
UPCOMING_DECISIONS([n]) |
The decisions of the coming weeks | /data/next.json |
RATE_HISTORY(bank, [since], [movesOnly]) |
Every decision since 2008, newest first: date, time (UTC), rate, change (bp). since: a date; movesOnly: TRUE for hikes and cuts |
/data/decisions.csv |
ODDS(bank, [outcome], [meeting]) |
Hike, hold and cut odds for the next meetings, 0 to 1 (format as %). outcome: hike, hold, cut, lower, higher for one number; meeting: 1 for the next, 2 for the one after |
/data/odds.json |
POSITION_SIZE(balance, riskPct, stopPips, pair, accountCcy, [rate]) |
Lots, rounded down to 0.01 | Worked out in the sheet |
COT_POSITION(currency, [field]) |
Large speculators’ (non-commercial) position: report date, net contracts, change on the week, net % of open interest, open interest. field: net, net_change_1w, net_pct_oi, open_interest, report_date for one value. Positions only: Forexia’s COT index is on the COT report |
ForexiaPro/cot-data data/latest.json (CFTC) |
LAST_SIGNAL(pair) |
The latest TradingSignalBot signal for the pair | tradingsignalbot.com/data/signals.json (not yet published) |
Each function’s help (the box Sheets shows while you type it) names its source.
Why not =RATE() and =COT()? Sheets already has built-in RATE (a loan’s interest rate) and COT (cotangent), and a custom function cannot take a built-in’s name. So the rate is POLICY_RATE and the positioning is COT_POSITION.
ODDS reads InterestRateDecision.com’s own odds: how often decisions like the coming one went each way, by fixed rules that are published with their whole record on the rate odds page. They are not market odds: nothing is taken from futures prices. If odds.json cannot be read, the function says “not yet available” and links the bank’s odds page rather than showing an error.
A standard lot is 100,000 units; a pip is 0.01 on yen pairs and 0.0001 on the others. The last argument is only needed when the account currency is not the pair’s quote currency:
| Account | Pair | rate |
|---|---|---|
| USD | EURUSD, GBPUSD… | not needed |
| USD | USDJPY, USDCAD… (account = base) | the pair’s price, e.g. 150.2 |
| GBP | EURUSD (cross) | one USD in GBP, e.g. 0.79 |
Metals, indices and crypto have other contract sizes: the function is for currency pairs only.
COT_POSITION and LAST_SIGNAL read feeds that are not public yet. Until they are, they return a sentence saying so, with a link to where the data is meanwhile. When the feeds go live, the functions start working with no change to the sheet. The URLs are in CONFIG at the top of Code.gs.
A signal is not advice, and TradingSignalBot’s record counts its losses.
Each fetched file is kept in the script’s cache: rates for 30 minutes, the calendar 15, the history 6 hours, the odds an hour. If the site cannot be reached, the last good copy is used. Interest rates → Fetch fresh data clears the cache; Sheets recalculates when you next edit or reopen.
Sheets also remembers a custom function’s answer for the same arguments. To force a new answer, change an argument, or add a cell such as =NOW() as an unused last argument.
- InterestRateDecision.com’s data (rates, dates, decisions, odds): free to reuse with a link to interestratedecision.com. The Sources tab carries that link; keep it when you share a sheet. The open data is described on the central bank data page.
- The decision history has no consensus figures in it: those are the calendar provider’s, not ours to redistribute.
- CFTC Commitments of Traders data is public domain.
- The code: MIT (LICENSE).
npm test # node:test, with UrlFetchApp and CacheService mocked (no network)
npm i -g @google/clasp
clasp login
cp .clasp.json.example .clasp.json # and paste the template's script id
npm run push # sends src/ to the template's Apps Script project
test/harness.mjs runs Code.gs in a Node sandbox with mocked Apps Script services; test/fixtures/ holds copies of the live files, so the tests check the real shapes. Refresh them with:
curl -s https://interestratedecision.com/data/rates.json -o test/fixtures/rates.json
curl -s https://interestratedecision.com/data/next.json -o test/fixtures/next.json
The “Make a copy” template
- Create a new Google Sheet in the account that will own the template, named “Central bank rates (InterestRateDecision.com)”.
- Extensions → Apps Script, paste
src/Code.gs; in Project settings tick “Show appsscript.json” and pastesrc/appsscript.json. Save. (Or use clasp, as above, with this project’s script id.) - Back in the sheet, reload, run Interest rates → Build the template, and allow the script when asked.
- Delete the empty “Sheet1”. Check each tab shows figures.
- Share → General access → Anyone with the link → Viewer.
- Take the sheet’s URL and replace
/edit...with/copy. That link opens “Make a copy” directly: put it at the top of this README, on IRD’s/central-bank-data/page and indocs/outreach.md. - Optionally submit it to the Google Sheets template gallery for Workspace (only within an organization) or list it on template sites; the
/copylink is the one to share.
Workspace Marketplace (later)
An editor add-on can carry these same custom functions, so people install once instead of copying. The steps:
- Create a Google Cloud project, link the Apps Script project to it (Project settings → Google Cloud Platform project).
- Configure the OAuth consent screen. The scopes are in
appsscript.json:script.external_request,spreadsheets.currentonlyandscript.container.ui. Keeping tocurrentonlyavoids the restricted-scope review. Google’s verification still needs a privacy policy URL and a home page: use IRD’s/privacy/and this repository. - Deploy → New deployment → Add-on, then enable the Google Workspace Marketplace SDK in the Cloud project and fill in the store listing (icons 32 and 128 px, screenshots, the description, support and terms links).
- Submit for review.
urlFetchWhitelistinappsscript.jsonlists the hosts fetched; add-ons must declare them. - In an add-on,
onOpenneedscreateAddonMenu()in place ofcreateMenu(), and anonInstall(e) { onOpen(e); }: change those two lines when it is time.
Part of the Forexia network: InterestRateDecision.com · Forexia Academy · TradingSignalBot · PropSim · OpenCharts