ores.refdata.currency_pair
Table of Contents
Currency pair identity: base/quote legs, deliverability, classification,
and fixing source. Conventions (pip factor, tick size, calendars,
business day convention) live 1:1 in
ores.refdata.currency_pair_convention, matching the codebase's
existing *_convention entity family. spot_days, calendars, and G11
membership are derived at read time from the two legs, not stored
here — see Currency pair support in reference data for the full design
rationale.
Flags
Columns
pair_code
Canonical base/quote pair code, e.g. "EUR/USD", always stored in canonical base-currency-precedence order.
std::string(faker::finance::currencyCode()) + "/" + std::string(faker::finance::currencyCode())
base_currency
Base (first) leg of the pair. Soft FK to currency.iso_code.
std::string("EUR")
quote_currency
Quote (second) leg of the pair. Soft FK to currency.iso_code.
std::string("USD")
classification
Liquidity classification; soft FK to
ores_refdata_currency_pair_classifications_tbl (e.g. "major", "minor",
"exotic"). Free-text pending a working dynamic-combo mechanism for
aux-type reference data (tracked separately; out of scope for this
entity — see Currency pair support in reference data).
std::string("major")
SQL
Flags
Extra drops
drop function if exists ores_refdata_validate_currency_pair_fn;
Foreign keys
base_currency
quote_currency
classification
Validation function
Insert trigger
Validations
| column | validation_function |
|---|---|
| base_currency | ores_refdata_validate_currency_fn |
| quote_currency | ores_refdata_validate_currency_fn |
| classification | ores_refdata_validate_currency_pair_classification_fn |
| change_reason_code | ores_dq_validate_change_reason_fn |
C++
Flags
Repository
Domain includes
#include <chrono> #include <optional> #include <string>
Entity includes
#include <optional> #include <string> #include "sqlgen/Timestamp.hpp"
Conventions
Table display
| column | header |
|---|---|
| pair_code | Pair |
| base_currency | Base |
| quote_currency | Quote |
| classification | Classification |
| modified_by | Modified By |
| version | Version |
Qt
Columns (Qt model)
| enum_name | field | header | type | width | is_badge | badge_key | formatter | formatter_include |
|---|---|---|---|---|---|---|---|---|
| PairCode | pair_code | Pair | string | 100 | ||||
| BaseCurrency | base_currency | Base | string | 80 | ||||
| QuoteCurrency | quote_currency | Quote | string | 80 | ||||
| Classification | classification | Classification | string | 100 | true | currency_pair_classification | ||
| Version | version | Version | int | 70 | ||||
| ModifiedBy | modified_by | Modified By | string | auto | ||||
| RecordedAt | recorded_at | Recorded At | timestamp | auto |
Icon columns (Qt model)
Composites base+quote flags into the PairCode column's decoration
(same cell as the "EUR/USD" text), matching the FX Spot grid's
existing hand-written pattern (see currency_flag_icon() in
FlagIconHelper.hpp).
| column | accessor | field1 | field2 |
|---|---|---|---|
| PairCode | currency_flag_icon | base_currency | quote_currency |
| BaseCurrency | currency_flag_icon | base_currency | |
| QuoteCurrency | currency_flag_icon | quote_currency |
Detail fields
| field | label | widget | type | is_key | is_required | placeholder | flag_source | combo_fetch | combo_fetch_include | combo_allow_blank | combo_values | badge_key | immutable |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| pair_code | Pair Code | pairCodeEdit | line_edit | true | true | e.g. EUR/USD | |||||||
| base_currency | Base Currency | baseCurrencyCombo | flagged_combo | true | currency | fetch_currency_codes | ores.qt/LookupFetcher.hpp | false | true | ||||
| quote_currency | Quote Currency | quoteCurrencyCombo | flagged_combo | true | currency | fetch_currency_codes | ores.qt/LookupFetcher.hpp | false | true | ||||
| classification | Classification | classificationCombo | static_combo | major,minor,exotic,commodity | currency_pair_classification |
Custom repository methods
Qt: derived pair_code
pair_code is base_currency + "/" + quote_currency, not independent
user input — kept live-synced while both are still editable (create
mode only; once created they're locked and pair_code never changes
again, per Pair code doesn't recompute when base/quote currency
changes). Base/Quote lock via WidgetUtils::set_combo_locked()
rather than the generated setEnabled(false), so the flag icon stays
visible while locked rather than fading out — see same task.
Deliverability (physical vs. cash/NDF settlement, and the settlement
currency for the latter) was modelled here as a deliverable=/
=non_deliverable boolean, then dropped entirely — real FX conventions
allow multiple settlement types per pair (sometimes business-centre
dependent), which a single boolean/optional-currency pair on
currency_pair can't express; needs a proper settlement_type
junction model, tracked as a future story (see the capture in the
inbox).
void updatePairCodeFromCurrencies();
connect(ui_->baseCurrencyCombo, &QComboBox::currentTextChanged, this, &CurrencyPairDetailDialog::updatePairCodeFromCurrencies); connect(ui_->quoteCurrencyCombo, &QComboBox::currentTextChanged, this, &CurrencyPairDetailDialog::updatePairCodeFromCurrencies);
WidgetUtils::set_combo_locked(ui_->baseCurrencyCombo, !createMode); WidgetUtils::set_combo_locked(ui_->quoteCurrencyCombo, !createMode); // pair_code is always derived (see updatePairCodeFromCurrencies()), // never independently typed -- the generated locked_fields loop above // left it as setReadOnly(!createMode) (is_key's normal, editable-at- // create behaviour), which is wrong for a field that's never directly // entered even at create time. ui_->pairCodeEdit->setReadOnly(true);
WidgetUtils::set_combo_locked(ui_->baseCurrencyCombo, true); WidgetUtils::set_combo_locked(ui_->quoteCurrencyCombo, true);
&& ui_->baseCurrencyCombo->currentText() != ui_->quoteCurrencyCombo->currentText()
void CurrencyPairDetailDialog::updatePairCodeFromCurrencies() { if (!createMode_) return; ui_->pairCodeEdit->setText( ui_->baseCurrencyCombo->currentText() + "/" + ui_->quoteCurrencyCombo->currentText()); }
Qt: reject duplicate pair_code on create
The SQL insert trigger treats a colliding pair_code as the next
version of the existing row (upsert-by-natural-key, the same
pattern every bitemporal entity uses) rather than rejecting it — the
trigger can't distinguish "genuine create" from "republish/upsert"
without breaking the DQ→refdata publish flow, which relies on that
exact semantics for its own re-versioning. So the check has to live
client-side, scoped to just the interactive Add flow — see
Creating a Currency Pair with an already-used pair_code silently
versions the existing row.
Synchronous (not QtConcurrent-wrapped) — a brief UI-thread block for
a quick pre-save check, same as other synchronous
process_authenticated_request call sites elsewhere in the app (e.g.
SystemSettingController::onRevertClicked). Requests a generously
high limit explicitly: the default (100) would silently miss
existing pairs beyond the first page and defeat the check.
if (createMode_) { // pair_.pair_code isn't synced from the UI until updatePairFromUi() // runs, later in this function -- read the live combo-derived value // directly rather than the (still-stale/empty) domain object. const QString baseCode = ui_->baseCurrencyCombo->currentText(); const QString quoteCode = ui_->quoteCurrencyCombo->currentText(); const std::string derivedPairCode = ui_->pairCodeEdit->text().trimmed().toStdString(); const std::string invertedPairCode = (quoteCode + "/" + baseCode).toStdString(); refdata::messaging::get_currency_pairs_request checkRequest; checkRequest.limit = lookup_fetch_limit; auto checkResult = clientManager_->process_authenticated_request(std::move(checkRequest)); if (!checkResult) { // Fail closed: the whole point of this check is to prevent a // silent overwrite, so if we can't even ask the server whether // the pair already exists, don't let the save through blind. MessageBoxHelper::warning(this, "Cannot Verify", "Could not check for an existing pair before saving. Please try again."); return; } const bool exists = std::ranges::any_of(checkResult->pairs, [&](const auto& p) { return p.pair_code == derivedPairCode; }); if (exists) { MessageBoxHelper::warning(this, "Duplicate Pair", QString("A currency pair '%1' already exists.") .arg(QString::fromStdString(derivedPairCode))); return; } // FX market convention quotes each pair in one canonical // direction only -- EUR/USD and USD/EUR are not independent // pairs, they're the same market inverted. Reject the inverse // rather than silently allowing both to coexist. const bool inverseExists = std::ranges::any_of(checkResult->pairs, [&](const auto& p) { return p.pair_code == invertedPairCode; }); if (inverseExists) { MessageBoxHelper::warning(this, "Inverted Pair", QString("'%1' is the inverse of the existing pair '%2'. FX market " "convention allows only one direction per currency pair.\n\n" "To use '%1' instead, delete '%2' first, then create '%1'.") .arg(QString::fromStdString(derivedPairCode)) .arg(QString::fromStdString(invertedPairCode))); return; } }
Qt: Conventions toolbar button
Cross-navigation from the Currency Pairs list window to the Currency
Pair Conventions list window (ores.refdata.currency_pair_convention),
via a "Conventions" toolbar button. Signal declared on the MdiWindow
and relayed through the Controller, mirroring the generated
show{{...}}History plumbing.
void showConventionsRequested();
{
toolbar_->addSeparator();
auto* conventionsAction = toolbar_->addAction(
IconUtils::createRecoloredIcon(Icon::Tag, IconUtils::DefaultIconColor), tr("Conventions"));
conventionsAction->setToolTip(tr("Open Currency Pair Conventions"));
connect(conventionsAction, &QAction::triggered, this,
&CurrencyPairMdiWindow::showConventionsRequested);
}
void showConventionsRequested();
connect(listWindow_, &CurrencyPairMdiWindow::showConventionsRequested, this, &CurrencyPairController::showConventionsRequested);
See also
- ores.refdata — component group overview.
- Currency pair support in reference data — parent story.
- ores.refdata.currency_pair_convention — 1:1 convention entity.