WarpCore / Documentation

Liquidity Simulator

SOURCE · docs/specs/liquidity-sim-v1.md · synced from the engineering repo

Liquidity Simulator — Specification v1

Status: Draft for bank treasury review Tool: warpcore-liqsim Document version: 2026-04-11 Reference implementation: crates/warpcore-liqsim

1. Abstract

Banks running cross-border settlement today must pre-fund correspondent nostro accounts in every currency in which they transact. BIS surveys put the global total locked in nostro at roughly $27 trillion. This capital earns little or no return. It exists because under T+2 (and even T+0 batch) settlement, there is a window during which a bank’s outflows have committed but its offsetting inflows have not. The bank must therefore hold enough balance to absorb the deepest trough of its intraday net outflow, plus a safety buffer against volatility.

Under WarpCore’s atomic settlement model, every payment is settled in a single Kaspa transaction. Either both legs of a cross-border transfer commit or neither does. The intraday trough collapses because the bank only needs funds to cover the single instantaneous transaction in flight, not the cumulative net of all transactions through a settlement window.

This specification defines a simulator that replays any historical payment flow under both models and reports the difference — the capital unlock a bank would realise by migrating to WarpCore.

2. Non-goals

  • The simulator does not value the unlocked capital. A CFO applies their own cost-of-capital to the result (typically 5–8% for a large bank).
  • It does not model intraday credit lines, overdraft facilities, or central bank standing facilities, which some banks use to smooth troughs. The number produced is therefore a strict upper bound on the nostro requirement, not a point estimate of actual held balance.
  • It does not simulate operational risk, counterparty risk, or FX risk.

3. Why this is honest

  • Uses rust_decimal everywhere. No floats, no rounding drift.
  • Rejects inputs that would allow cherry-picking: empty flow sets, null currencies, negative amounts, self-transfers, unsorted timestamps.
  • Reports per-participant, per-currency numbers. A skeptic can replay the simulator line-by-line.
  • The safety buffer is an explicit parameter rather than a hidden constant. Banks that use 1.50 instead of 1.25 can re-run with their own number.
  • Every test vector ships with the source.

4. Input format

A CSV file with five columns and a header row. Comment lines start with #. Blank lines are skipped.

timestamp,debtor_bic,creditor_bic,currency,amount
2024-01-01T09:00:00Z,DEUTDEFF,CHASUS33,EUR,5000000
2024-01-01T09:00:01Z,CHASUS33,DBSSSGSG,USD,2500000
ColumnFormat
timestampRFC3339 UTC; must be monotonically non-decreasing
debtor_bicISO 9362 BIC of the sender
creditor_bicISO 9362 BIC of the receiver (must differ from debtor)
currencyISO 4217 alpha-3 code
amountPositive decimal; no floats, no currency symbol

5. Model definitions

5.1 Traditional correspondent banking

For each (BIC, currency) pair, walk the flows in timestamp order and maintain a running cumulative net:

net_{t+1} = net_t - outflow_{t+1} + inflow_{t+1}

The peak outflow is max_{t} |min(net_t, 0)| — the absolute value of the deepest negative trough. The traditional pre-funding requirement for that (BIC, currency) is:

traditional = peak_outflow * safety_buffer

Default safety_buffer is 1.25. This matches published BIS guidance on intraday liquidity buffers.

5.2 WarpCore atomic PvP

Every flow commits atomically. The participant does not need pre-committed liquidity beyond the instantaneous transaction value, which is captured elsewhere (on-demand funding at execution time). For the purpose of this simulator, the atomic pre-funding requirement is zero:

atomic = 0

More sophisticated simulations can substitute a non-zero value representing, e.g., a 15-second pre-commitment window for the largest expected transaction. That refinement lives in v2.

5.3 Capital unlock

unlock = traditional - atomic

6. Output format

6.1 Per-participant requirement

{
  "bic": "DBSSSGSG",
  "currency": "AED",
  "peak_outflow": "18000000",
  "traditional_prefunding": "22500000",
  "atomic_prefunding": "0",
  "unlock": "22500000",
  "unlock_report_currency": "6126750.00"
}

6.2 Simulation report

{
  "flow_count": 10,
  "participant_count": 4,
  "currency_count": 3,
  "window_start": "2024-01-01T09:00:00Z",
  "window_end": "2024-01-01T09:00:09Z",
  "safety_buffer": "1.25",
  "requirements": [ ... ],
  "totals_by_currency": {
    "AED": { "traditional_prefunding": "26875000", "atomic_prefunding": "0", "unlock": "26875000" },
    "EUR": { "traditional_prefunding": "18437500", "atomic_prefunding": "0", "unlock": "18437500" },
    "USD": { "traditional_prefunding": "15625000", "atomic_prefunding": "0", "unlock": "15625000" }
  },
  "grand_total_report_currency": "42855562.50",
  "report_currency": "USD"
}

7. FX conversion

If --report-currency and at least one --rate are supplied, the simulator converts per-currency unlocks to a single report currency for a grand total. The conversion is:

unlock_report = unlock_native * rate_native_to_report

FX rates are expressed as decimals (AED=0.2723 means 1 AED = 0.2723 USD when the report currency is USD). The report currency itself has an implicit 1.00 rate.

8. CLI

warpcore-liqsim <flows.csv> [OPTIONS]

OPTIONS:
  --format <human|json>      Output format (default: human)
  --safety-buffer <DECIMAL>  Traditional model buffer (default 1.25)
  --report-currency <CCY>    Report totals in this currency (requires --rate)
  --rate <CCY=DECIMAL>       FX rate. Can be given multiple times.
  -h, --help                 Show help.

EXIT CODES:
  0  simulation succeeded
  1  simulation failed
  2  invocation error

9. Worked example

Given the 10-flow CSV in tests/ (4 banks, 3 currencies), run:

warpcore-liqsim flows.csv \
  --report-currency USD \
  --rate EUR=1.08 \
  --rate AED=0.2723 \
  --rate USD=1.0

Output (abridged):

Per-currency totals:
  CCY                TRADITIONAL                  ATOMIC                  UNLOCK
  AED              26,875,000.00                       0           26,875,000.00
  EUR              18,437,500.00                       0           18,437,500.00
  USD              15,625,000.00                       0           15,625,000.00

Grand total unlock (USD): 42,855,562.50

Top 5 participant unlocks:
  1. DBSSSGSG     AED    peak=18,000,000 unlock=22,500,000
  2. DEUTDEFF     EUR    peak= 8,750,000 unlock=10,937,500
  3. CHASUS33     USD    peak= 6,500,000 unlock= 8,125,000
  4. DEUTDEFF     USD    peak= 6,000,000 unlock= 7,500,000
  5. NRKEAEAD     EUR    peak= 6,000,000 unlock= 7,500,000

Translation for a CFO: on a 10-transaction window, four banks would collectively release ~$43M of pre-funded nostro capital. Scale this up to a realistic day (hundreds of thousands of transactions) and the number is in the hundreds of millions.

10. Test vectors

TestAsserts
single_flow_peak_equals_amountA lone outflow → peak == amount
netting_reduces_peak_below_grossInflows mid-window reduce peak
later_deeper_trough_winsMax is of the running net, not per-transaction
currencies_are_isolatedUSD inflows do not offset AED outflows
safety_buffer_scales_traditional_onlyBuffer affects traditional, not atomic
fx_conversion_produces_report_currency_totalsCross-currency grand total
empty_flows_rejectedEmpty input errors
unsorted_flows_rejectedNon-monotonic timestamps error
negative_amount_rejectedNegative amounts error
self_transfer_rejectedA→A transfers error
running_twice_gives_identical_outputDeterminism
csv_parse_roundtripCSV parser roundtrips
csv_with_comments_and_blank_linesTolerates comments
realistic_fx_desk_scenario20 flows × 4 banks × 3 ccys

Count: 14 tests, all passing.

11. Methodology caveats

  1. The safety buffer is policy, not math. 1.25 is a reasonable default but each bank should use their own number — often derived from their Liquidity Coverage Ratio (LCR) calculation under Basel III.
  2. Window length matters. Running this over a single hour produces smaller peaks than running it over a full day. For board-level numbers, use at least 30 trading days of flows.
  3. Cross-currency netting is not modelled. Some banks use FX swaps to reduce cross-currency nostro requirements. The simulator models each currency in isolation, which is the conservative (upper-bound) assumption for capital unlock.
  4. Central bank standing facilities are not modelled. In the euro area, a bank can borrow from the ECB overnight at the marginal lending rate to cover unexpected outflows. Banks that actively use this facility have a smaller “hard” pre-funding requirement than the simulator reports. For those banks, treat the output as “upper bound on avoidable pre-funding cost”.

12. Reference implementation

  • Crate: warpcore-liqsim
  • Key files:
    • src/lib.rsSimulator, Flow, SimParams, SimReport, FxRates, CSV loader
    • src/bin.rswarpcore-liqsim CLI with human + JSON output
    • tests/simulation.rs — 14 passing tests
  • Build: cargo build -p warpcore-liqsim --release
  • Test: cargo test -p warpcore-liqsim

13. Roadmap

VersionPlanned
v1 (this doc)Per-(BIC, currency) peaks, safety buffer, FX conversion
v1.1Intraday credit facility modelling, overdraft ceilings
v1.2Cross-currency FX swap modelling
v2Monte Carlo volatility simulation over historical variance
v3Plug-in cost-of-capital model; direct output in ”$ per year saved”

14. Target audiences

  • Bank CFOs and treasurers — top-of-funnel business case
  • Bank sponsor search — quantified pitch for correspondent bank partners
  • Regulatory sandboxes — demonstrating that WarpCore reduces systemic capital requirements
  • Academic peer review — the methodology is transparent enough to submit to the Journal of Financial Market Infrastructures

Authors: WarpCore engineering