13. Treasury Systems Architecture
Earlier chapters described what treasury does: managing liquidity, funding, interest rate and FX risk, and moving money. This chapter covers the software that does this work. It describes the main system families and how they fit together, the data architecture that keeps trades, positions, reference data and market data consistent, the processing models that turn that data into positions, valuations and risk figures in batch and in real time, and the reconciliation and control mechanisms that let a bank trust the numbers. For an engineer, this is the map of the territory: almost every change you make touches one of these systems and one of these data flows.
<a id="131-system-landscape"></a>
13.1 System Landscape
This subchapter covers the main families of systems in a bank's treasury landscape: what each family is responsible for, the leading products, how they exchange data, and how banks decide whether to buy a package or build their own.
The diagram below is the reference picture for the whole chapter. Every topic in this subchapter describes one of its boxes.
+------------------------------------------------+
Market data ------>| Front office |
(Bloomberg, LSEG, | Trading / TMS front end (Murex, Calypso, ...) |
brokers, venues) | pricing, deal capture, pre-trade limits |
+---------------------+--------------------------+
| trades (events)
+-------------------------------+-----------------------------+
| | |
+---------v---------+ +----------v-----------+ +----------v----------+
| Middle office | | Back office | | Risk engines |
| P&L, limits, | | confirmations, | | VaR, XVA, PFE, |
| IPV, valuations | | settlement, payments | | sensitivities |
+---------+---------+ +----------+-----------+ +----------+----------+
| | |
| +----------v-----------+ |
| | Payment hub / Swift | |
| | gateway (Ch. 12) | |
| +----------+-----------+ |
| | |
+---------v-------------------------------v------------------+ |
| Core banking system: customer accounts, loans, deposits, | |
| nostro/vostro accounts, the bank's own ledgers | |
+---------+--------------------------------------------------+ |
| balance sheet positions (loans, deposits, securities) |
+---------v--------------------------------------------------+ |
| ALM and liquidity engines: IRRBB, NII/EVE, LCR, NSFR, |<---------+
| cash flow ladders, FTP |
+---------+--------------------------------------------------+
|
+---------v--------------------------------------------------+
| Finance and regulatory reporting: subledger, GL, COREP, |
| FINREP, liquidity returns (Ch. 9, Ch. 14) |
+------------------------------------------------------------+
Treasury Management Systems (Murex, Calypso, FIS, Kyriba)
Why it matters. A treasury desk makes many decisions a day: it lends overnight cash, buys a bond for the liquidity buffer, hedges an FX exposure with a forward, and swaps fixed-rate funding to floating. Each trade has to be priced, captured, checked against limits, confirmed, settled, valued every day, accounted for and reported to risk. In the 1990s these steps lived in separate systems joined by spreadsheets and overnight files, and every handover was a chance for errors. A treasury management system (TMS) brings these steps together in one platform with one trade record. In a bank, these platforms are usually called "trading and risk platforms" or "front-to-back systems". In a corporate, a TMS is a lighter tool focused on cash visibility, payments and hedging.
What a TMS does. Most bank-grade TMS packages cover the same functional chain:
| Layer | Functions | Typical users |
|---|---|---|
| Front office | Pricing, deal capture, position and P&L views, pre-trade limit checks | Traders, treasury desk |
| Middle office | End-of-day valuation, P&L, independent price verification, limit monitoring, market risk | Product control, market risk |
| Back office | Confirmation generation and matching, settlement instructions, payments, nostro reconciliation | Operations |
| Accounting | Accounting events and postings to a subledger (Chapter 14) | Finance |
| Risk | Sensitivities, VaR, counterparty exposure, sometimes ALM | Risk management, ALM |
The main products. The market has consolidated, and each vendor has a typical customer profile.
| Product | Vendor and positioning | Typical users |
|---|---|---|
| Murex MX.3 | Murex (France). Cross-asset front-to-back-to-risk platform with deep derivatives coverage, including XVA, collateral and regulatory market risk (FRTB) modules | Large and mid-size banks: capital markets and treasury desks |
| Calypso | Now part of Adenza, owned by Nasdaq since 2023. Cross-asset trading, collateral and risk platform with strong post-trade and clearing coverage | Banks, central banks, clearing houses |
| FIS | Several products: Quantum and Integrity (corporate treasury), Front Arena (capital markets), Balance Sheet Manager (ALM) | Corporates, banks, asset managers |
| Kyriba | Cloud (SaaS) treasury platform: cash visibility, cash forecasting, payments, bank connectivity, FX hedging | Corporate treasuries |
| Others | ION (Wallstreet Suite, Openlink, Reval), SAP Treasury and Risk Management, Finastra (Kondor, Summit) | Banks and corporates |
The line between "bank TMS" and "corporate TMS" is about scale and depth, not concept. A bank's TMS has to price exotic derivatives and produce regulatory risk measures. A corporate TMS has to connect to dozens of banks and give the group treasurer a reliable cash position every morning (Chapter 10.1).
Architecture of a typical package.
+----------------------------------------------------------------+
| Presentation: trader GUI, blotters, web screens, reports |
+----------------------------------------------------------------+
| Workflow engine: trade status, confirmation, settlement and |
| payment states, approvals (maker-checker), exception queues |
+----------------------------------------------------------------+
| Pricing and risk library: curves, models, sensitivities |
+----------------------------------------------------------------+
| Batch / EOD processing: revaluation, accruals, accounting, |
| reports |
+----------------------------------------------------------------+
| Database: trades, events, static data, market data, results |
+----------------------------------------------------------------+
| Interfaces: market data in, trades in/out, confirmations, |
| payments, GL postings, risk feeds (files, MQ, APIs) |
+----------------------------------------------------------------+
The interface layer is where most bank engineering effort goes. A TMS is never alone: it receives market data, static data and sometimes trades from electronic platforms, and it feeds payments, accounting, risk, ALM and regulatory reporting.
Engineering view.
- Configuration, not code. Vendor platforms are tailored through configuration: products, workflows, accounting rules, static data and report definitions. A typical implementation holds thousands of configuration items. Version them, promote them through environments like code, and test them.
- Upgrades are projects. Major version upgrades usually take months, because every customisation and interface has to be retested. Keep customisation in well-defined extension points so upgrades stay manageable.
- Read access to the database. Many banks extract data straight from the TMS database for reports. This is fast to build but breaks when the vendor changes its schema. Prefer vendor-supported extraction interfaces or an event feed.
- Product coverage defines the boundary. Products the TMS cannot model end up in spreadsheets or side systems, and these are where operational risk concentrates. Keep an inventory of them.
Analogy. A bank TMS is like an ERP system for the trading floor. Like SAP for a manufacturer, it gives every department the same order record, enforces a workflow and integrates finance. It is also just as costly to customise and to upgrade.
Key takeaways
- A TMS covers the trade lifecycle from pricing and capture to settlement, valuation, accounting and risk, around a single trade record.
- Bank platforms (Murex, Calypso, FIS Front Arena) focus on markets and risk depth; corporate platforms (Kyriba, FIS Quantum) focus on cash visibility, payments and bank connectivity.
- Vendor packages are tailored mainly through configuration, which must be governed like code.
- Most engineering effort goes into interfaces and into the products the package cannot handle.
🧪 Practice
- Beginner: List the steps an FX forward goes through in a TMS, from deal capture to maturity, and name the team (front, middle, back office, finance) responsible for each step.
- Intermediate: A bank runs Murex for its treasury desk and a separate in-house system for structured deposits that Murex cannot model. Describe the risks this creates and the controls you would put around the side system.
- Interview: "Your bank's reports read directly from the TMS database tables. The vendor announces a schema change in the next release. How would you redesign the reporting interface?" Hint: consider vendor-supported extract APIs or event feeds, a staging layer with your own stable schema, contract tests against the vendor's release candidates, and how to migrate without a big-bang cutover.
Core Banking Systems
Why it matters. Treasury manages the balance sheet, but most of the balance sheet is not created by treasury. It consists of the customer loans, mortgages, current accounts and term deposits that the bank's retail and commercial businesses book. These products live in the core banking system, the bank's system of record for customer accounts. Treasury depends on it for three things: the balances and cash flows that feed liquidity and ALM calculations, the accounts through which treasury's own payments flow (nostro, vostro and internal accounts), and the posting of interest and fees that determine net interest income.
What a core banking system does.
| Function | Description | Treasury relevance |
|---|---|---|
| Customer and account management | Opening, maintaining and closing accounts, account hierarchies | Defines which balances exist |
| Deposits | Current and savings accounts, term deposits, interest calculation and capitalisation | Main funding source; behavioural models for ALM (Chapter 6.1) |
| Lending | Loan origination, schedules, repayments, arrears | Asset cash flows, prepayment behaviour |
| Payments posting | Debits and credits from payment systems | Intraday liquidity, account balances |
| Interest and fee accrual | Daily accruals, rate resets, fee charges | Net interest income |
| General ledger interface | Postings to the GL, usually in batch | Financial reporting |
Generations of core banking.
| Generation | Characteristics | Examples |
|---|---|---|
| Legacy mainframe | COBOL, batch-oriented, built from the 1970s onward; balances are authoritative only after the nightly run | In-house systems at many large banks, early packaged systems |
| Packaged, relational | Parameterised product engines on relational databases | Temenos Transact (formerly T24), Oracle FLEXCUBE, Infosys Finacle, TCS BaNCS, Finastra Fusion Essence |
| Cloud-native | Real-time posting, product definitions as configuration or code, APIs first | Thought Machine Vault, Mambu, 10x |
Many large banks still run a mainframe core for their main retail book, sometimes surrounded by newer systems for specific products. This is why "the balance" often has two values in practice: the real-time balance maintained by a front-end layer and the ledger balance that becomes authoritative after the core's end-of-day run.
How treasury connects to the core.
Core banking system
+--------------------------------------------------+
| Accounts: balances, rates, maturities, product |
| codes, customer segment |
+-----+--------------------+--------------------+--+
| | |
| EOD contract-level | intraday postings | GL postings
| extract | (payments) |
+-----v--------+ +-------v---------+ +------v--------+
| ALM / | | Liquidity | | General |
| liquidity | | monitoring, | | ledger |
| data mart | | cash position | | |
+-----+--------+ +-----------------+ +------+--------+
| |
+-------------- reconciliation -----------+
(sum of contract balances = GL balances)
The most important treasury feed is the end-of-day contract-level extract: one row per account or contract, with balance, currency, rate type, rate, repricing date, maturity, product and customer segment. ALM engines use it to project cash flows and repricing (Chapter 6.2), and liquidity reports use it to build maturity ladders (Chapter 9.2).
Example: what a contract extract row looks like.
| contract_id | product | ccy | balance | rate_type | rate | next_reset | maturity | segment |
|---|---|---|---|---|---|---|---|---|
| L-0045512 | MORTGAGE_FIX5 | EUR | 245,300.00 | FIXED | 3.15% | 2029-04-01 | 2051-04-01 | RETAIL |
| D-8812001 | TERM_DEP_12M | EUR | 50,000.00 | FIXED | 2.40% | n/a | 2027-03-15 | RETAIL |
| D-1100234 | CURRENT_ACC | EUR | 12,840.55 | ADMIN | 0.00% | n/a | none | SME |
The last row shows a typical problem: current accounts have no contractual maturity and an administered rate. ALM cannot use the contractual data alone and applies behavioural models to such products.
Engineering view.
- Reconcile the extract to the GL. The sum of contract balances by product and currency should equal the GL account balances. Differences mean missing contracts, wrong mappings or timing differences, and they flow straight into wrong liquidity and IRRBB figures.
- Map products explicitly. The core may have hundreds of product codes. Treasury needs a maintained mapping table from product code to ALM category, liquidity category and behavioural model, and a control that flags new, unmapped codes.
- Understand the cut-off. Know which business date the extract represents and which postings (late payments, back-valued corrections) it includes.
- Treat core banking modernisation as a treasury project. When a bank replaces its core, every treasury feed changes. Plan parallel runs that compare ALM and liquidity outputs from the old and new feeds.
Analogy. If the TMS is the trading floor's ERP, the core banking system is the bank's main warehouse ledger: it records every customer account, and treasury takes its stock count from it every night.
Key takeaways
- The core banking system is the system of record for customer accounts, which make up most of the balance sheet.
- Treasury depends on it for contract-level balance sheet data, intraday postings and GL postings.
- Legacy cores are batch-oriented, so the authoritative balance is often the end-of-day ledger balance.
- Contract extracts must be reconciled to the GL, and product codes mapped explicitly to ALM and liquidity categories.
🧪 Practice
- Beginner: Name four fields from a core banking contract extract that an IRRBB calculation needs, and explain what each one is used for.
- Intermediate: The sum of mortgage balances in the ALM extract is EUR 12 million lower than the GL mortgage account. List four possible causes and how you would investigate each one.
- Interview: "Your bank is migrating from a mainframe core to a cloud-native core over two years, product by product. How do you keep the treasury feeds consistent during the migration?" Hint: think about a canonical extract format that both cores feed, product mapping per source, avoiding double counting or gaps for contracts in migration, and comparing outputs in parallel runs.
ALM and Liquidity Engines
Why it matters. Chapters 5 and 6 explained what treasury must measure for the whole balance sheet: liquidity gaps, the LCR and NSFR, repricing gaps, NII sensitivity and EVE. None of these can be calculated from one trade at a time. They need every contract on the balance sheet, from the TMS, the core banking system and other sources, projected forward under assumptions about customer behaviour, new business and market scenarios. ALM and liquidity engines are the systems built for this: they take a balance-sheet-wide snapshot and turn it into projected cash flows, repricing profiles and regulatory metrics.
What these engines do.
Inputs Engine Outputs
+---------------------+ +-------------------------------+ +---------------------+
| Contract data | | 1. Data load and validation | | Liquidity ladders, |
| (core, TMS, other) +-->+ 2. Mapping to ALM categories +-->+ LCR, NSFR, ALMM |
| Market data | | 3. Cash flow generation | | Repricing gaps |
| (curves, FX) | | (contractual) | | NII projections |
| Behavioural models | | 4. Behavioural overlays | | EVE, delta EVE |
| (prepayment, NMD, | | (prepayment, NMD, rollover)| | FTP rates |
| rollover) | | 5. New business / balance | | Stress results |
| Scenarios and | | sheet projection | | Regulatory returns |
| assumptions | | 6. Scenario valuation | | |
+---------------------+ +-------------------------------+ +---------------------+
Two calculation modes are central:
| Mode | Question answered | Example output |
|---|---|---|
| Static (run-off) | What happens to the current balance sheet if nothing new is booked? | Contractual and behavioural cash flow ladder, repricing gap, EVE |
| Dynamic | What happens if the business plan is executed, with new volumes, margins and rates? | 12-36 month NII projection, forecast LCR |
Main products. Banks use specialised vendor engines, ALM modules of their TMS, or in-house builds:
| Product | Notes |
|---|---|
| QRM | Specialist ALM and balance sheet modelling platform, widely used by US and European banks |
| Moody's ALM and Wolters Kluwer OneSumX | ALM, liquidity and regulatory reporting suites |
| FIS Balance Sheet Manager | ALM and liquidity, also via FIS's broader banking offering |
| Oracle Financial Services (OFSAA) | ALM, liquidity risk, FTP modules on a shared data model |
| SAS | Risk and ALM analytics |
| In-house | Common for liquidity reporting and intraday monitoring, often on a data warehouse |
Example: from contract to ladder. A EUR 1,000,000 five-year amortising loan with annual payments at 4% produces, in its first year, a contractual principal repayment of about EUR 184,600 and interest of EUR 40,000. The engine places the payment of about EUR 224,600 in the 6-12 month bucket of the liquidity ladder. If the prepayment model assumes 5% conditional prepayment per year, it adds about EUR 40,800 of additional principal (5% of the balance after the scheduled repayment) to the same bucket and reduces the later flows accordingly. The same contract also appears in the repricing gap in its maturity buckets, because it is fixed rate. Each contract goes through these steps, and the engine aggregates the results by bucket, currency, entity and scenario.
Engineering view.
- Data volume is the hard part. A retail bank can have tens of millions of contracts. Engines either process each contract or aggregate contracts with similar characteristics into model points (cohorts with the same product, rate type, rate band and maturity band). Aggregation makes runs faster but loses detail. Record the aggregation rules and test their impact.
- Behavioural models are versioned assets. Prepayment, non-maturity deposit and rollover models change results significantly. Store each model with its parameters, calibration date and approval status (Chapter 15.4 covers model risk management), and record which version each run used.
- Reproducibility. Regulators and auditors ask for past results to be explained. Keep the input snapshot, model versions and assumptions for every official run so it can be reproduced.
- Consistency with other systems. Treasury's ALM view, risk's market risk view and finance's GL view must reconcile. Many disagreements between ALCO, risk and finance come from inconsistent data cuts, not from different methods.
Key takeaways
- ALM and liquidity engines take a balance-sheet-wide snapshot and project cash flows and repricing under behavioural assumptions and scenarios.
- Static (run-off) analysis looks at the existing balance sheet; dynamic analysis adds planned new business.
- Data volume and data quality are the main challenges; aggregation into model points trades speed for detail.
- Behavioural models, assumptions and input snapshots must be versioned so that every official run is reproducible.
🧪 Practice
- Beginner: Explain the difference between a static and a dynamic ALM run, and give one management question each one answers.
- Intermediate: An ALM engine aggregates 8 million mortgages into 4,000 model points by product, rate type, rate band (0.25% wide) and maturity year. What information is lost, and which outputs (liquidity ladder, repricing gap, EVE, NII) are most likely to be affected?
- Interview: "The ALCO pack shows an NII sensitivity that changed 15% from last month, but the balance sheet barely changed. How do you find out why?" Hint: compare run inputs step by step: contract data, market data, behavioural model versions, scenario definitions and new business assumptions. An attribution that changes one input at a time isolates the driver.
Risk Engines
Why it matters. Treasury trades must be valued and measured for risk every day: sensitivities for hedging, VaR and stressed VaR for market risk limits and capital, potential future exposure for counterparty limits, and XVA for pricing. Pricing a single swap is quick, but computing a Monte Carlo PFE for thousands of netting sets requires billions of valuations. Risk engines are the systems built for these calculations. They take trades, market data and scenarios as input and produce risk measures, usually on large compute grids.
What risk engines calculate.
| Measure | Method summary | Computational profile |
|---|---|---|
| Sensitivities (delta, gamma, vega, DV01, CS01) | Bump and revalue, or algorithmic differentiation | Several revaluations per risk factor bucket |
| Historical VaR, expected shortfall | Revalue the portfolio under 250-500 historical scenarios (Chapter 8.1) | Full revaluation or sensitivities times scenarios |
| Stressed VaR, stress tests | Revalue under defined stress scenarios | Fewer scenarios, often full revaluation |
| PFE and EPE (counterparty) | Monte Carlo simulation of risk factors over a time grid, valuation of each netting set at each point (Chapter 8.3) | Paths x time steps x trades: very large |
| XVA (CVA, DVA, FVA, KVA) | Expected exposures combined with default probabilities and funding spreads | Same simulation as PFE, plus sensitivities of XVA itself |
| FRTB capital | Standardised approach (sensitivities-based method) and internal model approach (expected shortfall by liquidity horizon) | Many sensitivities, many ES runs |
Typical architecture.
+-------------+ +---------------------+ +---------------------------+
| Trades | | Orchestrator | | Compute grid |
| (from TMS) +---->+ - splits portfolio +---->+ worker 1: pricing library |
+-------------+ | into tasks | | worker 2: pricing library |
| Market data +---->+ - schedules tasks | | ... |
| snapshot | | - retries failures | | worker N: pricing library |
+-------------+ +----------+----------+ +-------------+-------------+
| Scenarios +--------------->| |
+-------------+ | results (per trade, |
|<-----scenario, measure)------+
+----------v----------+
| Aggregation layer |
| (by book, desk, |
| netting set, legal |
| entity), cubes |
+----------+----------+
|
+----------v----------+
| Limits, reports, |
| capital, dashboards |
+---------------------+
Where the engine sits. Banks use one of three set-ups, often in combination:
| Set-up | Description | Strength | Weakness |
|---|---|---|---|
| TMS-native risk | Risk modules of the TMS (Murex, Calypso) | Same pricing as front office, no trade mapping | Limited flexibility, vendor roadmap |
| Specialised vendor engine | Third-party engines and analytics libraries, such as Numerix, Bloomberg analytics or the open-source ORE (Open Source Risk Engine, based on QuantLib) | Depth in specific measures (XVA, FRTB) | Trades must be mapped into the engine's model |
| In-house engine | Bank's own quant library on its own grid | Full control, consistent across systems | Cost, key-person risk, model validation burden |
Front-to-risk consistency. The key architectural risk is that front office and risk value the same trade differently: different curves, different conventions, different model versions. Regulators check this explicitly. Under FRTB, for example, desks using internal models must pass a P&L attribution test comparing risk-model P&L with front-office P&L. Sharing a pricing library and a market data snapshot between front office and risk is the most effective mitigation.
Engineering view.
- Same snapshot, same results. A risk run must use one consistent market data snapshot and one trade population as of a defined cut-off. Mixing data from different times creates artificial P&L and risk.
- Determinism. Monte Carlo results must be reproducible: fix random seeds per run and make task results independent of how work was split across workers.
- Partial results are dangerous. If 2% of tasks fail, a VaR aggregated from the remaining 98% looks valid but is wrong. Aggregation must check completeness against the expected task list before publishing.
- Store results at the right grain. Keeping results per trade and scenario enables drill-down and incremental runs, at the cost of storage. Many banks store vectors of P&L per trade (one value per scenario) and aggregate on demand.
Key takeaways
- Risk engines revalue portfolios under many scenarios to produce sensitivities, VaR, stress results, PFE and XVA.
- Counterparty exposure and XVA are the most compute-intensive measures because they simulate paths over time for every netting set.
- Banks combine TMS-native risk, specialised vendor engines and in-house quant libraries; front-to-risk consistency is a regulatory concern.
- Completeness checks, deterministic simulations and consistent snapshots are essential for trustworthy results.
🧪 Practice
- Beginner: Explain why calculating PFE for a portfolio of interest rate swaps takes far more computation than calculating its DV01.
- Intermediate: A historical VaR run has 500 scenarios and 20,000 trades. Calculate the number of trade revaluations for full revaluation. Then estimate the reduction if 15,000 linear trades are handled with sensitivities (one revaluation per sensitivity bump, with 40 buckets) and only the other 5,000 are fully revalued.
- Interview: "The risk report shows VaR dropping 30% overnight with no significant change in positions. What do you check first?" Hint: consider completeness of tasks and trade population, a missing or stale market data snapshot, scenario set changes, failed trade mappings that drop trades silently, and how you would build controls that detect these automatically.
Build vs Buy Decisions
Why it matters. Every system described above can be bought from a vendor, built in-house, or assembled from a mix of both. These decisions lock a bank in for ten years or more and cost tens or hundreds of millions. They are rarely pure technology decisions: they involve cost, regulation, differentiation, skills and risk. Engineers are often asked to contribute to them, and knowing the trade-offs helps you both in the decision and in the years of living with it.
The basic trade-off.
| Factor | Buy (vendor package) | Build (in-house) |
|---|---|---|
| Time to first value | Faster for standard functions | Slower: everything must be built |
| Functional coverage | Broad, proven on many clients | Only what you build |
| Fit to the bank's processes | The bank adapts to the package, or customises it at a cost | Exact fit |
| Differentiation | Same capabilities as competitors | Can be a competitive advantage |
| Regulatory change | Vendor delivers updates for common rules (Basel, FRTB, ISO 20022) | Bank must implement each change |
| Total cost of ownership | Licences, implementation, upgrades, customisation | Engineering team for the system's lifetime |
| Control and roadmap | Vendor's priorities, vendor lock-in | Full control |
| Key risks | Over-customisation, painful upgrades, vendor viability | Key-person dependency, under-investment over time |
What usually ends up bought, and what built. A common pattern in banks is "buy the commodity, build the differentiator, own the data":
Commodity (buy) Differentiator (build or extend)
--------------------------------- ---------------------------------
Trade capture and lifecycle (TMS) Proprietary pricing models
Swift / payment connectivity Client-facing digital channels
Core banking ledgers Liquidity optimisation, funding decisions
Regulatory reporting templates Internal analytics and dashboards
Reconciliation tools Integration layer and data platform
The integration layer and data platform are almost always the bank's own, whatever is bought. They are where the bank's knowledge of its own data lives, and they make it possible to replace a vendor later.
A structured evaluation. A defensible decision compares options on weighted criteria rather than on opinion. For example, for a new intraday liquidity monitoring capability:
| Criterion (weight) | Vendor A (score 1-5) | In-house build (score 1-5) |
|---|---|---|
| Functional fit (25%) | 4 | 5 |
| Time to deliver regulatory requirement (20%) | 4 | 2 |
| 10-year TCO (20%) | 3 | 3 |
| Integration with existing data platform (15%) | 2 | 5 |
| Vendor/key-person risk (10%) | 3 | 2 |
| Regulatory change support (10%) | 4 | 1 |
| Weighted score | 3.40 | 3.30 |
The scores are close, which is common. The decision then depends on non-scored factors: whether the regulatory deadline is fixed, whether the bank has the engineering capacity, and whether the capability is strategic.
Total cost of ownership. The licence is usually a minority of a vendor package's cost. A realistic TCO includes:
- implementation (integration, data migration, configuration, testing), often several times the first-year licence;
- annual maintenance and licence fees;
- infrastructure and environments (production, DR, several test environments);
- upgrades every few years, each a project in itself;
- the internal team that configures, supports and integrates the package;
- for builds, the full engineering team for the life of the system, not just the first release.
The customisation trap. Banks often buy a package and then customise it until it behaves like their old system. Each customisation makes upgrades harder, until the bank is stuck on an unsupported version. The usual guidance is to adopt the package's standard processes where they are not differentiating, and to put unavoidable custom logic outside the package, in the integration layer or in separate services.
Key takeaways
- Build-vs-buy decisions weigh time to value, fit, differentiation, regulatory change support, TCO, control and risk.
- Banks tend to buy commodity functions, build differentiating ones and always own their integration layer and data.
- TCO must include implementation, upgrades, infrastructure and the internal team, not just licences.
- Heavy customisation of a package combines the costs of buying and building; keep custom logic outside the package where possible.
🧪 Practice
- Beginner: Give two reasons why a bank might build its own intraday liquidity dashboard rather than buying one, and two reasons why it might buy.
- Intermediate: Estimate a 10-year TCO for a vendor TMS with a licence of 1.5M per year, an implementation of 8M, an internal support team of 10 people at 150k each per year, and a major upgrade of 3M every four years. Compare it with an in-house build requiring 25 engineers for three years and 12 engineers afterwards, at the same cost per person.
- Interview: "The business wants a new FX hedging product that our TMS cannot model. The vendor says it will be supported in 18 months. What options do you present?" Hint: consider waiting, a tactical side system with strict controls and a decommission plan, extending the TMS through a supported extension point, or modelling the product as a combination of supported products, and compare them on risk, time and cost.
<a id="132-data-architecture"></a>
13.2 Data Architecture
This subchapter covers how treasury data is structured and governed: the models for trades and positions, the management of reference and market data, the principle of a single authoritative source for each data item, and two techniques (bitemporal modelling and event sourcing) that let systems answer "what did we know, and when?".
Trade and Position Data Models
Why it matters. A trade is the basic fact of a treasury system: two parties agreed to exchange cash flows on certain terms. Almost everything else is derived from it: cash flows, settlements, positions, P&L, risk and accounting. If the trade model is wrong or ambiguous, every downstream system inherits the error. A position answers a different question: "how much of something do we hold, or owe, right now or on a given date?" Mixing up the two, or deriving positions inconsistently, is one of the most common sources of breaks between systems.
Trade versus position.
| Aspect | Trade | Position |
|---|---|---|
| What it records | An agreement at a point in time, with its terms | The net holding or exposure resulting from many trades |
| Granularity | One record per deal (plus versions) | One record per aggregation key (book, instrument, currency, date...) |
| Lifecycle | Created, amended, cancelled, partially terminated, matured | Recalculated whenever trades or market data change |
| Typical source | Captured in the TMS | Derived by a position keeping engine (Chapter 13.3) |
| Authority | Primary data | Derived data: must be reproducible from trades |
Anatomy of a trade model. Treasury products vary, but most models share a common structure: a header with economic and organisational attributes, one or more legs, and generated cash flows.
Trade (header)
+------------------------------------------------------------+
| trade_id, version, status (LIVE, CANCELLED, MATURED) |
| trade_date, entry_timestamp, trader, book, legal_entity |
| counterparty_id, product_type, external refs (UTI, USI) |
+------------------------------------------------------------+
|
+-- Leg 1 (e.g. fixed leg of a swap)
| notional, currency, start_date, end_date,
| rate or index (fixed 3.10% / EURIBOR 6M + spread),
| day count, payment frequency, calendars, BDC
| |
| +-- Cash flow 1: pay date, accrual period, amount (or "to be fixed")
| +-- Cash flow 2 ...
|
+-- Leg 2 (e.g. floating leg)
...
Lifecycle events: NEW, AMEND, PARTIAL_TERMINATION, FIXING, NOVATION, CANCEL, MATURE
The leg structure lets one model cover many products: an FX forward has two currency legs with single cash flows, a deposit has one leg with principal and interest flows, and an interest rate swap has a fixed and a floating leg. Standards such as FpML (Chapter 12.3) and ISDA's Common Domain Model (CDM) use the same idea.
Versioning. Trades change: a trader corrects the rate, a counterparty asks for a different settlement date, a swap is partially terminated. Two fundamentally different changes must be modelled differently:
| Change | Meaning | Correct modelling |
|---|---|---|
| Correction (amendment of an error) | The trade was always meant to have the new terms | New version replaces the old one from the start; downstream systems restate |
| Lifecycle event (economic change) | The trade's terms change from an effective date onward | Event with an effective date; history before that date remains valid |
Never update trade rows in place. Each change creates a new version with a reason code, user and timestamp, so that the trade can be reconstructed as of any time (see bitemporal modelling below).
Positions. A position is an aggregate over trades, defined by an aggregation key and a measure.
Trades Positions (key -> measure)
FX-1 BUY EUR 10M / SELL USD 11.65M val 22-Oct (BOOK_FX, EUR, 22-Oct) -> +10M
FX-2 SELL EUR 4M / BUY USD 4.67M val 22-Oct (BOOK_FX, EUR, 22-Oct) -> -4M
FX-3 BUY EUR 2M / SELL USD 2.33M val 24-Oct ==> net +6M
(BOOK_FX, EUR, 24-Oct) -> +2M
(BOOK_FX, USD, 22-Oct) -> -6.98M
(BOOK_FX, USD, 24-Oct) -> -2.33M
Typical position types in treasury:
- Cash positions by account, currency and value date (the cash ladder, Chapter 5.2);
- Securities positions by instrument and account, on a trade-date or settlement-date basis;
- Risk positions by risk factor (for example DV01 by currency and tenor bucket);
- Exposure positions by counterparty or netting set, for limits.
Engineering view.
- Use stable, immutable trade identifiers, plus a version number. Downstream systems should reference
(trade_id, version)when they need to know exactly what they processed. - Carry the regulatory identifiers (UTI, LEI) and external references (confirmation IDs, platform IDs) on the trade; matching and reporting need them.
- Make positions reproducible: given the trades and the position rules, you must be able to recompute a position for any date and get the same answer.
- Model "who owns this position" explicitly: book, desk, legal entity and portfolio hierarchy are part of the position key, and hierarchy changes (book moves) are a common source of breaks.
Key takeaways
- Trades are primary facts; positions are derived aggregates that must be reproducible from trades.
- A header-legs-cash flows structure lets one model cover deposits, FX, swaps and bonds.
- Distinguish corrections (restate from the start) from lifecycle events (change from an effective date); never update trades in place.
- Positions are defined by an aggregation key and a measure; cash, securities, risk and exposure positions use different keys.
🧪 Practice
- Beginner: Model a 3-month EUR 25M term deposit placed with another bank as a trade with header, leg and cash flows. Which cash flows does it generate?
- Intermediate: A swap's fixed rate was captured as 3.01% instead of 3.10% and corrected two weeks later. A month after that, the swap is partially terminated by 40%. Describe the versions and events stored, and what the P&L system must restate in each case.
- Interview: "Design the tables for trades and positions in a new treasury platform supporting deposits, FX forwards and interest rate swaps." Hint: consider a generic header/leg/cash flow structure versus product-specific tables, versioning and lifecycle events, the position key, and which data is stored versus recomputed.
Reference and Static Data Management
Why it matters. A trade record usually contains identifiers only: counterparty C-10442, instrument XS2411234567, calendar TARGET. Everything needed to process it (the counterparty's legal name, LEI, credit limits and settlement instructions, the bond's coupon schedule, the holidays of the calendar) lives in reference data, also called static data because it changes much less often than trades and prices. Industry studies have repeatedly found that a large share of failed trades and settlement breaks come from poor reference data, especially wrong or outdated settlement instructions. Reference data errors spread to every trade that uses the record.
Main reference data domains in treasury.
| Domain | Examples | Typical external source |
|---|---|---|
| Legal entities and counterparties | Name, LEI, parent/ultimate parent, country, sector, credit rating, KYC status | GLEIF, rating agencies, internal KYC |
| Settlement instructions (SSIs) | Account, correspondent bank, BIC per currency and product | Counterparty notices, SWIFT SSI databases, DTCC ALERT |
| Instruments | ISIN, CUSIP, issuer, coupon, maturity, day count, call schedule | Bloomberg, LSEG, ANNA (ISIN), exchanges |
| Calendars | Holidays per city or settlement system (TARGET2, New York, London) | Vendor calendar services, central banks |
| Currencies and conventions | ISO 4217 codes, decimals, spot lag, day count conventions | ISO, market conventions |
| Indices | EURIBOR, SOFR, ESTR: tenor, fixing time, publication lag | Administrators (EMMI, Federal Reserve, ECB) |
| Internal hierarchies | Books, desks, legal entities, cost centres, portfolios | Internal (finance, front office) |
Lifecycle of a reference data record.
Request Validate Approve Publish Use
(operations, --> (format, external --> (checker, --> (distribution --> (trade capture,
front office) sources, duplicates) second person) to systems) settlement,
risk...)
^ |
| Periodic review and external updates |
+---------------------------------------------------------------------------------+
The maker-checker step is mandatory for sensitive data such as SSIs: changing a counterparty's account number is a classic fraud vector. Many banks also require a call-back to the counterparty, using a phone number already on file, before an SSI change is activated.
Distribution patterns.
| Pattern | Description | Strength | Weakness |
|---|---|---|---|
| Point-to-point copies | Each system keeps its own copy, maintained by hand or by file | Simple to start | Copies drift; no single version of truth |
| Central hub, scheduled distribution | Central master publishes daily or intraday files to consumers | Consistent at each publication | Latency between change and use |
| Central hub, event publication | Changes published as events on a message bus; consumers update caches | Near real-time, auditable | Consumers must handle ordering and replay |
| Central service, query at use | Consumers call an API when they need the data | Always current | Runtime dependency, latency, availability |
Most banks combine these: an event feed for systems that need to react to changes, plus API lookup for occasional queries.
Example: a counterparty hierarchy. Credit limits and large exposure reporting (Chapter 9) aggregate exposure up to the ultimate parent. If one subsidiary is not linked to its parent, the group's exposure is understated.
Global Bank Holding plc (LEI 5493...A1) <- ultimate parent
|
+-- Global Bank plc (London) LEI 5493...B2
| +-- Global Bank plc, NY branch (same LEI, different BIC)
+-- Global Bank AG (Frankfurt) LEI 5299...C3
+-- Global Securities LLC (NY) LEI 2549...D4 <- missing link = understated exposure
Engineering view.
- Give each reference entity an internal immutable ID, and store external identifiers (LEI, BIC, ISIN) as attributes with validity dates. External identifiers change (corporate actions, mergers) and are sometimes reused.
- Make reference data effective-dated: a holiday added next year, an SSI valid from 1 November, a book moved to another desk at month-end.
- Add data quality rules at the source (mandatory fields, format checks such as LEI check digits and ISIN checksums) and dashboards for completeness and staleness.
- Treat reference data changes as high-risk operations: they need approval, audit trails and, for SSIs, out-of-band verification.
Key takeaways
- Reference data describes the entities that trades refer to: counterparties, SSIs, instruments, calendars, indices and internal hierarchies.
- Errors in reference data affect every trade that uses the record; poor SSIs are a leading cause of settlement failures.
- Changes go through request, validation, maker-checker approval and controlled distribution; SSI changes need out-of-band verification.
- Use internal IDs, effective dating and data quality rules; distribute via events or APIs rather than uncontrolled copies.
🧪 Practice
- Beginner: List five reference data items needed to settle an FX forward and say where each comes from.
- Intermediate: A counterparty sends an email asking to change its USD settlement account from next Monday. Design the process and system controls before the change becomes active.
- Interview: "Ten systems each hold their own copy of the counterparty list, and they disagree. How would you introduce a counterparty master without a big-bang migration?" Hint: consider building the master from the best existing source, a cross-reference table mapping each system's IDs to master IDs, publishing changes as events, migrating consumers one at a time, and measuring convergence with reconciliation reports.
Market Data Management
Why it matters. Every valuation, risk figure and many limits depend on market data: FX rates, interest rate fixings, bond prices, volatilities and credit spreads. Wrong market data produces wrong numbers silently: a stale USD/JPY rate or a mistyped swap rate flows straight into P&L, VaR and capital, and nothing crashes. Market data is also expensive (vendor licences are among a bank's largest IT costs) and governed by redistribution rules. Market data management covers sourcing, validating, storing and distributing this data, and building derived data such as curves and surfaces from it.
Raw versus derived market data.
Raw (observed) Derived (calculated) Consumers
+---------------------------+ +----------------------------+ +-----------------+
| Quotes: deposit rates, | | Discount and forward | | Pricing |
| futures, swap rates +------>+ curves (Chapter 11.2) +---->+ P&L |
| FX spot and forward points| | FX forward curves | | VaR / stress |
| Bond prices +------>+ Bond yield / spread curves +---->+ ALM / FTP |
| Option quotes +------>+ Volatility surfaces +---->+ Limits |
| Fixings (SOFR, ESTR, | | Historical scenario sets | | Accounting (FV) |
| EURIBOR) | | (returns time series) | | Collateral |
+---------------------------+ +----------------------------+ +-----------------+
Real-time versus end-of-day.
| Aspect | Real-time (streaming) | End-of-day (official snapshot) |
|---|---|---|
| Purpose | Trading, pre-trade pricing, intraday risk | Official valuation, P&L, risk, accounting, regulatory reports |
| Source | Vendor streams (Bloomberg B-PIPE, LSEG Real-Time), venues, brokers | Snapshot of streams at a fixed cut-off, vendor closing prices, broker quotes |
| Volume | Thousands of updates per second | One record per instrument per day |
| Validation | Light: filtering, spike detection | Full: tolerance checks, cross-source comparisons, sign-off |
| Storage | Usually not stored at full tick level by treasury | Stored permanently, versioned, used for history and VaR |
The official end-of-day snapshot. Banks define a close for each region (for example 17:00 London for EUR and GBP products, 17:00 New York for USD products). At the close, the market data system takes a snapshot, validates it, builds curves and publishes an official set that all end-of-day processes use. Using one official set is what makes P&L, risk and accounting consistent.
Validation rules. Typical checks before a snapshot is released:
| Check | Example | Action on failure |
|---|---|---|
| Staleness | Quote not updated for 3 days on a liquid instrument | Flag for review, use alternate source |
| Day-on-day jump | 10Y EUR swap rate moves 40bp, threshold 15bp | Require analyst confirmation |
| Cross-source comparison | Bloomberg and LSEG differ by more than tolerance | Investigate, choose source per hierarchy |
| Arbitrage/consistency | Negative forward rates where not plausible, crossed bid/ask, vol surface with calendar arbitrage | Reject or smooth |
| Completeness | Missing tenor points for a curve | Interpolate per policy or use previous with flag |
Each override (manual price, alternate source) is recorded with user, reason and approval. Product control later performs independent price verification (IPV): comparing the prices used for valuation against independent sources and adjusting P&L if needed.
Example: a market data record.
instrument_id : EUR_SWAP_10Y
field : MID_RATE
value : 0.02745
as_of_date : 2026-10-19
snapshot : LDN_CLOSE
source : VENDOR_A (primary)
status : VALIDATED
overridden : false
loaded_at : 2026-10-19T17:02:13Z
validated_by : md_ops_user_17 at 2026-10-19T17:21:40Z
Storing as_of_date and snapshot together lets the same instrument have several official values on one day (London close, New York close, Tokyo close), each used by the processes of that region.
Licensing. Vendor contracts restrict who may see data, how it may be redistributed and how derived data may be used. A market data platform must record which users and applications consume which data and report usage to vendors. Building curves from licensed quotes and publishing them to clients, for example, may require separate rights.
Engineering view.
- Keep raw quotes, validated values and derived objects (curves, surfaces) as separate layers with lineage between them, so you can explain any curve point from its inputs.
- Make snapshots immutable once published. Corrections create a new snapshot version, and consumers can be told to rerun.
- Separate the time a value applies to (
as_of_date, snapshot) from the time it was loaded or changed; this is bitemporal data (below). - Monitor consumers: know which processes used which snapshot version, so you can target reruns after a correction.
Key takeaways
- Market data drives valuations, risk and limits; errors propagate silently.
- Distinguish raw observed quotes from derived curves and surfaces, and real-time streams from official end-of-day snapshots.
- The official snapshot is validated (staleness, jumps, cross-source, consistency, completeness), and overrides are recorded and approved.
- Published snapshots are immutable and versioned; licensing rules restrict use and redistribution.
🧪 Practice
- Beginner: Explain why a bank uses one official end-of-day market data snapshot for P&L, risk and accounting rather than each system taking its own prices.
- Intermediate: Design validation rules for an EUR swap curve snapshot with 15 tenors. Include thresholds, how they differ by tenor, and what happens when a rule fails at 17:30 with the EOD batch due to start at 19:00.
- Interview: "After the official snapshot was published and the EOD batch completed, someone finds that the GBP 5Y swap rate was wrong by 20bp. What happens next?" Hint: consider who decides whether to correct, creating a new snapshot version, identifying all consumers of the old version, rerunning valuations and P&L, restating reports already sent, and documenting the incident.
Golden Source and Data Ownership
Why it matters. In a typical bank, the same fact is stored in many places. The counterparty's rating appears in the credit system, the TMS, the risk engine and the reporting warehouse. A trade's notional exists in front office, back office, risk and finance. When the copies disagree, which one is right? Without a clear answer, reports from different departments contradict each other and every disagreement becomes a manual investigation. After the 2008 crisis, regulators found that many banks could not aggregate their exposures quickly or reliably. The Basel Committee responded with BCBS 239 (Principles for effective risk data aggregation and risk reporting, 2013), which requires banks to have clear data ownership, accuracy, completeness and timeliness of risk data. The golden source principle is central to meeting it.
Key concepts.
| Concept | Definition |
|---|---|
| Golden source (authoritative source) | The single system designated as the authoritative origin of a data item. All other copies are derived from it |
| Data owner | A business role (not IT) accountable for the definition, quality and correct use of a data domain |
| Data steward | The person or team that manages the data day to day: fixing errors, maintaining rules |
| Data lineage | The documented path of a data item from its golden source through transformations to reports |
| Critical data element (CDE) | A data item whose errors materially affect key reports or decisions; gets stricter controls |
Example: golden sources for treasury data.
| Data domain | Golden source | Data owner | Main consumers |
|---|---|---|---|
| Treasury trades | TMS | Head of treasury front office | Back office, risk, finance, regulatory reporting |
| Customer loans and deposits | Core banking system | Retail/commercial banking operations | ALM, liquidity, finance |
| Counterparties and LEIs | Counterparty master | Client onboarding / KYC | All |
| SSIs | Settlement instruction master | Operations | TMS, payment hub |
| EOD market data | Market data platform | Product control or market data team | Valuation, risk, finance |
| GL balances | General ledger | Finance (financial control) | Reporting, reconciliations |
| Behavioural model parameters | Model inventory / ALM engine | ALM / treasury | ALM, FTP, liquidity stress |
Copies are allowed, edits are not. Consumers may cache or copy golden-source data for performance, but they must not change it locally. If a consumer finds an error, it is corrected at the source and flows back down. A common anti-pattern is fixing data in the reporting warehouse to make a report correct: the report is right this month, but the source is still wrong, and other consumers still see the error.
Wrong: Right:
TMS (rate 3.01%) --> Warehouse TMS (rate 3.01%) <-- error raised to owner
(patched to 3.10%) | corrected to 3.10%
--> Report OK v
Risk engine still uses 3.01% Warehouse, risk engine, finance
all receive 3.10%
Data lineage. For every critical number in a regulatory report or ALCO pack, the bank should be able to trace back through every transformation to the source records. Lineage is documented at two levels: design-time lineage (which tables and jobs feed which reports) and run-time lineage (which file, snapshot and run produced this particular figure).
Engineering view.
- Record the golden source of every data item in a data catalogue, and enforce it in architecture reviews: new systems may not become an additional source for an existing domain.
- Propagate source identifiers and versions through every transformation (source system, source record ID, extract timestamp) to make run-time lineage possible.
- Build data quality checks at the source and at each handover: completeness (record counts, control totals), validity (formats, ranges) and consistency (reconciliation with the source).
- Avoid "local fixes" in downstream systems. If a temporary adjustment is unavoidable, record it as an explicit, approved adjustment with an expiry date, not as a silent change.
Analogy. A golden source is like the master copy of a legal contract: you may make photocopies to work with, but if a clause is wrong, you amend the master, not a photocopy.
Key takeaways
- Each data item has one golden source; all other copies derive from it and must not be edited locally.
- Data owners are business roles accountable for definition and quality; stewards manage the data day to day.
- BCBS 239 requires accurate, complete, timely and traceable risk data, which depends on golden sources and lineage.
- Fix errors at the source; record downstream adjustments explicitly, with approval and expiry.
🧪 Practice
- Beginner: Explain the difference between a data owner and a data steward, with an example from the SSI domain.
- Intermediate: Both the TMS and the collateral system store the margin agreement terms (thresholds, minimum transfer amounts) for each counterparty, and they disagree for 5% of counterparties. Propose how to decide the golden source and how to fix the situation.
- Interview: "A regulator asks you to show how the LCR figure for last quarter was calculated, from source data to the reported number. What must exist in your architecture to answer within days?" Hint: consider a data catalogue with design-time lineage, run identifiers propagated through every layer, retained input snapshots, recorded adjustments and their approvals, and reconciliations at each handover.
Bitemporal Data Modeling
Why it matters. Financial data has two separate timelines. The first is when something is true in the business world: a trade was effective from 1 March, a rate fixed on 15 April. The second is when the bank's systems knew about it: the trade was booked on 3 March, a wrong fixing was corrected on 20 April. Auditors and regulators often ask questions that need both: "What did the risk report you submitted on 31 March show, and why is it different from what you would calculate today for 31 March?" A system that only overwrites values, or only keeps one timeline, cannot answer. Bitemporal modelling stores both timelines explicitly.
The two time dimensions.
| Dimension | Also called | Question it answers | Example |
|---|---|---|---|
| Valid time | Business time, effective time, application time | When is this fact true in the real world? | The swap's notional is 100M from 1 Mar to 15 Jun, then 60M after a partial termination |
| Transaction time | System time, knowledge time, record time | When did the system record this fact, and until when was it believed? | The 60M notional was recorded on 16 Jun at 09:12 |
Example. A bank holds a term deposit of EUR 50M. On 10 October it books the deposit with a rate of 2.40%, effective from 1 October. On 14 October it discovers the correct rate was 2.45%.
Table: deposit_terms (bitemporal)
+---------+-------+------------+------------+---------------------+---------------------+
| deal_id | rate | valid_from | valid_to | recorded_from | recorded_to |
+---------+-------+------------+------------+---------------------+---------------------+
| D-1001 | 2.40% | 2026-10-01 | 9999-12-31 | 2026-10-10 08:30 | 2026-10-14 11:05 | superseded
| D-1001 | 2.45% | 2026-10-01 | 9999-12-31 | 2026-10-14 11:05 | 9999-12-31 | current
+---------+-------+------------+------------+---------------------+---------------------+
The table now answers different questions:
| Question | Query condition | Answer |
|---|---|---|
| What is the rate on 12 Oct, as we know it today? | valid time contains 12 Oct, transaction time = now | 2.45% |
| What did we believe on 12 Oct about the rate on 12 Oct? | valid time contains 12 Oct, transaction time contains 12 Oct | 2.40% |
| What did the 12 Oct accrual report use? | Rerun "as known at" the report's run time | 2.40%, explaining the difference in accrued interest |
Visualised as a two-dimensional grid:
transaction time (when we knew)
^
| +------------------------------------------
14 Oct ----------| 2.45% (corrected; applies back to 1 Oct)
| +----------+------------------------------------------
10 Oct | 2.40% (original booking)
| +-------------------------------------------------------
+---+-------------------------------------------------------> valid time
1 Oct (when it is true)
Implementation options.
| Option | Description | Notes |
|---|---|---|
| Four timestamp columns | valid_from/to, recorded_from/to on each table; rows never updated except closing recorded_to | Portable; queries must always filter both dimensions |
| SQL:2011 temporal tables | PERIOD FOR application time plus SYSTEM_TIME versioning, supported to different degrees by Db2, SQL Server, MariaDB, Oracle (Flashback/temporal validity), and PostgreSQL extensions | System time maintained by the database; less custom code |
| Bitemporal databases | Databases designed around both timelines (for example XTDB) | Native "as of" queries, less mainstream |
| Event log plus projections | Store events with effective and recorded times; build bitemporal views from them | Combines with event sourcing (next topic) |
SQL example (four-column approach).
-- Rate as known at a given moment, effective on a given business date
SELECT rate
FROM deposit_terms
WHERE deal_id = 'D-1001'
AND valid_from <= DATE '2026-10-12'
AND valid_to > DATE '2026-10-12'
AND recorded_from <= TIMESTAMP '2026-10-12 18:00:00'
AND recorded_to > TIMESTAMP '2026-10-12 18:00:00';
-- returns 2.40%: the value used by the 12 October EOD run
-- A correction never updates the old row's data; it closes it and inserts a new one
UPDATE deposit_terms SET recorded_to = :now
WHERE deal_id = 'D-1001' AND recorded_to = TIMESTAMP '9999-12-31 00:00:00';
INSERT INTO deposit_terms VALUES ('D-1001', 0.0245, DATE '2026-10-01', DATE '9999-12-31',
:now, TIMESTAMP '9999-12-31 00:00:00');
Where bitemporality matters in treasury.
- Trade corrections and back-dated lifecycle events, to explain P&L restatements;
- Reference data (SSIs, ratings, hierarchies), to show what a payment or limit check used at the time;
- Market data corrections after a snapshot was published;
- Regulatory reports, which must be reproducible as submitted and also restatable with corrected data.
Engineering view.
- Decide per table whether it needs one dimension (most audit needs: transaction time), both, or neither (pure caches).
- Use half-open intervals
[from, to)consistently; this avoids gaps and overlaps at boundaries. - Enforce non-overlap constraints per key in both dimensions; overlapping rows make "as of" queries return two answers.
- Index for the common queries ("current", "as of business date", "as known at") and consider current-state views, because bitemporal tables grow quickly.
Key takeaways
- Bitemporal data tracks valid time (when a fact is true) and transaction time (when the system knew it).
- It lets you reproduce what a report showed when it was produced and explain differences with today's view.
- Corrections close the old record's transaction period and insert a new record; nothing is overwritten.
- Use half-open intervals, non-overlap constraints and suitable indexes; apply bitemporality where audit and restatement need it.
🧪 Practice
- Beginner: A bond's rating changes from A to BBB, effective 5 May, but it is loaded on 7 May. Write the rows of a bitemporal rating table before and after the load.
- Intermediate: Write a SQL query that returns, for each deal, the rate effective on the last business day of September as known at the time the September regulatory report was submitted (2026-10-15 16:00).
- Interview: "Our risk report for 30 June showed a counterparty exposure of 120M. Today, recalculating 30 June gives 95M. The regulator asks why. How would your data design let you answer?" Hint: consider rerunning "as known at" the report's run time, then changing one input at a time (trades, reference data, market data) to "as known today", and attributing the 25M difference to the specific corrections and their timestamps.
Event Sourcing for Ledgers
Why it matters. Accounting has used an append-only model for centuries: a ledger is a sequence of entries, and an error is corrected by a new, reversing entry, never by erasing the old one. Many software systems do the opposite: they store current state and overwrite it. For treasury ledgers (cash positions, securities holdings, accounting subledgers), overwriting loses exactly the information auditors, reconcilers and investigators need. Event sourcing applies the ledger principle to software: the system stores the sequence of events that happened, and derives current state by replaying them.
State-based versus event-sourced.
State-based (CRUD) Event-sourced
+--------------------------+ +-------------------------------------------+
| account | balance | | seq | event | amount |
| NOSTRO1 | 7,250,000 | | 1 | OpeningBalanceSet | 10,000,000 |
+--------------------------+ | 2 | PaymentSent (P-881) | -5,000,000 |
Only the latest value is kept. | 3 | FundsReceived (R-4) | 2,500,000 |
How did it get there? Unknown. | 4 | PaymentReturned | - 250,000 |
| | (R-4 partly) | |
+-------------------------------------------+
balance = replay(events) = 7,250,000
History, cause and order are preserved.
Core concepts.
| Concept | Description |
|---|---|
| Event | An immutable fact in the past tense: TradeBooked, PaymentSettled, CouponReceived. Contains the data, the effective time and the recorded time |
| Event store | Append-only log of events, ordered per stream (for example per account or per trade) |
| Stream | The events of one aggregate, such as one account or one trade |
| Projection (read model) | A view derived from events, such as current balances, positions by value date, or a GL trial balance |
| Snapshot | A saved state at a point in the stream to avoid replaying from the beginning |
| Compensating event | A new event that reverses or corrects an earlier one; the earlier event remains |
Example: an event-sourced cash ledger in Python.
from dataclasses import dataclass
from decimal import Decimal
from datetime import date, datetime
@dataclass(frozen=True)
class LedgerEvent:
seq: int # position in the account's stream
account: str
kind: str # "CREDIT", "DEBIT", "REVERSAL"
amount: Decimal
value_date: date # valid time: when the cash moves
recorded_at: datetime # transaction time: when we learned about it
ref: str # payment or trade reference
reverses: int | None = None
def balance(events: list[LedgerEvent], on: date, known_at: datetime) -> Decimal:
"""Value-dated balance as known at a given moment (bitemporal projection)."""
relevant = [e for e in events if e.value_date <= on and e.recorded_at <= known_at]
total = Decimal("0")
for e in sorted(relevant, key=lambda e: e.seq):
if e.kind == "CREDIT":
total += e.amount
else: # DEBIT, or REVERSAL of a credit
total -= e.amount
return total
Because each event carries both a value date and a recorded time, the same log answers "what is the balance for value 22 October?" and "what did we think the balance was at 10:00 yesterday?". The event log is naturally bitemporal.
Appending safely. An event store must prevent two writers from appending conflicting events to the same stream. The usual technique is optimistic concurrency: the writer states which sequence number it expects to be last, and the append fails if another event was added in between.
-- Event store table with a uniqueness constraint per stream position
CREATE TABLE ledger_events (
account TEXT NOT NULL,
seq BIGINT NOT NULL,
kind TEXT NOT NULL,
amount NUMERIC(20,2) NOT NULL,
value_date DATE NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
ref TEXT NOT NULL,
PRIMARY KEY (account, seq),
UNIQUE (account, ref, kind) -- idempotency: the same payment is not booked twice
);
-- Writer read seq 41 as last; it appends seq 42. A concurrent writer that also
-- appends seq 42 violates the primary key and must reload and retry.
Benefits and costs.
| Benefits | Costs |
|---|---|
| Complete audit trail by construction | More complex than CRUD; teams need to learn the model |
| Any past state can be reconstructed | Projections must be rebuilt when their logic changes |
| New read models can be built from history | Event schema evolution must be managed (versioned events, upcasters) |
| Natural fit for streaming and integration (events published to other systems) | Queries need projections; ad hoc queries on the event log are slow |
| Corrections are explicit and traceable | Personal data in immutable logs needs care (crypto-shredding, separation) |
Where it fits in treasury. Event sourcing is a strong fit for ledgers and positions (cash, nostro, securities, subledger postings) and for trade lifecycles (booked, amended, confirmed, settled). It is usually not worth it for reference data screens or report configuration. Many modern core banking and payment platforms are built on an append-only posting log with projected balances, which is event sourcing under another name.
Engineering view.
- Name events after business facts in the past tense, and include all data needed to understand them later, including references to the triggering message or user.
- Make event handlers and projections idempotent; events are delivered at least once in most infrastructures (Chapter 12.4).
- Reconcile projections against the event log regularly: the balance projection must equal the sum of events.
- Plan for schema evolution from day one: version event types and keep the code to read old versions.
Key takeaways
- Event sourcing stores the sequence of immutable events and derives current state by replaying them, as accounting ledgers have always done.
- Corrections are compensating events; nothing is deleted or overwritten.
- Events with value dates and recorded times give bitemporal answers naturally.
- Optimistic concurrency, idempotent handlers, projection reconciliation and event versioning are the essential engineering practices.
🧪 Practice
- Beginner: For a securities account, list the events (in past tense) generated by buying a bond, receiving two coupons and selling half the position.
- Intermediate: Extend the Python
balancefunction so that aREVERSALevent correctly reverses the specific event it references, whether that event was a credit or a debit.- Interview: "The business wants a new report showing nostro balances by hour for the last two years. The system is event-sourced. How do you build it, and what can go wrong?" Hint: consider building a new projection by replaying history, the cost of replay at this volume, snapshots, handling old event schema versions, and checking the new projection against existing end-of-day balances.
<a id="133-processing-models"></a>
13.3 Processing Models
This subchapter covers how treasury systems turn data into results: the nightly batch that produces official figures, intraday and real-time processing for decisions that cannot wait, position keeping, distributed compute grids for heavy calculations, and engines that run portfolios through scenarios.
End-of-Day Batch Processing
Why it matters. Banks produce official numbers once a day: the P&L the desk is judged on, the risk figures checked against limits, the accounting entries in the general ledger, the liquidity figures reported to regulators. These numbers must be consistent with each other, which means they must be calculated from the same trades, the same market data and the same reference data, as of the same moment. The end-of-day (EOD) batch does this: after the business day closes, it freezes the inputs and runs a chain of dependent jobs that produce the official results. In many banks the EOD batch is still the backbone of treasury processing, even where real-time systems exist alongside it.
The shape of an EOD batch.
17:00 Business close (per region)
|
v
[1] Cut-off: stop or tag new trades for today; freeze trade population
|
[2] Market data snapshot validated and published (Chapter 13.2)
|
[3] Lifecycle processing: fixings, resets, coupon and maturity events,
| option expiries, accruals for today
|
[4] Revaluation: mark-to-market of all trades, positions
|
+------------------+------------------+--------------------+
| | | |
[5a] P&L and [5b] Risk runs [5c] Accounting [5d] Liquidity /
P&L explain (sensitivities, events and ALM extracts
VaR, PFE) postings
| | | |
+------------------+--------+---------+--------------------+
|
[6] Reconciliations and controls (positions, GL, completeness)
|
[7] Reports published; date roll to next business day
|
07:00 Results available for the morning: P&L, risk, limits, liquidity
The interval between business close and morning publication is the batch window. Global banks often run several regional batches (Asia, Europe, Americas) or one global batch timed after the New York close, which leaves a short window before Asia opens.
Scheduling and dependencies. EOD batches have hundreds or thousands of jobs. They are managed by enterprise schedulers such as Control-M, Autosys or Tivoli Workload Scheduler, or by modern orchestrators such as Airflow. The scheduler holds the dependency graph: a job runs when its predecessors have completed successfully and its input conditions (files arrived, snapshot published) are met.
| Concept | Description |
|---|---|
| Dependency graph (DAG) | Jobs and the "must finish before" relationships between them |
| Critical path | The longest chain of dependent jobs; it determines when the batch finishes |
| Business date | The date being processed, which differs from the calendar date once processing passes midnight |
| Date roll | The step that moves systems to the next business date, after which new trades belong to the new day |
| Checkpoint/restart | The ability to restart a failed job without rerunning everything before it |
Example: critical path. Suppose these jobs and durations:
| Job | Duration | Depends on |
|---|---|---|
| A: trade cut-off | 10 min | - |
| B: market data snapshot | 30 min | - |
| C: lifecycle processing | 40 min | A |
| D: revaluation | 60 min | B, C |
| E: P&L | 20 min | D |
| F: VaR | 120 min | D |
| G: accounting | 45 min | D |
| H: reports | 15 min | E, F, G |
The critical path is A, C, D, F, H: 10 + 40 + 60 + 120 + 15 = 245 minutes. Speeding up accounting (G) does not shorten the batch. Speeding up VaR (F) does, until another path becomes critical (A, C, D, G, H takes 170 minutes).
Failure handling. A batch failure at 02:00 puts the morning's figures at risk. Good batch design includes:
- Idempotent, restartable jobs. A job rerun with the same business date and inputs produces the same result and does not double post.
- Completeness and control totals. Each job checks record counts and totals against its input before passing control.
- Clear status. A failed job stops its dependants; it does not let them run on partial data.
- Runbooks and support rotas. Operators know which failures can be fixed by a rerun and which need developers or business sign-off.
- Fallback. If a non-critical step cannot finish, decide in advance whether reports are published with yesterday's values and a clear flag.
Engineering view.
- Make the business date an explicit parameter of every job, never the system clock. This is what makes reruns of past dates possible.
- Separate "today's official run" from reruns and what-if runs in storage, with run IDs, so that corrections never overwrite published results silently.
- Monitor the critical path daily and track batch duration trends; volume growth slowly pushes completion time toward the morning deadline.
- Keep batch jobs small and well defined; a single monolithic job that takes four hours cannot be restarted halfway.
Key takeaways
- The EOD batch freezes inputs and produces consistent official figures for P&L, risk, accounting and liquidity.
- It is a dependency graph of jobs run by a scheduler; the critical path determines completion time.
- Jobs must take the business date as a parameter, be idempotent and restartable, and check completeness.
- Batch windows shrink as banks become global and volumes grow, which pushes processing toward intraday and incremental models.
🧪 Practice
- Beginner: Explain the difference between the business date and the calendar date in an EOD batch that finishes at 03:00.
- Intermediate: Using the critical path example, the VaR job is split into four parallel jobs of 35 minutes each, followed by a 10-minute aggregation. What is the new batch duration, and which path is now critical?
- Interview: "The EOD batch fails at 01:30 in the accounting step because of a bad trade. P&L and risk have already completed. What do you do, and how should the system be designed to make this easier?" Hint: consider isolating the bad trade (exception queue) versus fixing and rerunning, whether P&L and risk need rerunning if the trade is corrected, idempotent postings, and communication with finance before the GL deadline.
Intraday and Real-Time Processing
Why it matters. A batch tells you tomorrow morning what happened today. Many treasury decisions cannot wait. A payments treasurer must know now whether the bank has enough central bank reserves to release a large payment (Chapter 5.3). A trader needs the current position before hedging. A limit system must reject a trade that would breach a counterparty limit before it is executed, not the next morning. Regulators also expect banks to monitor intraday liquidity in real time (BCBS 248 monitoring tools). Intraday and real-time processing produce results continuously as events arrive.
Spectrum of processing models.
| Model | Latency | Typical treasury use |
|---|---|---|
| End-of-day batch | Hours (once a day) | Official P&L, risk, accounting, regulatory reports |
| Intraday batch (micro-batch) | Minutes (every 5-30 minutes) | Intraday risk refresh, liquidity dashboards, cash forecasts |
| Event-driven (streaming) | Seconds or less | Real-time cash positions, payment release decisions, position updates |
| Synchronous request/response | Milliseconds, inside a user or system transaction | Pre-trade limit checks, pricing requests, payment authorisation |
A typical event-driven architecture.
Sources Event backbone Processors Consumers
+-----------------+ +----------------+ +-------------------+ +-----------------+
| TMS: trades +--------->+ +--------->+ Position engine +---->+ Trader blotters |
| Payment hub: +--------->+ Message bus / +--------->+ (cash, securities)| +-----------------+
| sent, settled | | event log | +-------------------+ +-----------------+
| Swift: camt.054,+--------->+ (e.g. Kafka, +--------->+ Liquidity monitor +---->+ Liquidity |
| MT900/910 | | MQ) | | (reserve balance,| | dashboard, |
| RTGS: balance +--------->+ | | queued payments)| | alerts |
| notifications | | +--------->+ Limit engine +---->+ Payment release |
| Market data +--------->+ | +-------------------+ +-----------------+
+-----------------+ +----------------+
Example: real-time reserve position. A treasurer starts the day with EUR 2.0bn at the central bank. During the day, the liquidity monitor processes settlement events:
| Time | Event | Amount (EUR m) | Balance (EUR m) | Queued outgoing (EUR m) |
|---|---|---|---|---|
| 07:00 | Opening balance | 2,000 | 0 | |
| 08:15 | Incoming RTGS credit | +350 | 2,350 | 0 |
| 09:30 | Outgoing payment settled | -900 | 1,450 | 0 |
| 10:05 | Large payment queued (waiting for release) | 1,450 | 1,200 | |
| 10:06 | Alert: balance minus queue below threshold of 500 | |||
| 10:40 | Incoming repo cash | +600 | 2,050 | 1,200 |
| 10:41 | Treasurer releases queued payment | -1,200 | 850 | 0 |
The batch view would only show the end-of-day balance; the real-time view makes the 10:06 decision possible.
Batch and real-time together. Most banks run a hybrid: a real-time layer for operational decisions and an EOD layer for official, reconciled figures. The two views must be reconciled at the end of the day, because real-time processing can miss or duplicate events, and late corrections arrive after the stream has moved on.
| Concern | Real-time layer | EOD layer |
|---|---|---|
| Accuracy | Best available now; may be incomplete | Complete and reconciled |
| Market data | Live prices | Official snapshot |
| Corrections | Applied as new events, may arrive late | Included in rerun |
| Use | Decisions during the day | Official reporting, P&L, accounting |
Engineering view.
- Ordering and duplicates. Events can arrive late, duplicated or out of order. Use idempotency keys and sequence numbers per source, and decide how to handle late events (apply and recompute, or apply as a correction).
- State recovery. A streaming processor holds state (current balances). It must be able to rebuild that state after a restart, from a snapshot plus the event log (see event sourcing in Chapter 13.2).
- Start-of-day alignment. Initialise real-time positions from the reconciled EOD positions every morning, so errors do not accumulate over days.
- Latency budgets. For synchronous checks, such as pre-trade limits, define a maximum response time and a policy for timeouts (fail open or fail closed). For payments and limits, fail closed is the usual default.
Key takeaways
- Intraday and real-time processing support decisions that cannot wait for the batch: payment release, limit checks, hedging.
- Models range from micro-batches to event streaming to synchronous checks, with different latency and complexity.
- Banks run a hybrid: real-time for operations, EOD for official figures, reconciled daily.
- Ordering, duplicates, state recovery and start-of-day alignment are the main engineering concerns.
🧪 Practice
- Beginner: For each of these, say whether EOD batch, intraday batch, streaming or synchronous processing fits best: (a) regulatory LCR report, (b) pre-trade counterparty limit check, (c) central bank reserve balance dashboard, (d) intraday VaR refresh for the trading desk.
- Intermediate: A streaming position engine receives a settlement confirmation for a payment before the event saying the payment was sent, because the two come from different systems. How should the engine handle this?
- Interview: "The real-time cash position for a nostro account shows 15M more than the EOD reconciled position for the same day. How would you design the system so that such differences are detected and explained?" Hint: consider an automatic end-of-day comparison by account, keeping event-level detail on both sides so differences can be matched, typical causes (missing events, duplicates, late back-valued entries), and resetting the real-time state from EOD positions.
Position Keeping Engines
Why it matters. "How much do we have?" sounds simple, but treasury needs many versions of the answer: how much EUR cash will be on the nostro account at Deutsche Bank for value tomorrow, how many units of a bond the desk holds today on a trade-date basis, and how much the bank owes a counterparty across all its FX forwards. A position keeping engine maintains these positions by applying trade, settlement and cash events to aggregated balances, and it is the basis for cash management, funding decisions, limits and risk.
Position bases. The same trades produce different positions depending on which date is used.
| Basis | Position changes on | Answers | Used by |
|---|---|---|---|
| Trade-date | The day the trade is agreed | What have we committed to? | Traders, market risk, P&L |
| Settlement-date (contractual) | The day the trade is due to settle | What should we hold or have on the account on date X? | Cash management, funding |
| Actual settlement | The day settlement is confirmed | What do we actually hold now? | Operations, reconciliation |
| Projected (forecast) | Expected flows including forecasts | What will the balance probably be? | Liquidity management (Chapter 5.2) |
Example: a cash ladder. The engine projects cash by currency, account and value date. On Monday 19 October, for the USD nostro:
USD nostro at Correspondent X Mon 19 Tue 20 Wed 21 Thu 22 Fri 23
-------------------------------------------------------------------------------
Opening (actual / projected) 120.0 105.0 135.0 85.0 85.0
FX trades settling -40.0 +30.0 -50.0 -10.0
Money market maturities +25.0
Customer payments (forecast) +10.0
-------------------------------------------------------------------------------
Closing projected 105.0 135.0 85.0 85.0 85.0
(USD millions)
As each settlement is confirmed, the engine moves the flow from "projected" to "actual". If a flow fails to settle, the projected balance for the following days changes, and the treasurer must fund or invest the difference.
Processing model. Position engines apply incremental updates and avoid recalculating from all trades on every event.
Event (trade booked, amended, cancelled, settled, cash received)
|
v
1. Determine affected keys: (account, currency, value date), (book, instrument)...
2. Compute delta: new contribution minus old contribution of this trade version
3. Apply delta to position aggregates (atomically, with the event's sequence)
4. Record the delta with a reference to the event (audit, drill-down)
5. Publish position-changed events for dashboards and limit engines
Step 2 matters for amendments. If a trade's amount changes from 10M to 12M, the engine must remove the old 10M contribution and add the new 12M, not add 12M. Storing each trade's last contribution per key makes this possible.
Python sketch: applying a trade amendment.
from collections import defaultdict
from decimal import Decimal
positions = defaultdict(Decimal) # (account, ccy, value_date) -> balance
contrib = {} # trade_id -> {key: amount} of current version
def apply_trade_version(trade_id: str, flows: dict) -> None:
"""flows: {(account, ccy, value_date): amount} for the new trade version.
A cancellation passes an empty dict."""
old = contrib.get(trade_id, {})
for key in set(old) | set(flows):
delta = flows.get(key, Decimal("0")) - old.get(key, Decimal("0"))
if delta:
positions[key] += delta
contrib[trade_id] = flows
Engineering view.
- Recompute to verify. Incremental positions can drift because of missed or duplicated events. Recompute positions from the full trade population regularly (for example in the EOD batch) and compare with the incremental results.
- Concurrency. Many events update the same popular keys (the main USD nostro). Use per-key ordering (partitioning by key in a stream) or database-level atomic updates to avoid lost updates.
- Drill-down. Users always ask "what makes up this number?". Keep the per-trade contributions so any position can be broken down.
- Multiple bases from one event stream. Derive trade-date, settlement-date and actual positions from the same events with different rules, rather than separate feeds that can disagree.
Key takeaways
- A position keeping engine maintains aggregated holdings and cash balances from trade and settlement events.
- Trade-date, contractual settlement, actual settlement and projected positions answer different questions.
- Incremental updates must apply the difference between old and new trade versions; per-trade contributions make this and drill-down possible.
- Periodic full recomputation and per-key ordering protect against drift and lost updates.
🧪 Practice
- Beginner: A desk buys a bond on Monday for settlement on Wednesday. Show its securities position on Monday, Tuesday and Wednesday on a trade-date basis and on a settlement-date basis.
- Intermediate: Using the Python sketch, trace the
positionsdictionary for: an FX trade booked with USD -10M for value 21 Oct, amended to USD -12M for value 22 Oct, then cancelled. Confirm that all positions return to zero.- Interview: "The cash ladder for value tomorrow in the position engine differs from the sum of trades in the TMS by 3M. How do you investigate?" Hint: compare at trade level using per-trade contributions, look for trades missing on either side, amendments applied twice or as additions, value date or account mapping differences, and timing of the TMS extract compared with the engine's last event.
Calculation Grids and Distributed Compute
Why it matters. Some treasury calculations are too heavy for a single machine. A counterparty exposure simulation with 5,000 netting sets, 2,000 Monte Carlo paths and 100 time steps needs a billion netting set valuations. A historical VaR on 50,000 trades with 500 scenarios needs 25 million revaluations, before any sensitivities. These calculations must finish within the batch window, and some must also run on demand during the day. Calculation grids spread these calculations across hundreds or thousands of CPU cores.
Why risk calculations parallelise well. Most risk calculations are "embarrassingly parallel": the valuation of trade A under scenario 17 does not depend on the valuation of trade B under scenario 18. The work can be split into independent tasks, computed in parallel and aggregated at the end.
Portfolio x Scenarios
scen 1 scen 2 scen 3 ... scen 500
trade 1 [ task | task | task | ... | task ]
trade 2 [ task | task | task | ... | task ]
...
trade N [ task | task | task | ... | task ]
Partitioning options:
- by trade (each task: one block of trades, all scenarios) -> good cache reuse of trade data
- by scenario (each task: all trades, one block of scenarios) -> good reuse of market data
- by both (blocks of trades x blocks of scenarios) -> most flexible
Grid architecture.
| Component | Role |
|---|---|
| Client / job submitter | Defines the job (portfolio, scenarios, measures) and submits it |
| Scheduler / broker | Splits jobs into tasks, assigns tasks to workers, tracks status, retries failures |
| Workers (engines) | Load the pricing library, market data and trades, run tasks, return results |
| Data distribution | Delivers market data, trade data and static data to workers (shared cache, object storage, pre-loaded) |
| Result aggregation | Collects and combines task outputs (sums, vectors, cubes) |
Products and platforms used for this include IBM Spectrum Symphony and TIBCO GridServer in many banks, and increasingly Kubernetes-based schedulers, cloud batch services and frameworks such as Ray or Dask. GPUs are used for Monte Carlo workloads such as XVA, where the same calculation runs on many paths.
Example: sizing a grid. A nightly PFE run needs 1.2 billion netting-set valuations, each taking on average 0.5 ms of CPU time. The batch window for this job is 90 minutes.
Total CPU time = 1.2e9 x 0.5 ms = 6.0e5 s = ~167 CPU-hours
Window = 1.5 hours
Cores (ideal) = 167 / 1.5 = ~111 cores
With 70% efficiency (data loading, stragglers, retries):
Cores needed = 111 / 0.7 = ~160 cores
Practical issues.
| Issue | Description | Mitigation |
|---|---|---|
| Data distribution | Shipping market data and trades to thousands of workers can dominate runtime | Pre-load and cache, ship references not data, locality-aware scheduling |
| Stragglers | A few slow tasks delay the whole job | Smaller tasks, speculative re-execution, balanced partitioning by cost |
| Failures | Workers crash or are pre-empted | Idempotent tasks, automatic retries, checkpointing |
| Determinism | Results depend on task split or random seeds | Seeds per path, order-independent aggregation (Chapter 11.4) |
| Completeness | Missing task results produce plausible but wrong totals | Expected task manifest, aggregation refuses to publish incomplete results |
| Cost | Large grids sit idle outside the batch | Elastic or cloud capacity, sharing with other workloads |
Engineering view.
- Design tasks to be pure functions of their inputs (trades, market data snapshot ID, scenario IDs, model version). This makes retries, caching and reproducibility straightforward.
- Make the pricing library identical on all workers, versioned and recorded with each result.
- Use floating-point aggregation that does not depend on the order in which results arrive, or sort before summing; otherwise reruns differ in the last digits and reconciliations fail.
- Measure task duration distributions, not just averages; the slowest tasks determine job completion.
Key takeaways
- Grids spread embarrassingly parallel risk calculations (trades x scenarios x time steps) over many cores.
- A scheduler splits jobs into tasks, workers compute them, and an aggregation layer combines results.
- Data distribution, stragglers, failures, determinism and completeness are the main practical problems.
- Tasks should be pure, idempotent functions of versioned inputs, with order-independent aggregation.
🧪 Practice
- Beginner: Explain why a historical VaR calculation is "embarrassingly parallel", and name one step in the process that is not.
- Intermediate: Using the sizing example, the bank adds 40% more netting sets and wants to halve the window to 45 minutes. How many cores are needed at the same efficiency? Suggest two ways to reduce this number without changing the methodology.
- Interview: "Rerunning yesterday's XVA calculation on the grid gives a CVA that differs from the original by 0.3%. Nothing changed in the inputs. What could cause this?" Hint: consider random seeds tied to worker or task order, non-deterministic floating-point aggregation, different library versions on some workers, missing or duplicate tasks, and how to detect each one.
Scenario and Stress Engines
Why it matters. Treasury constantly asks "what if?": what happens to NII if rates rise 200bp, to the LCR if 20% of corporate deposits leave in a month, to the bond portfolio if credit spreads widen like in 2008, to collateral calls if the bank is downgraded by two notches. Regulators ask the same questions in prescribed formats (Chapter 16.1 covers regulatory stress tests and scenario design). A scenario engine is the infrastructure that defines scenarios, applies them consistently to the bank's portfolios and models, and stores the results so they can be compared and explained.
What a scenario is, technically. A scenario is a set of changes to the inputs of a calculation, with a horizon and possibly a path over time.
| Scenario element | Examples |
|---|---|
| Market shocks | Parallel +200bp shift, steepener, EUR/USD -15%, credit spreads +150bp, equity -30% |
| Behavioural assumptions | Deposit run-off rates, prepayment speeds, drawdown of committed facilities |
| Counterparty and rating events | Default of the largest counterparty, two-notch downgrade of the bank |
| Business assumptions | New business volumes, margins, funding mix |
| Time structure | Instantaneous shock, or a path over 1, 3, 12 or 36 months |
Shock definition. Shocks are usually applied to risk factors, not directly to prices, so that one scenario definition applies to every product.
| Shock type | Formula | Typical risk factors |
|---|---|---|
| Absolute | new = base + shock | Interest rates, spreads (in bp) |
| Relative | new = base x (1 + shock) | FX rates, equity prices, volatilities |
| Replacement | new = specified value | Fixings, specific prices |
Engine architecture.
+----------------------+ +-------------------------+ +------------------------+
| Scenario library | | Scenario application | | Calculation |
| - definitions +----->+ - expand to risk factor +----->+ - full revaluation |
| - versions, approval | | level shocks | | (grid) |
| - historical, | | - build shocked curves, | | - or sensitivity-based |
| hypothetical, | | surfaces, FX | | approximation |
| regulatory | | - apply behavioural | | - ALM / liquidity |
+----------------------+ | overrides | | projections |
+-------------------------+ +-----------+------------+
|
+-------------------------+ +-----------v------------+
| Analysis and reporting |<-----+ Result store |
| - base vs scenario | | (portfolio x scenario |
| - attribution by driver | | x measure x horizon) |
+-------------------------+ +------------------------+
Full revaluation versus sensitivity-based.
| Approach | How | Strength | Weakness |
|---|---|---|---|
| Full revaluation | Reprice every trade with the shocked market data | Captures non-linearity (options, convexity) exactly | Expensive: trades x scenarios valuations |
| Sensitivity-based (Taylor) | Approximate P&L = delta x shock + 1/2 gamma x shock^2 + ... | Fast; scenarios can be applied instantly | Inaccurate for large shocks and non-linear products |
| Hybrid | Sensitivities for linear products, full revaluation for options | Balances cost and accuracy | Two methods to maintain and reconcile |
Example: sensitivity-based versus full revaluation. A bond portfolio has a DV01 of -EUR 85,000 (it loses 85,000 per +1bp) and a convexity contribution of +EUR 30 per bp squared.
Shock +200bp
Linear: -85,000 x 200 = -17,000,000
With convexity: -17,000,000 + 30 x 200^2 = -17,000,000 + 1,200,000 = -15,800,000
Full revaluation (from the pricing engine) = -15,750,000 (for example)
The linear approximation overstates the loss by about 1.25M (8%); adding
convexity reduces the error to 50k. For options, the error can be much larger.
Engineering view.
- Scenarios as versioned data. Store scenario definitions as data with IDs, versions, owners and approval status, not as code or spreadsheet formulas. Every result must reference the exact scenario version.
- Consistent application. One scenario expansion service should produce shocked market data for all consumers (market risk, ALM, liquidity, capital). Different teams applying "the same" +200bp shock differently is a common source of inconsistent ALCO and risk reports.
- Base run first. Every scenario result is compared with a base run on the same data. Store base and scenario results together with the same input snapshot.
- Explainability. Management asks why a scenario result changed from last quarter. Design for attribution: change one input at a time (portfolio, market data, scenario definition, model version) and record each step.
Key takeaways
- A scenario is a versioned set of changes to market, behavioural, counterparty and business inputs, applied instantly or over a time path.
- Shocks are defined on risk factors (absolute, relative, replacement) so one definition applies to all products.
- Full revaluation is accurate but expensive; sensitivity-based methods are fast but miss non-linearity in large shocks.
- Store scenarios as approved, versioned data, apply them through one shared service, and keep base and scenario results together for attribution.
🧪 Practice
- Beginner: Define a scenario "EUR rates +100bp parallel, EUR/USD -10%, deposit outflow 5%" as a structured record with risk factor shocks and behavioural overrides.
- Intermediate: A portfolio has DV01 = -EUR 40,000 per bp and a convexity term of EUR 12 per bp squared. Estimate the P&L for shocks of +50bp, +200bp and -200bp with and without convexity. For which shock is the linear approximation least reliable, and why?
- Interview: "Market risk and treasury ALM both report the impact of a +200bp shock on the banking book bond portfolio, and their numbers differ by 20%. How would you investigate, and how would you prevent this?" Hint: compare portfolio scope, shock definitions (instantaneous vs over a horizon, which curve is shocked, floors), valuation method (full revaluation vs sensitivities), and market data snapshot; consider a shared scenario expansion service.
<a id="134-reconciliation-and-controls"></a>
13.4 Reconciliation and Controls
This subchapter covers the mechanisms that let a bank trust its numbers: reconciling cash and positions against external and internal sources, the algorithms that match records, the workflows that resolve breaks, the four-eyes principle, and the audit trails that show who did what and when.
Cash and Nostro Reconciliation
Why it matters. A bank holds accounts with other banks, called nostro accounts ("ours", held with you), in every currency it deals in (Chapter 1.4). The bank keeps its own record of what should be on each nostro account, based on its trades and payments. The correspondent bank keeps the real account and sends statements. If the two disagree, something is wrong: a payment failed, a counterparty paid the wrong amount, a fee was charged, a fraudulent payment left the account, or a trade was booked incorrectly. Unreconciled differences are real money at risk, so nostro reconciliation is one of the oldest and most important controls in bank operations. Regulators and auditors check that it is done daily and that differences are resolved promptly.
The two sides.
Internal side ("our books") External side ("their books")
+--------------------------------+ +--------------------------------+
| Expected flows from TMS, | | Correspondent's statement: |
| payment hub, core banking | | MT950 / MT940 / camt.053 (EOD) |
| (ledger entries on the nostro | | MT900/910, camt.054 (intraday |
| mirror account) | | debit/credit notifications) |
+---------------+----------------+ +----------------+---------------+
| |
+--------------------+-----------------------+
v
+---------------------------+
| Reconciliation engine |
| - load and normalise |
| - match (rules) |
| - generate breaks |
+-------------+-------------+
v
Matched items Breaks (unmatched or partially matched)
(closed) -> investigation workflow
The internal record of a nostro account is often called the mirror account or nostro ledger: an account in the bank's own books that should mirror the balance at the correspondent.
Two levels of reconciliation.
| Level | Question | Example |
|---|---|---|
| Balance reconciliation | Does our closing balance equal the statement's closing balance? | Our USD mirror: 105,000,000.00; statement: 104,999,975.00 -> difference 25.00 |
| Transaction (item) reconciliation | Can every movement on each side be matched with a movement on the other? | Statement shows a 25.00 debit "CHARGES" with no internal entry |
Balance reconciliation is a quick check; item reconciliation explains any difference and finds offsetting errors that leave the balance unchanged.
Typical break types.
| Break | Description | Typical cause | Typical resolution |
|---|---|---|---|
| Missing on statement | We expected a payment or receipt; the bank has not booked it | Failed or delayed settlement, counterparty did not pay | Chase counterparty, claim interest compensation |
| Missing in our books | Statement shows a movement we did not expect | Fees, interest, unexpected receipt, fraud | Identify, book, return or escalate |
| Amount difference | Matched by reference but amounts differ | Charges deducted (SHA/BEN, Chapter 12.1), wrong amount paid | Book charges, claim difference |
| Value date difference | Same amount, different value date | Late settlement | Claim interest for late value (back-valuation request) |
| Duplicate | Two entries for one business event | Payment sent twice, duplicate booking | Recall payment, reverse booking |
Example. Internal entries and statement lines for a EUR nostro on 19 October:
| Internal ref | Internal amount | Statement ref | Statement amount | Result |
|---|---|---|---|---|
| FX-778812 | +5,000,000.00 | FX-778812 | +5,000,000.00 | Matched |
| PAY-2026-001234 | -50,000.00 | PAY2026001234 | -50,000.00 | Matched (reference normalised) |
| MM-55120 | +20,000,000.00 | Break: missing on statement | ||
| CHG/OCT | -125.00 | Break: missing in our books (fees) | ||
| PAY-2026-001240 | -1,000,000.00 | PAY-2026-001240 | -1,000,020.00 | Break: amount difference of 20.00 (charges debited with the payment) |
Engineering view.
- Ingest statements in every format the correspondents send (MT950, MT940, camt.053, camt.054) into one normalised model; Chapter 12 covers these formats.
- Check statement sequence numbers and opening/closing balance continuity: the opening balance of today's statement must equal the closing balance of yesterday's. A gap means a missing statement.
- Run reconciliation intraday as well as at end of day when intraday notifications are available; detecting a missing receipt at 10:00 leaves time to act before cut-offs.
- Age breaks from the day they occur and escalate by age and amount; old breaks are an audit finding and may need provisioning.
Key takeaways
- Nostro reconciliation compares the bank's mirror account with the correspondent's statement.
- Balance reconciliation checks the totals; item reconciliation matches individual movements and explains differences.
- Common breaks are missing items on either side, amount and value date differences, and duplicates.
- Statement continuity checks, intraday reconciliation and break ageing are essential controls.
🧪 Practice
- Beginner: The mirror account shows a closing balance of USD 48,250,000 and the statement shows USD 48,249,950. List three possible explanations and what you would check for each.
- Intermediate: Yesterday's statement had sequence number 211 and closing balance EUR 12,400,000. Today's statement has sequence 213 and opening balance EUR 13,100,000. What has happened, and what should the reconciliation system do?
- Interview: "Our nostro reconciliation has 4,000 open breaks, some older than a year. How would you clean this up and prevent it from happening again?" Hint: consider categorising breaks by type, age and amount, finding systematic causes (for example unbooked fees or a reference format issue) that explain many breaks at once, improving matching rules, write-off policy with approval, and ageing KPIs with escalation.
Position and Trade Reconciliation
Why it matters. Cash is not the only thing that can disagree. The same trade lives in the front office system, the back office, the risk engine, the accounting subledger and the counterparty's books. Securities positions exist in the bank's books and at the custodian or central securities depository. Every handover between systems can lose, duplicate or alter a record. Position and trade reconciliation checks that these copies agree. Many past trading losses and operational incidents went undetected for long periods because reconciliations were missing or breaks were ignored.
Types of reconciliation.
| Type | Compares | Purpose |
|---|---|---|
| Front-to-back | Trades in TMS front office vs back office / settlement systems | Every trade traded is processed and settled |
| Front-to-risk | Trades and positions in TMS vs risk engine | Risk covers all positions with the same terms |
| Front-to-finance | Positions and P&L vs subledger and GL | Accounting reflects trading activity |
| Custody / depository | Securities positions in books vs custodian or CSD statements (MT535, semt.002) | We hold what we think we hold |
| Counterparty | Trades vs counterparty confirmations, portfolio reconciliation for OTC derivatives | Both sides agree on the terms (required periodically under EMIR for uncleared OTC derivatives) |
| Inter-company | Positions between legal entities of the same group | Internal trades net to zero on consolidation |
Example: front-to-risk trade reconciliation.
Source A: TMS (12,450 live trades) Source B: risk engine (12,447 trades)
Step 1: Population comparison (by trade_id)
In A only : 4 trades -> missing in risk (not loaded? mapping failed?)
In B only : 1 trade -> in risk but not live in TMS (cancelled, not propagated)
In both : 12,446
Step 2: Attribute comparison for 12,446 common trades
notional, currency, maturity, rate, book, counterparty
Notional mismatch : 2 (amendment not propagated)
Book mismatch : 7 (book transfer done after the risk extract)
Fully matched : 12,437
Two steps are typical: first the population (does each record exist on both sides?), then the attributes (do the fields agree, within tolerances?).
Aggregate versus detailed reconciliation. Comparing totals (sum of notionals by book, count of trades) is cheap and catches large problems quickly, but offsetting errors can hide in totals. Detailed reconciliation at trade level finds every difference but is more expensive. A common design runs aggregate checks first as a gate and detailed checks afterwards for investigation.
Engineering view.
- Agree a common key between systems. If systems use different trade IDs, maintain a cross-reference, and treat missing cross-references as breaks in their own right.
- Compare data as of the same cut-off. Many "breaks" are timing differences: a trade booked after one system's extract but before the other's. Record extract timestamps and classify timing breaks separately.
- Define tolerances per field: exact match for identifiers and currencies, small absolute tolerances for amounts that pass through rounding, and none for dates.
- Automate reconciliations as part of the batch (Chapter 13.3) and publish break counts as metrics; an unmonitored reconciliation is not a control.
Key takeaways
- Trades and positions are reconciled between internal systems (front, back, risk, finance) and with external parties (custodians, counterparties).
- Reconciliation checks the population first, then the attributes, within defined tolerances.
- Aggregate checks are fast gates; detailed checks find and explain individual differences.
- Common keys, consistent cut-offs and classification of timing differences make reconciliations useful rather than noisy.
🧪 Practice
- Beginner: Explain why reconciling only the total notional per book between the TMS and the risk engine might miss real problems. Give an example.
- Intermediate: A custody reconciliation shows the bank's books hold 10,000,000 nominal of a bond, while the custodian's statement shows 9,000,000. List the possible causes (at least four) and the data you would request to investigate.
- Interview: "Design a generic reconciliation service that can be configured for front-to-risk, front-to-finance and custody reconciliations." Hint: consider source adapters and normalisation, configurable keys, field mappings and tolerances, population and attribute comparison, break storage with classification, cut-off handling, and APIs for the break management workflow.
Matching Algorithms
Why it matters. A reconciliation is only as good as its matching. Records on two sides rarely look identical: references are truncated or reformatted, amounts differ by charges, one side shows a single payment while the other shows it split into several, and dates differ by a day. Strict matching leaves thousands of false breaks for operations staff to clear by hand. Loose matching pairs the wrong items and hides real problems. Matching algorithms balance these two risks, and typical reconciliation platforms (for example SmartStream TLM, Duco, Gresham, or in-house engines) are configured with layered matching rules.
Matching cardinalities.
| Type | Description | Example |
|---|---|---|
| One-to-one | One record on each side | One payment, one statement line |
| One-to-many | One record matches several on the other side | One bulk payment in our books, three statement debits |
| Many-to-one | Several records match one on the other side | Three FX trades netted into one settlement |
| Many-to-many | Groups on both sides | Several partial settlements against several trades |
Rule passes. Engines apply rules in order, from strictest to loosest, and remove matched items after each pass:
Pass 1 Exact: reference = reference AND amount = amount AND value date = value date
Pass 2 Normalised: normalise(reference) equal (strip spaces, dashes, case, leading zeros)
AND amount equal AND value date equal
Pass 3 Tolerance: same reference, |amount difference| <= 50.00 or 0.01%,
value date within +/- 1 business day
Pass 4 Aggregate: one item vs a group on the other side with the same reference
root or counterparty, where amounts sum to the single item
Pass 5 Fuzzy: no reliable reference; similarity score on counterparty name,
amount and date above a threshold -> proposed match, needs human approval
Remainder Breaks
Each match records which rule produced it. Matches from loose rules (fuzzy, tolerance) are often flagged as proposed and need confirmation, while exact matches close automatically.
Python sketch: one-to-one matching with normalisation and tolerance.
import re
from decimal import Decimal
def norm_ref(ref: str) -> str:
return re.sub(r"[^A-Z0-9]", "", ref.upper()).lstrip("0")
def match(internal: list[dict], external: list[dict], tol=Decimal("50.00")):
"""Each record: {'id', 'ref', 'amount', 'value_date'}. Returns matches and leftovers."""
by_ref = {}
for e in external:
by_ref.setdefault(norm_ref(e["ref"]), []).append(e)
matches, unmatched = [], []
for i in internal:
candidates = by_ref.get(norm_ref(i["ref"]), [])
# prefer exact amount, then the smallest difference within tolerance
best = min(
(e for e in candidates
if abs(e["amount"] - i["amount"]) <= tol and e["value_date"] == i["value_date"]),
key=lambda e: abs(e["amount"] - i["amount"]),
default=None,
)
if best:
rule = "EXACT" if best["amount"] == i["amount"] else "TOLERANCE"
matches.append((i["id"], best["id"], rule))
candidates.remove(best) # each external item matches only once
else:
unmatched.append(i)
leftover_external = [e for lst in by_ref.values() for e in lst]
return matches, unmatched, leftover_external
The sketch is greedy: it takes internal records in order and gives each the best available candidate. When several internal records compete for the same candidates, the order can change the result. Production engines either assign optimally (for example by sorting all candidate pairs by quality first) or flag ambiguous groups for review.
The many-to-many problem. Finding which subset of items on one side sums exactly to an item on the other side is the subset sum problem, which is NP-complete. With 40 candidate items there are about one trillion subsets. Engines therefore restrict the search:
- group candidates by a shared attribute first (counterparty, reference root, value date);
- limit group size (for example at most 10 items per side);
- use time limits and propose, rather than auto-confirm, results.
Example. A counterparty pays 1,250,000.00 in one transfer to settle three invoices of 400,000.00, 350,000.00 and 500,000.00 sitting in the internal ledger with the same counterparty and value date. Pass 4 finds that the three internal items sum to the single statement line and creates a many-to-one match.
Measuring matching quality.
| Metric | Meaning | Target direction |
|---|---|---|
| Auto-match rate | Share of items matched without human action | Higher |
| False match rate | Share of automatic matches later reversed by users | Very low |
| False break rate | Breaks that users close as "matched manually, no issue" | Lower |
| Time to match | Delay between item arrival and match | Lower |
A high auto-match rate with a rising false match rate means the rules have become too loose.
Key takeaways
- Matching handles one-to-one, one-to-many, many-to-one and many-to-many relationships.
- Engines apply rule passes from exact to loose, recording which rule matched each pair; loose matches are proposed for human approval.
- Normalisation of references and tolerances for amounts and dates remove most false breaks.
- Many-to-many matching is a subset sum problem; restrict it by grouping, size limits and time limits, and monitor false match rates.
🧪 Practice
- Beginner: Normalise the references
PAY-2026-001234,pay 2026 001234andPAY2026001234/Xusing thenorm_reffunction. Which ones match? Is the third one a problem?- Intermediate: Extend the Python sketch with a second pass that matches remaining internal items to groups of up to three external items with the same counterparty and value date whose amounts sum exactly to the internal amount.
- Interview: "Operations complains that the reconciliation tool produces too many breaks, and someone proposes raising the amount tolerance from 50 to 5,000. What is your response?" Hint: consider analysing break categories first, the risk of hiding real differences (charges, partial payments, fraud), tolerances relative to the amount or per currency, separate rules for known charge patterns, and tracking false match rates after any change.
Break Management Workflows
Why it matters. Matching finds differences, but somebody has to investigate and resolve them. A break may be a timing difference that clears tomorrow, a fee that needs booking, a failed payment that needs chasing, or a sign of fraud. Without a structured workflow, breaks pile up in spreadsheets and email, old breaks are forgotten, and nobody can show an auditor what happened to a difference from six months ago. Break management turns each difference into a tracked case with an owner, a category, a deadline and a resolution.
Break lifecycle.
+--------+ assign +-------------+ root cause +--------------+
Matching ---> | NEW +--------------->+ IN PROGRESS +--------------->+ PENDING |
engine +---+----+ +------+------+ identified, | RESOLUTION |
| | action taken | (awaiting |
| auto-clear | escalate | booking, |
| (timing break v | payment, |
| matched next day) +-------------+ | approval) |
| | ESCALATED +-------------->+------+-------+
| +-------------+ |
v v
+--------+ +--------------+
| CLOSED |<---------------------------------------------+ RESOLVED |
+--------+ verification (e.g. matched after +--------------+
correcting entry, checker approval)
Attributes of a break case.
| Attribute | Purpose |
|---|---|
| Category and sub-category | Root cause type (timing, fees, missing booking, failed settlement, duplicate, unknown) for reporting and automation |
| Amount and currency (with base-currency equivalent) | Prioritisation and risk reporting |
| Age (business days) | Escalation and KPIs |
| Owner and team | Accountability |
| Linked items | The unmatched records on each side, and any correcting entries |
| Comments and evidence | Investigation history, emails, Swift queries (MT n95/n96, camt.026-029) |
| Resolution type and approver | How the break was closed and who approved it |
Prioritisation and escalation. Not all breaks are equal. A typical policy combines amount and age:
| Age \ Amount (EUR equivalent) | < 10k | 10k - 1M | > 1M |
|---|---|---|---|
| 0-2 days | Team queue | Team queue | Team lead alerted same day |
| 3-10 days | Team queue | Team lead | Head of operations |
| 11-30 days | Team lead | Head of operations | Head of operations and finance; possible provision |
| > 30 days | Head of operations; possible write-off | Finance, provisioning review | Senior management, risk committee |
Breaks with suspected fraud skip the queue and go to a dedicated team immediately, whatever their amount.
Resolution types. Each closure needs a reason that can be audited:
- Matched after correction: a correcting entry was booked and the items now match;
- Timing: cleared automatically when the late item arrived;
- Claimed and received: funds or interest compensation recovered from a counterparty;
- Written off: small differences closed against a P&L account, within approval limits and with a second approver;
- Not a break: false break due to a matching rule gap (should feed back into rule improvements).
Engineering view.
- Link breaks to their source items by ID, and close them automatically when matching later succeeds, with the matching rule recorded.
- Track resolution data in structured fields, not just free text, so that recurring root causes can be analysed and automated (for example auto-booking recurring correspondent fees).
- Enforce maker-checker for write-offs and manual matches above defined amounts (see the next topic).
- Publish ageing reports and KPIs (open breaks by age, amount, category, team) daily; these are reviewed by management and auditors.
Key takeaways
- Break management turns each reconciliation difference into a tracked case with owner, category, age and resolution.
- Prioritisation combines amount and age; suspected fraud is escalated immediately.
- Every closure has an auditable resolution type; write-offs need approval within limits.
- Structured root cause data lets the bank remove recurring breaks at the source.
🧪 Practice
- Beginner: Classify these breaks by category: (a) a 35 EUR debit labelled "CHARGES", (b) a 2M receipt expected today that arrives tomorrow, (c) the same 500k payment appearing twice on the statement.
- Intermediate: Design the data model (tables and key fields) for break cases, including links to the unmatched items, comments, status history and approvals.
- Interview: "Thirty percent of breaks each month are correspondent fees that operations books manually. How would you use the break management data to reduce this?" Hint: consider analysing fee patterns by correspondent and transaction type, automatic booking rules with tolerances and approval, using fee schedules as reference data, and measuring the effect on break volumes and false matches.
Four-Eyes Principle and Maker-Checker
Why it matters. One person acting alone can make a mistake, or commit fraud, with nobody noticing until it is too late. A trader can enter a trade with the wrong amount; an operator can change a counterparty's bank account to their own; an administrator can raise their own access rights. The four-eyes principle requires that critical actions are performed by one person and verified by a second, independent person before they take effect. In systems, this is implemented as maker-checker (also called dual control or dual authorisation): the maker prepares a change, and the checker approves or rejects it. Regulators and auditors expect it for payments, reference data, limits, manual adjustments and access changes.
How maker-checker works.
Maker System Checker
----- ------ -------
Creates / changes record --> Stores change as PENDING
(original remains active)
Validates: maker != checker,
checker has authority for this
type and amount
-----------> Reviews: sees the
before/after diff
<----------------------------------------- Approves / rejects
APPROVED: change applied atomically,
audit record written
REJECTED: change discarded, reason kept
Notifies maker
Typical controlled actions in treasury.
| Action | Why it needs four eyes | Typical additional controls |
|---|---|---|
| Release of manual payments | Direct loss and fraud risk | Amount-based approval levels, call-back for new beneficiaries |
| SSI and beneficiary changes | Classic fraud vector: redirecting funds | Out-of-band verification with counterparty |
| Limit creation and changes | Exceeding risk appetite | Approval by risk function, not the business |
| Manual market data overrides | P&L manipulation | Independent price verification |
| Manual journal entries and write-offs | Hiding losses | Approval limits, finance review |
| Trade amendments after confirmation | Hiding errors or unauthorised trading | Middle office review, reconfirmation |
| User access and role changes | Privilege escalation | Approval by line manager and system owner |
| Production configuration changes | Unintended system behaviour | Change management (Chapter 15.4) |
Designing maker-checker correctly.
- Segregation enforced by the system. The system, not the user, checks that maker and checker are different people. Also check that they do not share a single shared or generic account.
- Authority levels. Approval rights depend on the action and the amount. For example, payments up to 1M need one checker, up to 50M a senior checker, and above that two senior checkers.
- Show the difference. The checker must see exactly what changes (before and after values). Approving a screen that just says "SSI updated" is a rubber stamp.
- Atomic application. The approved change is applied exactly as reviewed. If the record changed in between, the approval is invalid.
- No self-approval via workarounds. Users must not be able to create a second identity, act as a delegate for their own requests, or edit a pending change after approval.
Example: an approval check in code.
from decimal import Decimal
APPROVAL_LEVELS = [ # (max amount, required role, number of checkers)
(Decimal("1000000"), "PAYMENT_CHECKER", 1),
(Decimal("50000000"), "SENIOR_CHECKER", 1),
(None, "SENIOR_CHECKER", 2),
]
def can_release(payment, approvals, users) -> bool:
level = next(l for l in APPROVAL_LEVELS if l[0] is None or payment.amount <= l[0])
_, role, needed = level
valid = {
a.user_id for a in approvals
if a.user_id != payment.created_by # maker cannot approve
and role in users[a.user_id].roles # checker has authority
and a.payment_version == payment.version # approved exactly this version
}
return len(valid) >= needed
The payment_version check stops a pending payment from being changed after approval.
Limits of four eyes. Checkers under time pressure approve without reading, especially when volumes are high, which is known as rubber-stamping. Collusion between two people defeats the control. Banks complement maker-checker with sampling reviews of approvals, monitoring of approval times (approvals in a few seconds on complex changes are a warning sign), job rotation and mandatory leave.
Key takeaways
- The four-eyes principle requires critical actions to be prepared by one person and approved by another independent person.
- Maker-checker holds changes as pending until approved and applies them atomically, exactly as reviewed.
- Segregation and authority levels must be enforced by the system; checkers must see the before/after difference.
- Rubber-stamping and collusion are the main weaknesses; monitoring and other controls complement the principle.
🧪 Practice
- Beginner: List four treasury actions that should require maker-checker, and explain the risk each one controls.
- Intermediate: A user creates a payment of 45M; a senior checker approves it; the maker then edits the beneficiary name to fix a typo. Using the code example, explain what happens and why that is the correct behaviour.
- Interview: "How would you detect rubber-stamping in a payment approval system?" Hint: consider approval time relative to the complexity of the change, approval rates close to 100% for particular checkers, approvals clustered in bursts, approvals of changes later found to be wrong, and how to feed these signals into reviews without creating perverse incentives.
Audit Trails
Why it matters. When something goes wrong in a bank (a wrong payment, a misreported liquidity figure, an unexplained P&L jump, a suspected fraud), the first questions are always: who did what, when, from where, and with which data? Regulators, internal audit and external auditors also ask these questions as a matter of routine, and laws require records to be kept for years (for example, MiFID II requires many transaction records to be kept for at least five years, and record retention rules for broker-dealers in the US prescribe tamper-resistant storage). An audit trail is the chronological, tamper-evident record of actions and changes in a system. Without it, a bank cannot prove that its controls worked.
What an audit trail records.
| Element | Example |
|---|---|
| Who | User ID, or technical account for system actions, and the user on whose behalf it acted |
| What | Action type (create, amend, approve, release, delete, login, export) and the object affected |
| Before and after | Old and new values of the changed fields |
| When | Timestamp from a synchronised clock (UTC), and the business date |
| Where | Application, host, IP address, session ID |
| Why | Reason code or comment, linked ticket or approval |
| Outcome | Success, failure, rejected by control |
Example: an audit record.
{
"audit_id": "a3f1c9e2-6b7d-4e1a-9f0c-2d8b7e5a4c11",
"timestamp_utc": "2026-10-19T09:14:27.512Z",
"business_date": "2026-10-19",
"actor": {"user_id": "u.rossi", "role": "SSI_MAKER", "session": "s-88213", "ip": "10.12.4.77"},
"action": "SSI_AMEND_REQUESTED",
"object": {"type": "SSI", "id": "SSI-C10442-USD", "version_from": 6, "version_to": 7},
"changes": [
{"field": "beneficiary_account", "old": "021000021-123456789", "new": "026009593-987654321"}
],
"reason": "Counterparty notice ref CN-2026-1187, call-back pending",
"outcome": "PENDING_APPROVAL",
"prev_hash": "9c2e...f01a",
"hash": "47bd...a9e3"
}
Tamper evidence. An audit trail that an administrator can quietly edit is not much use. Common techniques:
| Technique | How it works |
|---|---|
| Append-only storage with restricted access | No update or delete privileges for anyone, including application accounts; separate administrators for the audit store |
| WORM storage | Write-once-read-many storage (for example object storage with retention lock) prevents modification until retention expires |
| Hash chaining | Each record includes the hash of the previous one; changing any record breaks the chain from that point on |
| External anchoring | Periodic hashes of the chain are stored in a separate system or with a third party, so the whole chain cannot be rewritten |
| Separate log pipeline | Audit events are shipped immediately to a central security log platform (SIEM) outside the application's control |
Hash chaining
+-----------+ +-----------+ +-----------+
| record 1 | | record 2 | | record 3 |
| data | | data | | data |
| prev: 0 |---->| prev: h1 |---->| prev: h2 |
| h1=H(...) | | h2=H(...) | | h3=H(...) |
+-----------+ +-----------+ +-----------+
Modifying record 2 changes h2, so record 3's "prev" no longer matches.
Audit trail versus application logs.
| Aspect | Audit trail | Application (debug) logs |
|---|---|---|
| Purpose | Accountability, evidence, compliance | Troubleshooting, operations |
| Content | Business actions, before/after values | Technical events, errors, performance |
| Retention | Years, set by regulation | Days to months |
| Integrity | Tamper-evident, access restricted | Best effort |
| Completeness | Every relevant action must be recorded; a failure to write should block the action | Sampling and dropping are acceptable |
Engineering view.
- Write the audit record in the same transaction as the business change (or via a transactional outbox, Chapter 12.4); otherwise a change can happen without an audit record.
- Record read access to sensitive data (exports of client data, payment files) as well as changes; many investigations are about who saw or downloaded something.
- Use synchronised clocks and UTC; investigations across systems depend on ordering events correctly.
- Do not put secrets or unnecessary personal data in audit records; they are kept for years and read by many people. Mask account numbers where full values are not needed.
- Test that audit trails can actually be queried: an investigator must be able to answer "show every change to this SSI and who approved it" in minutes.
Key takeaways
- An audit trail records who did what, when, from where, why, with before and after values and the outcome.
- It must be complete (written atomically with the change) and tamper-evident (append-only, WORM, hash chaining, external anchoring).
- Audit trails differ from application logs in purpose, retention and integrity requirements.
- Retention periods are set by regulation; design for efficient investigation queries over years of data.
🧪 Practice
- Beginner: List the fields you would include in an audit record for the release of a payment, and explain why each is needed.
- Intermediate: Write a short Python function that verifies a hash-chained list of audit records and returns the first record whose
prev_hashdoes not match the hash of the record before it.- Interview: "A regulator asks for evidence that nobody changed limit settings in the treasury system without approval over the last three years. How does your system design let you provide this?" Hint: consider complete audit records for every limit change linked to maker-checker approvals, tamper evidence for the audit store, coverage of direct database changes (database-level auditing, restricted access), retention, and a reproducible query or report that auditors can verify.
<a id="14-accounting-and-financial-reporting-for-treasury"></a>