Валютная конвертация: контракт курса и воспроизводимый расчёт
Как закрепить источник, направление котировки, дату действия и версию курса, чтобы повторить финансовый пересчёт.
7 мин чтения·База знаний
Справочный материал. Контракты данных, архитектурные решения и ограничения применения. Источники приведены в материалах, где используются внешние основания.
Валютная конвертация отвечает на вопрос, как исходная сумма превратилась в сумму другой валюты. Для аудита и повторного расчёта нужно восстановить не только число курса, но и источник, назначение, валютную пару, временные срезы, версию и правило округления.
Курс сделки, справочная котировка и курс, используемый для конкретного отчёта, могут различаться. Выбор зависит от деловой цели и утверждённой политики. Эта статья описывает инженерную модель, а не учётное правило организации. Примеры синтетические. Статус knowledge не означает личный опыт автора или реализацию в лаборатории.
IAS 21 рассматривает учёт операций в иностранной валюте и перевод финансовой отчётности. Функциональная валюта связана с основной экономической средой организации. Валюта отчёта в DWH сама по себе не определяет функциональную валюту. Для выбора курса нужно установить конкретное требование применимой редакции стандарта и утверждённой политики. Модель хранения ниже является предложением для DWH, а не требованием IAS 21.
Зерно входа в примере - версия строки проводки: система-источник, юридическое лицо, книга учёта, документ и номер строки. Исходную сумму хранят вместе с валютой и масштабом. Для иллюстрации дебет положителен, кредит отрицателен; положительный коэффициент сохраняет знак. Нельзя складывать суммы разных валют до согласованной конвертации.
Зерно результата можно определить как версия строки, целевая валюта, назначение расчёта и версия запуска. Исходная и конвертированная суммы остаются отдельными показателями. Если источник уже поставляет сумму в валюте книги учёта, её хранят отдельно: расчёт DWH не должен молча заменять значение источника.
Временные атрибуты отвечают на разные вопросы:
business_date - дата финансового события и его периода;
rate_date - дата курса, выбранная по политике;
published_at - время публикации курса провайдером;
observed_at - когда конкретная версия стала доступна EDWH;
loaded_at - когда запись загрузили в хранилище.
Дата учётного события не обязана совпадать с датой курса. Для timestamp задают часовой пояс, для дат - календарь провайдера. Правило выбора курса должно быть явным.
Провайдер, временной ряд и назначение, например справочный или договорный курс
base_currency, quote_currency
Валюты, задающие направление котировки
base_units, quote_amount
Число основных единиц базовой валюты и соответствующая сумма котировки
rate_date, valid_from, valid_to
Дата наблюдения и заданный политикой интервал применимости
published_at, observed_at, revision_id
Публикация, доступность в EDWH и версия
artifact_id, policy_version
Сохранённый файл источника и версия правила выбора
Полуоткрытый интервал удобен для временной применимости: начало включается, конец исключается. Для дневного ряда правило переноса курса через нерабочий день является отдельным правилом потребителя, а не следствием наличия даты в файле. Бизнес-ключ наблюдения включает провайдера, ряд, тип, пару валют и дату. Исправление создаёт новую версию, не уничтожая предыдущую.
Европейский центральный банк публикует справочные курсы с EUR в качестве базовой валюты для информационных целей и прямо не рекомендует использовать их для транзакций. Это пример семантики конкретного провайдера, а не рекомендация выбирать курс ЕЦБ для любой учётной задачи. Доступный официальный курс может не подходить для договорной операции или регуляторного отчёта.
Для воспроизведения сохраняют полученный файл, его контрольную сумму и метаданные загрузки. Контрольная сумма помогает заметить изменение файла, но сама по себе не подтверждает надёжность провайдера. Ссылка на актуальную страницу не заменяет сохранённую версию данных.
Пусть провайдер указывает q единиц валюты B за u основных единиц валюты A. Нормализованный курс r = q / u имеет единицу B/A. Тогда сумма в B равна amount_A * r, а обратная конвертация требует деления на r. Код пары без единиц измерения не устраняет неоднозначность.
Синтетический пример: за 100 USD получено 92 EUR. Следовательно, r = 0.92 EUR/USD; 250 USD дают 230 EUR, а конвертация 230 EUR обратно даёт 250 USD до округления. Если ошибочно прочитать 92 как курс за один доллар, результат вырастет в 100 раз. Сначала сумму исходной валюты переводят из минимальных единиц в основные по сохранённому масштабу, затем применяют масштаб целевой валюты.
Для кросс-курса через валюту C, если r_A имеет единицу A/C, а r_B - B/C, то курс B/A равен r_B / r_A. Например, синтетические курсы 1.25 USD/EUR и 0.90 GBP/EUR дают 0.72 GBP/USD. У обеих котировок должны быть согласованы провайдер, тип и временной срез. Смешение дат создаёт другой показатель. Сохраняют версии обоих курсов и путь расчёта. Кросс-курс не обязательно совпадает с исполнимой рыночной котировкой.
Рабочая точность и округление разделяются. Правила округления и зерно разобраны в статье о денежной точности. Обратная конвертация уже округлённой суммы может не восстановить исходное значение.
Зафиксировать версии строк-источников, политику и cutoff доступности.
Для каждой строки определить целевую валюту и дату курса по политике.
Выбрать версии курсов, известные на заданный cutoff.
Разрешить версии одного наблюдения согласно контракту провайдера.
Требовать ровно одну применимую котировку или разрешённый путь.
Проверить единицы, знак, конечность значения и допустимый возраст курса.
Выполнить конвертацию без преждевременного округления.
Сохранить результат, сумму источника, версии курса и правило округления.
Проверить полноту строк и расчётные результаты.
Для одинаковых валют политика может определять тождественную конвертацию с коэффициентом 1 и явной причиной, без запроса к провайдеру. Если курс отсутствует, расчёт должен завершиться отказом либо получить статус неполного. Подстановка нуля недопустима. Перенос последнего доступного курса разрешён только утверждённым правилом с ограничением возраста и отдельным признаком. Пересекающиеся интервалы и несколько подходящих рядов разрешают до соединения с фактами: обычный JOIN может размножить строки и суммы.
Снимок «как было рассчитано» использует зафиксированные входы и версии курсов. Пересчёт с учётом исправлений создаёт новый запуск и сохраняет прошлый результат. Даже при одной дате курса позднее исправление меняет доступный набор версий. Общая модель временных срезов описана в статье об истории и времени.
Альтернативы - конвертировать при каждом запросе или сохранять результаты запусков. Первый способ сокращает производные записи, но требует закрепить версии для старого отчёта. Второй упрощает аудит, но увеличивает объём хранения и ответственность за пересчёты. В обоих случаях нужна трассировка результата и нельзя выбирать «самый новый курс» без временного cutoff.
Проверяют уникальность зерна результата, количество входных и выходных строк, наличие кодов валют и положительность и конечность применённого коэффициента. Каждая сумма должна ссылаться на исходную строку и использованные версии. Итоги сверяют по юридическому лицу, книге учёта, валюте и запуску. Баланс документа проверяют в исходной валюте; баланс после конвертации зависит также от зерна округления и политики разниц. Нельзя автоматически объявлять расхождение курсовой разницей.
До приёмки задают ожидаемое поведение для прямой и обратной котировки, курса за 100 единиц, отрицательной суммы, одинаковых валют, отсутствующего курса в выходной день, поздней корректировки и двух одновременно применимых курсов. Для кросс-курса отдельно проверяют согласованность версий компонентов. Повторный запуск с теми же входами и политиками должен дать тот же результат. Новый запуск с исправлением должен объяснять разницу изменившимися входами.
Эта модель не рассматривает отсутствие обменяемости, ограничения рынка, специальные договорные курсы или полный перевод финансовой отчётности. Для них нужны отдельные правила и оценка применимых требований. Умножение каждой строки на один курс не является универсальным алгоритмом IAS 21.
Зерно проводки раскрыто в статье о финансовых событиях, полнота набора - в материале о сверках. Правила точности и временных срезов разобраны в связанных статьях выше. Эта статья не заменяет их общим алгоритмом.
Какие единицы котировки нужно сохранять, чтобы исключить ошибку направления и масштаба?
Как воспроизвести отчёт, если провайдер исправил курс за ту же дату?
Что делать, если подходящего курса нет, а последняя котировка слишком старая?
Currency conversion in an EDWH must explain why a particular source amount produced a particular target-currency value and allow the calculation to be repeated after rates are corrected. Storing the rate number alone is insufficient: its purpose, quotation unit, and version are also needed.
This article proposes an engineering contract for converting journal lines for a report. It does not define an organization's accounting policy, balance revaluation, or recognition of exchange differences. All numerical examples are synthetic. The knowledge status indicates neither the author's experience nor a lab implementation.
IAS 21 governs accounting for foreign-currency transactions and translation of financial statements; functional currency relates to an entity's primary economic environment. Selecting a report currency in an EDWH does not itself determine functional currency. Establish the specific accounting rule for rates using the applicable edition of the standard and the approved policy. The storage model below is a design proposal, not an IAS 21 requirement.
The input grain is a journal-line version: source system, legal entity, ledger, document, and line number. Store the source amount with its currency and scale. In the illustration, debit is positive and credit is negative; a positive rate preserves that sign. Do not add amounts in different currencies before an agreed conversion.
The output grain is the input-line version, target currency, calculation purpose, and run version. Source and converted amounts remain separate measures. If the source already supplies an amount in ledger currency, retain it separately: an EDWH calculation must not silently replace it.
business_date determines the accounting period, rate_date is the rate date selected by policy, and observed_at is when the specific version became available in the EDWH. Retain the provider's publication timestamp and the load timestamp separately as well. The accounting date need not equal the rate date; specify the selection rule explicitly. Define a time zone for timestamps and the provider's calendar for dates.
Provider, series, and purpose, such as a reference or contractual rate
base_currency, quote_currency
The currencies defining the quotation direction
base_units, quote_amount
The number of base-currency major units to which the quoted value applies
rate_date, valid_from, valid_to
Observation date and policy-defined applicability interval
published_at, observed_at, revision_id
Publication, availability in the EDWH, and version
artifact_id, policy_version
Retained source file and selection-rule version
A half-open applicability interval is convenient: include the start and exclude the end. For a daily series, applicability and carrying a rate over a nonbusiness day are separate consumer rules, not consequences of a date appearing in a file. The rate business key includes provider, series, type, currency pair, and date; a correction creates a new version without destroying the previous one.
The ECB publishes reference rates with the euro as base currency for information purposes and explicitly discourages their use for transactions. This illustrates one provider's semantics; it is not a recommendation to choose ECB rates for every accounting purpose. An available official rate may still be unsuitable for a contractual transaction or regulatory report.
Archive the received file, its checksum, and retrieval metadata. A checksum helps detect file changes but does not itself establish the provider's trustworthiness. A link to a current page does not replace a retained data version.
Suppose a provider quotes q units of currency B for u major units of currency A. The normalized rate r = q / u has units B/A. Then amount_B = amount_A * r; conversion in the opposite direction requires division by r. A currency-pair code without units does not remove ambiguity.
Synthetic example: 100 USD cost 92 EUR. Thus r = 0.92 EUR/USD, 250 USD converts to 230 EUR, and converting 230 EUR back gives 250 USD before rounding. Treating 92 as a rate per dollar would inflate the result by a factor of 100. First convert source minor units into major units using the retained scale; apply the target-currency scale when producing the result.
For a cross rate through C, if r_A has units A/C and r_B has units B/C, the B/A rate is r_B / r_A. For example, synthetic rates of 1.25 USD/EUR and 0.90 GBP/EUR give 0.72 GBP/USD. Both component quotations must use an agreed provider, type, and time snapshot; arbitrarily mixing dates produces a different measure. Retain both rate versions and the conversion path. A cross rate need not equal an executable market quotation.
Rounding and working precision are separate policies covered in monetary precision. Converting a rounded result back need not recover the original amount. This article contains no executable SQL/Python examples and claims no database testing; the sequence below is pseudocode.
Pin input-line versions, the policy, and the availability cutoff.
For each line, determine the target currency and rate date by policy.
Select eligible rate versions known at the chosen cutoff.
Resolve versions of the same rate using the provider contract.
Require exactly one applicable quotation or an authorized path.
Validate units, positivity, finiteness, and permitted age.
Convert without premature rounding.
Save the result, source amount, rate versions, and rounding policy.
Check line completeness and calculation results.
For identical currencies, the policy can specify an identity conversion with a coefficient of 1 and an explicit reason, without consulting a provider. If a quotation is missing, block the calculation or explicitly mark it incomplete. Substituting zero is unacceptable. Carrying the last available rate forward is permitted only by an approved rule with an age limit and a carry-forward flag. Resolve overlapping intervals and competing series before joining rates to facts: an ordinary JOIN can multiply rows and amounts.
An "as originally calculated" snapshot uses pinned inputs and rate versions. A "recalculate with corrections" snapshot creates a new run with new versions while retaining the previous result. Even for the same rate date, a later correction changes the available set. The general snapshot model is covered in history and time; here it is applied to quotations.
Alternatives include conversion on every query and retaining run results. The former reduces derived records but requires pinning all versions to reproduce an old report; the latter simplifies audit at the cost of storage and recalculation management. Both require result lineage and must avoid using the "latest rate" without a time cutoff.
Check output-grain uniqueness, input and output row counts, the absence of unknown currencies, and rate positivity and finiteness. Every amount must reference its source line and the versions used. Reconcile totals by legal entity, ledger, currency, and run. Check document balance in the source currency; balance after conversion also depends on rounding grain and the policy for differences. Do not automatically classify a discrepancy as an exchange difference.
Before acceptance, define expected outcomes for direct and inverse quotations, quotations per 100 units, negative amounts, identical currencies, a missing rate on a weekend, a late correction, and two applicable rates. For cross rates, check consistency between component versions. Repeating a run with the same inputs and policies must produce the same result; a new run incorporating a correction must explain the difference through changed inputs.
This model does not cover lack of exchangeability, market restrictions, special contractual rates, or full financial-statement translation. These require separate policies and assessment of applicable requirements. Multiplying every line by one rate is not a universal IAS 21 algorithm.
Journal-line grain is covered in financial events, and completeness in reconciliation. Precision and time-snapshot rules are linked above; this article does not replace them with another general algorithm.
Which quotation units must be retained to prevent direction and scale errors?
How can a report be reproduced after the provider corrects a rate for the same date?
What should happen when no eligible rate exists and the last quotation is too old?