Как получать актуальные курсы валют в Google Sheets (GOOGLEFINANCE, Apps Script и валютный API)
Получение актуальных курсов валют в Google Sheets выглядит как давно решённая задача. Вы вводите =GOOGLEFINANCE("CURRENCY:USDEUR"), появляется число — и можно двигаться дальше. Для личного бюджета на поездку этого вполне достаточно. Но всё ломается в тот момент, когда таблица начинает питать счёт-фактуру, расчёт зарплаты, отчёт о выручке или что угодно, на основании чего коллега примет решение, — из-за лицензионного ограничения, задокументированной особенности с датами и ограничения Apps Script, о котором почти не пишут в руководствах.
Это руководство разбирает все три рабочих метода — встроенную функцию GOOGLEFINANCE, пользовательскую функцию на Apps Script поверх валютного API и таблицу курсов, обновляемую по расписанию через триггер, — с реальными цифрами по квотам, точными формулировками оговорок самой Google и работающим кодом. Каждое утверждение о поведении Google ниже процитировано из официальной документации Google, а всё, что в ней не задокументировано, помечено отдельно.
Три способа получить курсы валют в Google Sheets
GOOGLEFINANCE | Пользовательская функция Apps Script | Таблица курсов по расписанию | |
|---|---|---|---|
| Время настройки | Секунды | ~10 минут | ~20 минут |
| Нужен ключ API | Нет | Да | Да |
| Обновление | Автоматически, при пересчёте | При пересчёте | По триггеру, который вы контролируете |
| Исторические курсы | Да (с оговорками) | Зависит от вашего тарифа | Да |
| Работает в Apps Script / Sheets API | Исторические данные: нет | Да | Да |
| Коммерческое / профессиональное использование | Ограничено условиями Google | Регулируется условиями вашего API-провайдера | Регулируется условиями вашего API-провайдера |
| Лучше всего для | Быстрых справок, личных таблиц | Разовых колонок с конвертацией | Всего, от чего зависит кто-то ещё |
Метод 1: GOOGLEFINANCE — и что именно говорит о нём Google
Базовая формула для валют
Схема, которой учит любое руководство, — тикер CURRENCY:, составленный из двух склеенных кодов валют ISO 4217:
=GOOGLEFINANCE("CURRENCY:USDEUR")Чтобы конвертировать сумму из ячейки A2:
=A2 * GOOGLEFINANCE("CURRENCY:USDEUR")Чтобы собрать пару из двух ячеек с кодами валют:
=GOOGLEFINANCE("CURRENCY:" & B2 & C2)Честная оговорка, которой нет ни в одной конкурирующей статье: форма тикера CURRENCY:XXXYYY не встречается нигде в официальной документации Google по GOOGLEFINANCE. Задокументированная сигнатура — GOOGLEFINANCE(ticker, [attribute], [start_date], [end_date|num_days], [interval]), а всё, что справка говорит о валютах, — это атрибут "currency" (валюта, в которой котируется бумага) и пример с графиком. Синтаксис пары CURRENCY: работает и работает годами, но это поведение, устоявшееся в сообществе, а не задокументированное обязательство, — то есть Google вправе изменить его без всякого уведомления об устаревании. Google также не публикует список поддерживаемых валютных пар, поэтому любая статья, уверяющая, что поддерживается «около 50 валют», просто гадает.
Исторические курсы: правило атрибутов, которое все понимают неправильно
Передайте дату — и получите исторические данные:
=GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,15))=GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,1), DATE(2026,1,31), "DAILY")Обратите внимание на атрибут. Google формулирует это прямо: «Если указан хотя бы один параметр даты, запрос считается историческим, и допускаются только исторические атрибуты». Исторические атрибуты — это open, close, high, low, volume и all, и "price" среди них нет. "price" задокументирован только как атрибут реального времени («Котировка в реальном времени с задержкой до 20 минут»).
Откройте прямо сейчас руководства по валютам в Google Sheets из топа выдачи: большинство из них подсовывает вам GOOGLEFINANCE("CURRENCY:USDEUR", "price", DATE(...)). Эта комбинация противоречит собственной документации Google. Используйте "close".
Ещё два задокументированных поведения, которые стоит усвоить:
- «Если
start_dateуказан, аend_date|num_days— нет, возвращаются данные только за один день». - «Результаты в реальном времени возвращаются как значение в одной ячейке. Исторические данные, даже за один день, возвращаются как развёрнутый массив с заголовками столбцов». Именно поэтому внешне корректная историческая формула выливает в вашу аккуратную вёрстку два столбца и строку заголовков, и именно поэтому её обычно приходится оборачивать в
INDEX(...,2,2).
Сдвиг на полдень по UTC
Эта особенность тихо портит отчёты, привязанные к датам. Дословно от Google:
«Google трактует даты, переданные в GOOGLEFINANCE, как полдень по UTC. Биржи, закрывающиеся раньше этого времени, могут оказаться сдвинуты на день».
Если вы сверяете реестр, где каждая строка должна использовать курс на дату проводки, сдвиг на день — это реальный разрыв в сверке, а не вопрос округления. Если это про вашу таблицу, прочитайте наше руководство по курсам валют и налоговой отчётности, прежде чем доверять развёрнутому массиву GOOGLEFINANCE роль аудиторской записи.
Задержка — 3 минуты, а не 20
Почти каждая статья на эту тему уверяет, что курсы валют в GOOGLEFINANCE «задерживаются до 20 минут». Эта цифра в 20 минут взята из общего дисклеймера по котировкам ценных бумаг, относящегося к атрибуту "price". На собственной странице дисклеймера Google Finance опубликована таблица задержек по классам активов, и строка Currency — глобально, поставщик Morningstar — это 3 минуты. Для криптовалют тоже 3 минуты.
То есть данные свежее, чем принято считать в интернете. А вот чего Google не публикует — так это того, как часто Sheets пересчитывает формулу, а именно эта цифра вас на самом деле и волнует: курс трёхминутной свежести, лежащий в ячейке, которая не пересчитывалась со вторника, — это по-прежнему курс вторника. Кто называет конкретный интервал пересчёта, тот цитирует то, чего Google никогда не документировала.
Для сравнения: Finexly обновляет курсы каждую минуту и отдаёт их по запросу, так что свежесть зависит от того, когда обращаетесь вы, а не от того, когда таблица решит пересчитаться. Если хотите разобраться, что вообще означает «актуальный» у разных провайдеров, посмотрите, откуда API курсов валют берут данные.
Ограничение на использование, которое никто не цитирует
Это самый важный абзац в статье, и он не встречается ни в одном руководстве из топа выдачи. Со страницы справки Google по GOOGLEFINANCE, дословно:
«Ограничения использования: данные не предназначены для профессионального использования в финансовой отрасли или для использования другими специалистами в нефинансовых организациях (включая государственные структуры). Профессиональное использование может потребовать дополнительных лицензионных платежей стороннему поставщику данных».
И из дисклеймера Google Finance:
«Вы обязуетесь не копировать, не изменять, не переформатировать, не загружать, не хранить, не воспроизводить, не перерабатывать, не передавать и не распространять любые размещённые здесь данные или сведения, а также не использовать такие данные или сведения в коммерческом предприятии без предварительного письменного согласия».
«Google не может гарантировать точность отображаемых курсов валют. Вам следует уточнить текущие курсы, прежде чем совершать любые операции, на которые могут повлиять изменения курсов».
Сопоставьте это с тем, что ваша таблица делает на самом деле. Расчёт суммы счёта для клиента, конвертация счетов поставщиков для бухгалтерии или выгрузка пересчитанной выручки в отчёт, который вы отправляете клиенту, — это явно не «информационные цели». Лицензированный API курсов валют снимает вопрос целиком, и именно это, а вовсе не точность как таковая, — реальная причина, по которой финансовые команды уходят от GOOGLEFINANCE.
И жёсткий блокер
«Исторические данные нельзя скачать или получить через Sheets API либо Apps Script. При попытке сделать это вы увидите ошибку #N/A вместо значений в соответствующих ячейках таблицы».
Как только вы захотите что-то автоматизировать — ночную выгрузку, обращение через Sheets API, скрипт, снимающий срез курсов, — исторические данные GOOGLEFINANCE оказываются недоступны по замыслу. Именно эта стена и толкает людей к методу 2.
Метод 2: пользовательская функция Apps Script поверх валютного API
Пользовательская функция — это JavaScript-функция в Apps Script, которую вы вызываете из ячейки, как любую встроенную. Google прямо подтверждает, что она работает с веб-запросами: пользовательские функции «могут вызывать только сервисы, у которых нет доступа к персональным данным», и URL Fetch входит в разрешённый список.
Шаг 1: получите ключ API и храните его правильно
Возьмите бесплатный ключ в панели Finexly — 1000 запросов в месяц, без банковской карты. И вот что важно: не зашивайте его в код. Два самых цитируемых руководства по валютам в Google Sheets вставляют ключ API прямо в скрипт — в файл, который путешествует вместе с таблицей ко всем, с кем вы ею поделитесь.
В редакторе Apps Script (Расширения → Apps Script) один раз запустите вот это из редактора, чтобы сохранить ключ в свойствах скрипта:
function storeApiKey() {
PropertiesService.getScriptProperties()
.setProperty('FINEXLY_API_KEY', 'YOUR_API_KEY');
}После этого удалите литерал из файла. Учтите задокументированные лимиты: значение свойства ограничено 9 КБ, а всё хранилище свойств — 500 КБ; для ключа с запасом, но не место для кеширования данных.
Шаг 2: пользовательская функция для одной пары
/**
* Returns the live exchange rate for a currency pair.
*
* @param {string} from Base currency code, e.g. "USD".
* @param {string} to Quote currency code, e.g. "EUR".
* @return The exchange rate.
* @customfunction
*/
function FX_RATE(from, to) {
if (!from || !to) throw new Error('Both currency codes are required.');
var pair = String(from).toUpperCase() + '_' + String(to).toUpperCase();
var cache = CacheService.getScriptCache();
var hit = cache.get(pair);
if (hit !== null) return Number(hit);
var key = PropertiesService.getScriptProperties().getProperty('FINEXLY_API_KEY');
var res = UrlFetchApp.fetch(
'https://api.finexly.com/v1/rate?from=' + encodeURIComponent(from) +
'&to=' + encodeURIComponent(to),
{
headers: { Authorization: 'Bearer ' + key },
muteHttpExceptions: true
}
);
var code = res.getResponseCode();
var body = JSON.parse(res.getContentText());
if (code !== 200) {
throw new Error(body.error ? body.error.code + ': ' + body.error.message : 'HTTP ' + code);
}
cache.put(pair, String(body.rate), 300); // 5 minutes
return body.rate;
}Использование в ячейке:
=FX_RATE("USD","EUR")
=A2 * FX_RATE($B$1, $C$1)Эндпоинт /v1/rate возвращает {"pair": "USD_EUR", "rate": 0.9215}. Полное описание параметров — в документации API Finexly.
Шаг 3: обработайте диапазон пакетно — то, что упускают все конкуренты
Вот сценарий отказа. Google описывает его прямо:
«Каждый раз, когда пользовательская функция используется в таблице, Sheets делает отдельный вызов к серверу Apps Script. Если ваша таблица содержит десятки (или сотни, или тысячи!) вызовов пользовательских функций, этот процесс может быть медленным».
Протяните FX_RATE вниз на 400 строк — и вы сделали 400 обращений к серверу. С лимитом в 30 секунд на одно выполнение пользовательской функции вы получите #ERROR! с пометкой Exceeded maximum execution time (line 0). задолго до того, как упрётесь в квоту API.
Решение, которое рекомендует Google, — пакетная обработка массивов: принимайте диапазон, возвращайте массив. Эндпоинт /v1/convert у Finexly принимает пары через запятую, так что весь столбец превращается в один HTTP-запрос:
/**
* Converts a column of amounts from one currency to another in a single API call.
*
* @param {A2:A400} amounts Range of amounts.
* @param {string} from Base currency code.
* @param {string} to Quote currency code.
* @return {Array} Converted amounts.
* @customfunction
*/
function FX_CONVERT_RANGE(amounts, from, to) {
var rate = FX_RATE(from, to); // one fetch, then cached
var rows = Array.isArray(amounts) ? amounts : [[amounts]];
return rows.map(function (row) {
return row.map(function (v) {
return (v === '' || v === null) ? '' : Number(v) * rate;
});
});
}=FX_CONVERT_RANGE(A2:A400, "USD", "EUR")Одна формула, один сетевой вызов, на 399 обращений меньше. Если нужно несколько пар сразу, обратитесь к /v1/convert?q=USD_EUR,USD_GBP,USD_JPY и читайте body["USD_EUR"].rate.
Что Google на самом деле говорит о кешировании в пользовательских функциях
Выше используется CacheService, но важно точно понимать зачем. Руководство Google по пользовательским функциям оценивает сервис Cache как «работает, но не особенно полезен в пользовательских функциях». Выигрыш от кеширования реален только между отдельными выполнениями — при повторных пересчётах одной и той же пары, — а не внутри одного развёрнутого массива. Задокументированная оптимизация — это пакетная обработка массивов; кеширование полезно вторично.
Задокументированные лимиты кеша: ключи до 250 символов, значения до 100 КБ, потолок в 1000 элементов и срок жизни от 1 секунды до 21 600 секунд (6 часов), по умолчанию 600 секунд. Дневная квота на обращения к кешу не публикуется.
Ещё одна задокументированная ловушка — потому что этот обходной приём широко разошёлся: добавление NOW() в качестве аргумента для принудительного обновления ломает функцию. Google: «Аргументы пользовательских функций должны быть детерминированными… Если пользовательская функция пытается вернуть значение на основе одной из таких изменчивых встроенных функций, она бесконечно показывает Loading...».
Метод 3: таблица курсов по расписанию (продакшен-паттерн)
Пользовательские функции пересчитываются тогда, когда Sheets сочтёт нужным, а это ровно то свойство, которое не нужно таблице, которую читает кто-то ещё. Надёжный паттерн — вообще перестать ходить в сеть из ячеек: записывать курсы в таблицу по расписанию, а затем подтягивать их обычными формулами.
Скрипт обновления
var PAIRS = ['USD_EUR', 'USD_GBP', 'USD_JPY', 'USD_CAD', 'USD_AUD', 'USD_CHF'];
function refreshRates() {
var key = PropertiesService.getScriptProperties().getProperty('FINEXLY_API_KEY');
var res = UrlFetchApp.fetch(
'https://api.finexly.com/v1/convert?q=' + PAIRS.join(','),
{ headers: { Authorization: 'Bearer ' + key }, muteHttpExceptions: true }
);
if (res.getResponseCode() !== 200) {
console.error('Finexly refresh failed: ' + res.getContentText());
return; // keep yesterday's rates rather than blanking the sheet
}
var data = JSON.parse(res.getContentText());
var stamp = new Date();
var rows = PAIRS.map(function (p) {
return [p, p.split('_')[0], p.split('_')[1], data[p].rate, stamp];
});
var sheet = SpreadsheetApp.getActive().getSheetByName('Rates') ||
SpreadsheetApp.getActive().insertSheet('Rates');
sheet.clear();
sheet.getRange(1, 1, 1, 5)
.setValues([['Pair', 'From', 'To', 'Rate', 'Updated (UTC)']])
.setFontWeight('bold');
sheet.getRange(2, 1, rows.length, 5).setValues(rows);
}Две детали, которые отличают этот скрипт от тех, что вы найдёте в других местах. Во-первых, один вызов setValues(), а не по одному на ячейку: пакетная запись диапазона работает драматически быстрее. Во-вторых, неудачный запрос завершает функцию, а не очищает таблицу, так что сбой у провайдера оставляет устаревшие, но помеченные курсы вместо листа с пустыми ячейками, который молча обнуляет все итоги ниже по цепочке.
Создание триггера
function installTrigger() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === 'refreshRates') ScriptApp.deleteTrigger(t);
});
ScriptApp.newTrigger('refreshRates').timeBased().everyHours(1).create();
}Google документирует, что триггеры по времени запускаются «как часто раз в минуту, так и редко — раз в месяц», и что момент срабатывания намеренно размывается: «если вы создадите повторяющийся триггер на 9:00, Apps Script выберет время между 9:00 и 10:00». Если вы используете everyMinutes(n), то n должно быть 1, 5, 10, 15 или 30 — другие значения не принимаются.
Чтение курсов
=XLOOKUP("USD_EUR", Rates!A:A, Rates!D:D)
=A2 * XLOOKUP($B$1 & "_" & $C$1, Rates!A:A, Rates!D:D)Теперь любая конвертация в книге мгновенно вычисляется из локальных ячеек, использует один согласованный курс и несёт видимую отметку времени. Именно про последний пункт и спрашивают аудиторы.
Квоты и лимиты, в которые вы действительно упрётесь
Это опубликованные Google цифры по Apps Script — их стоит знать, прежде чем проектировать вокруг них:
| Лимит | Личный аккаунт (gmail.com) | Аккаунт Google Workspace |
|---|---|---|
| Вызовы URL Fetch | 20 000 / день | 100 000 / день |
| Суммарное время работы триггеров | 90 мин / день | 6 ч / день |
| Чтение/запись свойств | 50 000 / день | 500 000 / день |
| Время работы пользовательской функции | 30 сек / выполнение | 30 сек / выполнение |
| Время работы скрипта | 6 мин / выполнение | 6 мин / выполнение |
| Триггеров на пользователя на скрипт | 20 | 20 |
| Длина URL в URL Fetch | 2 КБ / вызов | 2 КБ / вызов |
?q= — с запасом больше сотни, но не бесконечно.Со стороны API бесплатный тариф Finexly даёт 1000 запросов в месяц при 10 запросах в минуту. Ежечасный триггер тратит примерно 730 запросов в месяц — укладывается в бесплатный тариф с запасом. Добавьте горстку разовых вызовов пользовательских функций — и, возможно, захочется тариф Starter; актуальные уровни есть на странице тарифов. Учтите, что исторические курсы требуют платного тарифа, так что если вам нужны курсы задним числом и нулевой бюджет, GOOGLEFINANCE остаётся честным ответом для личного, непрофессионального использования.
Каждый ответ несёт заголовки X-RateLimit-Limit, X-RateLimit-Used и X-RateLimit-Units, так что вы можете логировать расход из res.getAllHeaders() и видеть приближение лимита.
Частые ошибки и как их исправить
| Что вы видите | Причина | Как исправить |
|---|---|---|
#N/A от GOOGLEFINANCE | Неподдерживаемая пара или опечатка в ней либо запрос исторических данных через Apps Script / Sheets API | Проверьте оба кода ISO; для автоматизации используйте API |
#ERROR! с «Exceeded maximum execution time (line 0).» | Слишком много отдельных вызовов пользовательской функции | Перейдите на пакетный паттерн FX_CONVERT_RANGE |
Бесконечное Loading... | В аргумент пользовательской функции передана NOW(), RAND() или другая изменчивая функция | Уберите её — Google документирует это как неподдерживаемое |
401 UNAUTHORIZED / invalid token | Ключ API отсутствует или неверен | Убедитесь, что свойство скрипта задано и заголовок содержит Bearer <key> |
429 RATE_LIMIT_EXCEEDED | Достигнут поминутный или месячный лимит | Увеличьте TTL кеша, упакуйте больше пар в один вызов или перейдите на старший тариф |
You do not have permission to call X service. | Пользовательская функция вызвала сервис, требующий авторизации | Перенесите эту логику в функцию, запускаемую по триггеру |
| Историческая формула выливает лишние столбцы | Задокументированное поведение — исторические результаты возвращаются массивом с заголовками | Оберните в INDEX(..., 2, 2) |
Какой метод выбрать?
- Личная таблица, быстрая справка, деньги не на кону:
GOOGLEFINANCE. Бесплатно, мгновенно, а лицензионное ограничение вас не касается. - Колонка конвертации в рабочей таблице: пакетная пользовательская функция Apps Script. Предсказуемый источник данных, никакой лицензионной двусмысленности, один вызов на диапазон.
- Всё, что читает коллега, клиент или аудитор: таблица курсов по расписанию. Один курс на цикл обновления, видимая отметка времени и никакой зависимости от того, когда Sheets решит пересчитаться.
Работаете вместо этого в Excel? Эквивалентные методы — Power Query, WEBSERVICE и тип данных «Валюты» — разобраны в статье как получать актуальные курсы валют в Excel. Если вы всё ещё выбираете провайдера, наше сравнение валютных API раскладывает различия по полочкам, а перепроверить любой курс можно в конвертере валют Finexly.
Часто задаваемые вопросы
Как бесплатно получать актуальные курсы валют в Google Sheets?
=GOOGLEFINANCE("CURRENCY:USDEUR") ничего не стоит и не требует настройки. Помните про заявленное Google ограничение использования — данные «не предназначены для профессионального использования в финансовой отрасли или для использования другими специалистами в нефинансовых организациях» — и про то, что исторические значения нельзя прочитать через Apps Script или Sheets API. В качестве лицензированной альтернативы за ноль рублей: бесплатный тариф API на 1000 запросов в месяц с запасом покрывает ежечасное обновление (около 730 вызовов).
Как часто GOOGLEFINANCE обновляет курсы валют?
В таблице дисклеймера Google Finance для валютных данных указана задержка в 3 минуты, источник — Morningstar, а вовсе не 20 минут, которые повсеместно повторяют в интернете; 20 минут — это общая цифра по котировкам ценных бумаг, привязанная к атрибуту "price". Отдельно: Google никогда не публиковала, как часто Sheets пересчитывает формулу, поэтому на возраст числа в вашей ячейке полагаться нельзя.
Можно ли использовать GOOGLEFINANCE внутри Apps Script?
Для исторических данных — нет. Google заявляет: «Исторические данные нельзя скачать или получить через Sheets API либо Apps Script. При попытке сделать это вы увидите ошибку #N/A». Кроме того, метода GOOGLEFINANCE нет ни в одном сервисе Apps Script — скрипт может только прочитать значение, которое ячейка уже вычислила. Если курсы нужны в коде, обращайтесь к валютному API через UrlFetchApp.
Почему моя валютная формула в Google Sheets тормозит или показывает #ERROR!?
Каждый вызов пользовательской функции — отдельное обращение к серверу Apps Script, и каждое выполнение ограничено 30 секундами. Сотни отдельных вызовов упрутся в таймаут. Принимайте диапазон и возвращайте массив, чтобы один вызов покрывал весь столбец, кешируйте курс, а всё, что идёт по расписанию, переносите из ячейки в триггер по времени.
Можно ли получить исторические курсы валют в Google Sheets?
Да, двумя способами. GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,1), DATE(2026,1,31), "DAILY") возвращает дневной ряд — используйте "close", поскольку "price" не является допустимым историческим атрибутом, и помните, что Google трактует даты как полдень по UTC, из-за чего значение может сдвинуться на день. Либо получайте исторические курсы из API на платном тарифе и записывайте их в лист по триггеру — это единственный путь, который переживает автоматизацию.
Готовы положить в свою таблицу лицензированные курсы валют с минутной свежестью? Получите бесплатный ключ API Finexly — банковская карта не нужна. Начните с 1000 запросов в месяц по 170+ валютам и переходите на старший тариф, когда ваши таблицы это перерастут.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →