Skip to main content
Reference
DraftLast reviewed 2026-06-23

Conventions: snake_case, system date, FX

JSON casing

RuleDetail
Wire formatJackson is globally configured with PropertyNamingStrategies.SNAKE_CASE
AffectsAll DTO/record fields, both request and response bodies — targetStatus becomes target_status, indexName becomes index_name
Does not affectRaw Map<String, Object> keys — Jackson's naming strategy only renames declared fields, never map keys
warning

If you build a request body as a raw Map (common in scripts, tests, and the trms CLI's --details flag), you must supply snake_case keys yourself. Map.of("targetStatus", "confirmed") silently sends a body where the API sees no target_status field, fails validation, and returns 400 — there is no error pointing at the casing mismatch.

Money and decimals

TypeRule
Amounts (notional, P&L, market value, FX rates)BigDecimal everywhere — never double/float
Wire representationNumeric JSON value (not a string), e.g. "notional": 10000000
CLI shorthandThe trms CLI accepts suffixes K/M/B for notional (e.g. 10M, 1.5B, 500K) and expands them to BigDecimal before sending

Identifiers

TypeRule
Entity IDs (deals, books, counterparties, approvals, ...)UUID, serialized as a string
OptionalityPublic APIs return Optional<T> internally and null/absent fields on the wire — never throw for "not found" inside a service method

System date and time

OpenTRMS distinguishes three time concepts that must not be collapsed (see the backend's SystemDate_Convention.md):

ConceptSourceUse for
business_datedomain.system_params.business_date, read via BusinessClock.businessDate()EOD sequencing, daily eligibility, date-bucketed projections
system_timestamp()BusinessClock.systemTimestamp() — non-overrideable wall clockrecorded_at, booked_at, and other facts about when TRMS observed or wrote something
trade_timestampCaller-supplied execution time on deal captureStored as received; trade_date is derived from it in UTC and never overwritten on late booking

Rules:

  • Application code never calls LocalDate.now(), Instant.now(), or Clock.system*() directly — always go through BusinessClock.
  • Bitemporal projection/audit tables carry both as_of_date (business-effective date for the row version) and recorded_at (when TRMS wrote that version).
  • EOD events use the EOD business date as as_of_date, not wall-clock CURRENT_DATE. See End of day.

FX quoting

OpenTRMS stores one spot curve per base currency, not per pair:

RuleDetail
Curve namingFX_SPOT.{BASE} — e.g. FX_SPOT.USD
Grid point labelA counter-currency ISO code (e.g. EUR, BRL, JPY)
Grid point valueCounter per 1 baseFX_SPOT.USD / EUR = 0.92 means 1 USD = 0.92 EUR
InvalidPair-named curves such as FX_EURUSD or FX_USDEUR
ResolutionAll inversion and cross-currency triangulation happens inside FxRateService; a non-base-to-non-base conversion is a single hop through the system base currency, e.g. EUR -> JPY = (EUR -> USD) * (USD -> JPY)
Base currencysystem.base_currency is mandatory runtime configuration (a deployment choice, not a code default); demo/local config sets it to USD
Missing dataFxRateService fails loud with FxRateUnavailableException — no silent 1.0 fallback, no reverse-pair-name fallback, no last-good fallback
Reporting basePer-legal-entity reporting base is sourced from parties.base_currency when a caller supplies that context

Valuators and accounting code never construct FX curve names directly — they go through FxRateService. Forward-curve conventions and bid/ask handling are intentionally out of scope for the current FX model.

  • Scopes & entitlements — the authorization model these conventions sit alongside
  • Data dictionary — where business_date, as_of_date, and recorded_at appear as actual columns
  • End of day — the main consumer of business_date sequencing