ores.marketdata.fx_forward_publish

Table of Contents

The detail row of one marketdata.fx_forward_publish approval request: the FX forward set the request authorises, where each of its numbers came from, and the record of the act once the set is published.

A forward set is derived data, so it belongs to a party, as a funding curve does. The subject is the party, the pair and the as-of instant. The set is computed when the request is raised, so the person who decides reads the numbers they approve, and the apply publishes exactly those numbers. The tenors are the fx_forward_publish_point rows of the publish, and the spot observations it read are its fx_forward_publish_spot rows: one for a USD pair, two for a cross.

The provenance is on this row: the publish of each funding curve the set discounts with, the version of the pair's convention, the calendars its dates roll on, and the method that combined them. Each names its own record, so a reader follows a number back to the market and to the curves. A curve publish is named and not copied, so the withdrawal of a curve shows on every set built from it without a write here. The apply does not check the curves again: the decider approved those numbers, and a curve withdrawn after the raise shows on the set as withdrawn rather than stopping it.

published_at and published_by are the publication record, empty while the request waits and written in one act when the approved set is published. request_id is the natural key, so a request authorises one publish.

1. Flags

2. Columns

2.1. id

Surrogate UUID identifying this row.

2.2. request_id

The approval request this record is the subject of.

ctx.generate_uuid()

2.3. party_id

The party the set belongs to. Two parties that publish the same pair at the same instant publish two sets, each from its own curves.

ctx.generate_uuid()

2.4. pair_code

The currency pair, in the form of its convention, such as EUR/USD.

std::string("EUR/USD")

2.5. as_of

The instant the set reads the market at: the latest spot at or before it, and the funding curves published at or before it.

ctx.past_timepoint()

2.6. spot_date

The spot date the forwards are measured from, rolled on the pair's calendars.

std::chrono::year_month_day{std::chrono::year{2026}, std::chrono::month{11}, std::chrono::day{11}}

2.7. spot_rate

The spot rate of the pair the set used. For a cross it is derived from the two USD legs its spot rows name.

1.1

2.8. base_curve_publish_id

The curve_publish of the base currency's funding curve the set discounts with.

ctx.generate_uuid()

2.9. quote_curve_publish_id

The curve_publish of the quote currency's funding curve the set discounts with.

ctx.generate_uuid()

2.10. convention_version

The version of the pair's currency_pair_convention the set read its spot lag, roll and pip factor from.

1

2.11. calendar_codes

The calendars the dates roll on, comma separated, in the order the convention names them.

std::string("TARGET,US")

2.12. method_code

How the set combined its inputs. covered_interest_parity is F = S x DF_base / DF_quote, each discount factor measured from the spot date.

std::string("covered_interest_parity")

2.13. published_by

The username of the account that applied the request, or empty while it waits.

std::string("")

2.14. published_at

When the set was published, or null while the request waits.

std::nullopt

3. SQL

3.1. Flags

The record is tenant-scoped, as an IR curve publish is: the decider of a publish is a tenant function, so the subject names its party instead of hiding behind it.

3.2. Checks

expression
("published_at" is null) = ("published_by" = '')
"as_of" > '1970-01-01 00:00:00+00'::timestamp with time zone
"spot_rate" > 0
"pair_code" <> ''

3.3. Indexes

name columns unique current_only
fx_forward_publishes_party_pair tenant_id, party_id, pair_code, as_of desc false true

4. Foreign keys

4.1. request_id

The request the record is the subject of. Its own id is the one the publish call names, so a record that names a request the store does not hold authorises nothing.

#include "ores.iam.api/generators/account_generator.hpp"
#include "ores.iam.core/repository/account_repository.hpp"
#include "ores.inbox.api/generators/approval_request_generator.hpp"
#include "ores.inbox.core/repository/approval_kind_repository.hpp"
#include "ores.inbox.core/repository/approval_request_repository.hpp"
#include "ores.inbox.core/repository/approval_request_state_repository.hpp"
#include "ores.utility/uuid/tenant_id.hpp"
{
    auto asker = ores::iam::generators::generate_synthetic_account(ctx);
    asker.account_type = "service";
    asker.change_reason_code = "system.test";
    ores::iam::repository::account_repository asker_repo;
    asker_repo.write(party_ctx, asker);

    auto request = ores::inbox::generators::generate_synthetic_approval_request(ctx);
    request.change_reason_code = "system.test";
    request.requested_by = asker.id;

    const auto system_ctx =
        party_ctx.with_tenant(ores::utility::uuid::tenant_id::system(), h.db_user());
    const auto kinds =
        ores::inbox::repository::approval_kind_repository{}.read_latest(system_ctx);
    REQUIRE_FALSE(kinds.empty());
    request.kind_code = kinds.front().code;
    const auto states =
        ores::inbox::repository::approval_request_state_repository{}.read_latest(system_ctx);
    REQUIRE_FALSE(states.empty());
    request.state_code = states.front().code;

    ores::inbox::repository::approval_request_repository request_repo;
    request_repo.write(party_ctx, request);
    v.request_id = request.id;
}

4.2. party_id

5. C++

5.1. Flags

5.2. Repository

5.2.1. read_latest_by_request

Reads the record of one request, if the store holds one. The gate asks here.

std::optional<domain::fx_forward_publish>
read_latest_by_request(context ctx, const std::string& request_id);
#include <optional>
#include <string>
std::optional<domain::fx_forward_publish>
fx_forward_publish_repository::read_latest_by_request(context ctx, const std::string& request_id) {
    static constexpr auto max = db_timestamp::max();
    const auto tid = ctx.tenant_id().to_string();
    const auto query = query_builder::read<std::vector<fx_forward_publish_entity>> |
                       query_builder::where("tenant_id"_c == tid && "request_id"_c == request_id &&
                                            "valid_to"_c == max);
    auto rows = query_builder::execute_read_query<fx_forward_publish_entity, domain::fx_forward_publish>(
        ctx,
        query,
        [](const auto& entities) { return fx_forward_publish_mapper::map(entities); },
        lg(),
        "Reading the FX forward publish of a request.");
    if (rows.empty())
        return std::nullopt;
    return rows.front();
}

5.2.2. read_published_for_pair

Every published set of one party and pair, newest as-of first. A reader at an as-of takes the first one at or before it.

std::vector<domain::fx_forward_publish>
read_published_for_pair(context ctx, const boost::uuids::uuid& party_id,
                        const std::string& pair_code);
#include <algorithm>
#include <string>
#include <vector>
#include <boost/uuid/uuid_io.hpp>
std::vector<domain::fx_forward_publish>
fx_forward_publish_repository::read_published_for_pair(context ctx,
                                                       const boost::uuids::uuid& party_id,
                                                       const std::string& pair_code) {
    static constexpr auto max = db_timestamp::max();
    const auto tid = ctx.tenant_id().to_string();
    const auto pid = boost::uuids::to_string(party_id);
    const auto query = query_builder::read<std::vector<fx_forward_publish_entity>> |
                       query_builder::where("tenant_id"_c == tid && "party_id"_c == pid &&
                                            "pair_code"_c == pair_code && "valid_to"_c == max);
    auto rows = query_builder::execute_read_query<fx_forward_publish_entity, domain::fx_forward_publish>(
        ctx,
        query,
        [](const auto& entities) { return fx_forward_publish_mapper::map(entities); },
        lg(),
        "Reading the published FX forward sets of a pair.");
    std::erase_if(rows, [](const auto& r) { return !r.published_at.has_value(); });
    // Newest as-of first; between two sets of one as-of, the one published last.
    std::ranges::sort(rows, [](const auto& a, const auto& b) {
        if (a.as_of != b.as_of)
            return a.as_of > b.as_of;
        return *a.published_at > *b.published_at;
    });
    return rows;
}

5.3. Domain includes

#include <chrono>
#include <optional>
#include <string>
#include <boost/uuid/uuid.hpp>

5.4. Entity includes

#include <optional>
#include <string>

5.5. Conventions

5.6. Table display

column header
pair_code Pair
as_of As Of
published_by Published By
published_at Published At

6. See also

Emacs 29.3 (Org mode 9.6.15)