Назад к блогу

Конвертация валют в Power BI: курсы в реальном времени через API (руководство 2026)

V
Vlado Grigirov
August 27, 2026
Currency API Exchange Rates Power BI Power Query DAX Tutorial Finexly

Любое руководство по конвертации валют в Power BI относится к одному из двух лагерей. Лагерь DAX показывает изящную меру и негласно предполагает, что таблица ExchangeRate в вашей модели уже есть. Лагерь API показывает фрагмент Power Query, который прекрасно работает в Power BI Desktop и ломается в момент публикации, потому что запланированное обновление его не принимает.

Это руководство закрывает обе половины, по порядку: как забрать курсы валют в реальном времени в Power BI из REST API так, чтобы публикация в службе Power BI пережила это; как смоделировать курсы, чтобы ваши цифры можно было защитить; и как конвертировать суммы — на этапе импорта или на этапе запроса, в зависимости от того, что вашему отчёту действительно нужно.

Все примеры Power Query и DAX ниже написаны по документированным структурам ответов API Finexly.

Сначала определите, какая у вас задача конвертации

«Конвертация валют в Power BI» — это три разные инженерные задачи под одним названием, и выбрать не ту — самая дорогая ошибка во всей статье.

  1. Много исходных валют, одна валюта отчётности. В таблице продаж есть строки в EUR, GBP и JPY, а финансовому директору нужна одна цифра в USD. Конвертируйте на импорте. Курс — свойство транзакции, а не отчёта.
  2. Одна исходная валюта, много валют отчётности. Всё хранится в USD, а пользователь выбирает валюту отображения срезом. Конвертируйте на запросе, в DAX. Предрассчитывать каждую валюту непрактично.
  3. Много исходных валют, много валют отчётности. Приведите всё к одной опорной валюте на импорте, а затем наложите сверху случай 2. Не пытайтесь решить это одним шагом.

Правило, которое повторяют опытные моделировщики и которое стоит повторить ещё раз: конвертируйте так рано, как только можете себе позволить. Каждая конвертация, отложенная до момента запроса, берёт плату на каждом визуальном элементе, при каждой смене фильтра и каждом клике по срезу.

Как правильно забрать курсы в Power Query

Начните с таблицы курсов. Самый эффективный вызов — запрос по нескольким парам сразу: вы получаете все нужные валюты одним запросом вместо одного запроса на валюту.

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.finexly.com/v1/convert?q=USD_EUR,USD_GBP,USD_JPY"
{
  "USD_EUR": { "rate": 0.9215 },
  "USD_GBP": { "rate": 0.7892 }
}

Вот эквивалент в виде запроса M для Power Query. Создайте пустой запрос (Главная → Создать источник → Пустой запрос → Расширенный редактор) и вставьте:

let
    ApiKey = "YOUR_API_KEY",
    Pairs  = "USD_EUR,USD_GBP,USD_JPY,USD_CAD,USD_AUD,USD_CHF",
    Source = Json.Document(
        Web.Contents(
            "https://api.finexly.com",
            [
                RelativePath = "v1/convert",
                Query        = [ q = Pairs ],
                Headers      = [ #"Authorization" = "Bearer " & ApiKey ]
            ]
        )
    ),
    ToTable  = Record.ToTable(Source),
    Expanded = Table.ExpandRecordColumn(ToTable, "Value", {"rate"}, {"Rate"}),
    Split    = Table.SplitColumn(
                   Expanded, "Name",
                   Splitter.SplitTextByDelimiter("_", QuoteStyle.None),
                   {"BaseCurrency", "Currency"}
               ),
    Typed    = Table.TransformColumnTypes(
                   Split,
                   {{"BaseCurrency", type text}, {"Currency", type text}, {"Rate", type number}}
               ),
    Stamped  = Table.AddColumn(Typed, "RetrievedAt", each DateTimeZone.UtcNow(), type datetimezone)
in
    Stamped

Назовите его FxRates. Вы получите таблицу из четырёх столбцов — BaseCurrency, Currency, Rate, RetrievedAt — готовую к использованию в связях и мерах.

Ошибка Web.Contents, которая ломает запланированное обновление

Обратите внимание, чего запрос выше не делает: он никогда не склеивает параметры внутрь строки URL. Это самая частая причина, по которой отчёт с конвертацией валют работает на десктопе и умирает в облаке.

Если написать вот так:

// Do NOT do this
Source = Json.Document(
    Web.Contents("https://api.finexly.com/v1/convert?q=" & Pairs)
)

…Power BI Desktop обновит это без возражений, а служба Power BI откажет с сообщением «Этот набор данных содержит динамический источник данных. Обновление не поддерживается.» Службе нужно разрешить статический базовый URL на этапе анализа, чтобы привязать к нему учётные данные. Передача переменных частей через RelativePath и Query даёт ровно это: база остаётся https://api.finexly.com, а всё динамическое живёт в параметрах.

То же самое относится к любому запросу, который собирает URL из параметра, даты или значения из другой таблицы. Если вы вынесете из этой статьи одну вещь, пусть это будет RelativePath.

Как обращаться с ключом API, не зашивая его в код

ApiKey = "YOUR_API_KEY" прямо в коде годится для пятиминутного теста и не годится ни для чего общего. Код M семантической модели виден каждому, у кого есть разрешение на сборку.

Два рабочих варианта:

  • Параметр Power Query (Главная → Управление параметрами), на который ссылаются как ApiKey = KeyParam. Он по-прежнему хранится вместе с моделью, но он централизован, его легко ротировать, и его можно переопределять по средам через конвейеры развёртывания.
  • Тип учётных данных Web API. В службе Power BI откройте Параметры семантической модели → Учётные данные источника данных → Изменить учётные данные и выберите Веб-API, указав ключ там. Дальше служба сама подставляет заголовок Authorization, а параметр Headers из вашего M можно убрать полностью. Секрет остаётся вне определения модели.

Что бы вы ни выбрали, используйте ключ, ограниченный задачами отчётности, и меняйте его, когда кто-то уходит из команды. Если ключ окажется в строке запроса URL, а не в заголовке, он может попасть в журналы доступа прокси и серверов — документация Finexly отдельно об этом предупреждает, и это верно для любого поставщика.

Постройте измерение валют, а не просто список курсов

Одна лишь таблица курсов даёт цифры. Она не даёт ни корректных подписей, ни корректного числа знаков после запятой, ни среза с осмысленной сортировкой. Заберите список валют как отдельное измерение:

let
    ApiKey = "YOUR_API_KEY",
    Source = Json.Document(
        Web.Contents(
            "https://api.finexly.com",
            [
                RelativePath = "v1/currencies",
                Headers      = [ #"Authorization" = "Bearer " & ApiKey ]
            ]
        )
    ),
    ToTable = Table.FromList(Source, Splitter.SplitByNothing(), {"Currency"}),
    Typed   = Table.TransformColumnTypes(ToTable, {{"Currency", type text}})
in
    Typed

Затем добавьте два столбца, которые Power BI не может вывести за вас:

  • MinorUnits — сколько знаков после запятой валюта действительно использует. У JPY — 0, у KWD — 3, у большинства — 2. Отформатировать сумму в иенах с двумя знаками — заметная содержательная ошибка в финансовом отчёте, а округление не на том шаге её усугубляет. Руководство по округлению валют показывает, откуда берётся погрешность.
  • FormatString — например, "\€#,0.00", "\¥#,0". Понадобится дальше для динамических строк формата.

Оба столбца следуют стандарту ISO 4217, а не чему-то, что Power BI знает изначально; в справочнике ISO 4217 есть полная таблица. Отметьте эту таблицу как измерение, свяжите её с FxRates[Currency] как «один ко многим» и используйте именно её — а не таблицу курсов — источником для среза.

Путь A: конвертация на импорте (быстро, скучно, правильно)

Для сценария 1 — много валют на входе, одна на выходе — сделайте работу в Power Query и позвольте модели хранить одно чистое число.

  1. Загрузите запрос транзакций в Power Query.
  2. Главная → Объединить запросы, соединив Transactions[Currency] с FxRates[Currency] (левое внешнее).
  3. Разверните объединённый столбец и оставьте Rate.
  4. Добавить столбец → Настраиваемый столбец:
= if [Currency] = "USD" then [Amount]
  else if [Rate] = null then null
  else [Amount] / [Rate]

Обратите внимание на ветку null. Левое внешнее соединение с таблицей курсов, где какой-то валюты нет, даёт null, а null в арифметике Power Query молча даёт null вместо ошибки — это превращается в пустое значение в визуале и в итог, который незаметно занижен. Сделайте пробел явным, чтобы его можно было отфильтровать и увидеть.

Обратите внимание и на деление. USD_EUR = 0.9215 означает, что один USD покупает 0,9215 EUR, поэтому перевод суммы в EUR в USD — это деление. Перевод суммы в USD в EUR — умножение. Перепутать направление — вторая по частоте ошибка в мультивалютных отчётах, и при курсах около 1,0 она почти незаметна: погрешность в 3% в цифре EUR/USD выглядит как расхождение округления, пока кто-нибудь не начнёт сверку.

Путь B: конвертация на запросе в DAX

Для сценария 2 — одна валюта хранения и валюта отображения, выбираемая пользователем — конвертация должна происходить в мере.

Наивная версия делает LOOKUPVALUE на каждую строку и становится медленной уже после нескольких сотен тысяч строк. Сначала агрегируйте, потом конвертируйте один раз:

Sales (Reporting Currency) =
VAR SelectedCurrency = SELECTEDVALUE ( Currency[Currency], "USD" )
VAR Rate =
    CALCULATE (
        SELECTEDVALUE ( FxRates[Rate] ),
        FxRates[Currency] = SelectedCurrency
    )
VAR Result =
    IF (
        SelectedCurrency = "USD",
        [Sales Amount],
        [Sales Amount] * Rate
    )
RETURN
    IF ( ISBLANK ( Rate ) && SelectedCurrency <> "USD", BLANK (), Result )

Две детали, которые важнее, чем кажутся:

  • SELECTEDVALUE со значением по умолчанию. Без запасного "USD" мера возвращает пустоту всякий раз, когда валюта не выбрана, — а это ровно то состояние, в котором отчёт открывается.
  • Явная защита от пустоты. Если у валюты нет курса, возвращайте пустоту намеренно, а не позволяйте [Sales Amount] * BLANK() вернуть ноль. Ноль в карточке выручки — это ложь; пустота — видимый пробел.

Когда курс меняется со временем

Мера выше применяет один текущий курс ко всему набору данных. Это верно для вопроса «сколько стоила бы прошлогодняя выручка сегодня» и неверно почти для всего остального. Если в таблице курсов одна строка на валюту на день, сгруппируйте по дате перед конвертацией:

Sales (Historical Rates) =
SUMX (
    VALUES ( 'Date'[Date] ),
    VAR DayRate =
        CALCULATE (
            SELECTEDVALUE ( FxRates[Rate] ),
            FxRates[Currency] = SELECTEDVALUE ( Currency[Currency], "USD" )
        )
    RETURN
        [Sales Amount] * DayRate
)

Итерация по VALUES('Date'[Date]), а не по таблице фактов, держит итератор маленьким — это дни, а не транзакции.

Динамические строки формата

Сконвертированное число с жёстко зашитым префиксом $ хуже, чем вообще без символа. В Power BI задайте формат меры как динамический и передайте выражение:

SELECTEDVALUE ( Currency[FormatString], "#,0.00" )

Теперь карточка, показывающая ¥, покажет ¥ без десятичных знаков и без второй меры. Раньше для этого требовались группы вычислений в Analysis Services; динамические строки формата для мер принесли это в сам Power BI.

Какой курс вам на самом деле нужен

Это вопрос, отделяющий дашборд от отчёта, который подпишет финансовая служба, и ни один API не ответит на него за вас.

  • Спот-курс на дату транзакции — для учёта отдельных операций. Максимальная точность, самая большая таблица курсов.
  • Среднее за месяц или период — стандарт для статей отчёта о прибылях и убытках как по МСФО (IAS) 21, так и по ASC 830. Сглаживает внутримесячную волатильность и используется в большинстве консолидаций.
  • Курс на конец периода — для статей баланса: денежные средства, дебиторская и кредиторская задолженность.
  • Бюджетный или плановый курс — фиксированный курс на весь год, чтобы анализ отклонений отделял операционные результаты от валютного движения.

Серьёзной модели часто нужны два-три таких курса одновременно — как отдельные столбцы одной таблицы (SpotRate, AverageRate, ClosingRate), а не как разные таблицы. Если ваш отчёт питает что-то, что попадает в отчётность, руководство по курсам и налоговой отчётности разбирает, какой источник и какую метку времени вы должны уметь обосновать, а руководство по историческим курсам — как получать курсы на дату, а не текущие.

Выходные, из-за которых итоги тихо ломаются

Валютные рынки закрываются. В ежедневной таблице курсов, построенной из живого API, нет ни субботы, ни воскресенья, ни 25 декабря. Соедините транзакцию, датированную субботой, с такой таблицей — получите null, null станет пустотой, а пустота превратится в итог, заниженный ровно на выходные продажи.

Чините это в таблице курсов, а не в мере. Сгенерируйте полный список дат и заполните вниз:

let
    Dates = List.Dates(#date(2026,1,1), Duration.Days(Date.From(DateTime.LocalNow()) - #date(2026,1,1)) + 1, #duration(1,0,0,0)),
    DateTable = Table.FromList(Dates, Splitter.SplitByNothing(), {"Date"}),
    Typed = Table.TransformColumnTypes(DateTable, {{"Date", type date}}),
    Joined = Table.NestedJoin(Typed, {"Date"}, RateHistory, {"Date"}, "r", JoinKind.LeftOuter),
    Expanded = Table.ExpandTableColumn(Joined, "r", {"Currency", "Rate"}),
    Filled = Table.FillDown(Expanded, {"Currency", "Rate"})
in
    Filled

Table.FillDown переносит пятничный курс через выходные — это общепринятая трактовка и, что важнее, трактовка заявленная, а не случайная. Отсортируйте по валюте и дате перед заполнением, иначе через пробел перенесётся курс другой валюты.

Если ваш тариф не включает исторические эндпоинты, историю можно наращивать вперёд: при каждом обновлении дописывайте сегодняшние курсы в сохранённую таблицу — подойдут поток данных или таблица Fabric Lakehouse — и через квартал у вас будет настоящий временной ряд. Это не задним числом, но стоит одного вызова API в день.

Расписание обновлений и арифметика квоты

Power BI Pro допускает 8 запланированных обновлений в день для семантической модели; ёмкости Premium и Fabric — 48. Это и есть число, которое должна покрыть ваша квота API, и арифметика тут добрее, чем принято думать.

Таблица курсов выше — это два вызова на обновление: один к /v1/currencies, один к /v1/convert. Итого:

Частота обновленияОбновлений в месяцВызовов API в месяцТариф Finexly
8 в день (максимум Pro)~240~480Бесплатный (1 000/мес)
48 в день (Fabric, каждые 30 мин)~1 440~2 880Starter
48 в день + почасовая история в потоке данных~2 160~4 320Growth
Бесплатный тариф — 1 000 запросов в месяц с потолком 10 запросов в минуту, чего с запасом хватает рабочей области Pro, обновляющейся на максимальной частоте. Актуальные лимиты и доступность исторических данных — на странице тарифов.

Потолок в 10 в минуту — вот за чем стоит следить. Если вы соберёте запрос, вызывающий /v1/rate по разу на валюту внутри Table.AddColumn, двадцать валют дадут двадцать вызовов за пару секунд и серию ответов 429 посреди обновления. Именно поэтому и существует многопарный вызов /v1/convert. Группируйте и кэшируйте: руководство по кэшированию и обработке ошибок описывает схемы повторов с экспоненциальной задержкой, которые в равной мере применимы к запланированному обновлению.

Шлюзы, Excel и Fabric

Несколько замечаний по среде, каждое из которых экономит полдня:

  • Шлюз не нужен. Облачный REST API — не локальный источник, поэтому локальный шлюз данных для этого не требуется. Если обновление падает и кто-то предлагает поставить шлюз, это почти всегда ошибка динамического источника в другом обличье.
  • Excel использует тот же движок. Power Query в Excel принимает ровно тот же M. Если ваша аудитория живёт в книгах, а не в дашбордах, руководство по курсам в Excel разбирает WEBSERVICE, LAMBDA и матрицу версий; есть и аналог для Google Таблиц.
  • Fabric Dataflow Gen2 — лучшее место для таблицы курсов, как только она нужна более чем одному отчёту. Загрузите курсы один раз, дайте всем семантическим моделям читать одну и ту же таблицу — и расход API перестанет расти вместе с числом отчётов.
  • Сверьтесь с известной цифрой перед публикацией. Возьмите одну пару в конвертере валют и сравните с тем, что показывает ваша модель для той же пары на тот же момент. Если расходятся — у вас проблема направления или округления, и найти её лучше сейчас, а не на заседании совета директоров.

Часто задаваемые вопросы

Может ли Power BI конвертировать валюты без API? Может, если курсы предоставите вы: таблица, которую ведут вручную, выгрузка из финансовой системы или представление в базе данных. Встроенного источника курсов в Power BI нет. API нужен тогда, когда курсы должны обновляться без того, чтобы кто-то помнил об этом.

Почему отчёт с конвертацией обновляется в Power BI Desktop, но падает в службе? Почти всегда из-за ошибки динамического источника данных. Ваш вызов Web.Contents собирает URL склейкой строк. Перенесите переменные части в параметры RelativePath и Query, чтобы базовый URL был статическим, опубликуйте заново и введите учётные данные снова.

Где конвертировать — в Power Query или в DAX? В Power Query, если у отчёта одна валюта отчётности: быстрее и проще. В DAX, если пользователь выбирает валюту во время работы. Если нужно и то и другое — приведите всё к опорной валюте в Power Query, а меру DAX положите сверху.

Сколько запросов к API тратит одно обновление Power BI? Два, если вы объединяете все пары в один вызов /v1/convert. При максимуме Power BI Pro в 8 обновлений в день это около 480 запросов в месяц — в пределах бесплатного тарифа. Дорого становится, только если вызывать API по разу на валюту или по разу на строку.

Как обрабатывать выходные и праздники в ежедневной таблице курсов? Сформируйте непрерывную таблицу дат, присоедините курсы левым соединением, отсортируйте по валюте и дате и заполните вниз. Пятничный курс переносится на выходные. Важно, чтобы такая трактовка была осознанной и задокументированной, а не чтобы строки молча исчезали.

Какой курс использовать для финансовой отчётности? Средние курсы периода — для статей отчёта о прибылях и убытках, курсы на конец периода — для статей баланса, и по IAS 21, и по ASC 830. Храните их как отдельные столбцы одной таблицы курсов, чтобы отчёт переключался между ними без изменения модели.


Готовы поставить живые курсы за своими дашбордами? Получите бесплатный ключ API Finexly — без банковской карты. Начните с 1 000 запросов в месяц: этого достаточно, чтобы обновлять рабочую область Power BI Pro на максимальной частоте, — и переходите на старший тариф, когда понадобятся исторические данные или более частое обновление. Если вы всё ещё сравниваете поставщиков, страница сравнения показывает варианты рядом.

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 →