Fintech Engineering Handbook

A comprehensive guide to the most important software engineering patterns and principles used when building systems where money is the primary focus.
Welcome to the Fintech Engineering Handbook. This resource aims to describe the most important patterns used in software engineering, where money is the primary focus of the system. It can be read in full to get a comprehensive understanding or in parts when dealing with a particular problem.
For whom?
**People joining fintech.**To get familiar with the domain and the patterns that make money systems trustworthy.**People already in fintech.**As a reference to reach for when facing a particular problem, and a shared vocabulary to point colleagues at.**People outside fintech.**To understand how building for money differs from what they’re used to, and why.
It’s meant as a living document and contributions are welcomed.
Principles
Everything you will read below is a way to adhere to the three principles:
**No invented data.**Money can’t be created out of nowhere, so we can’t tolerate duplicates or arbitrary balance updates. We enforce this with idempotency, deduplication and reconciliation.**No lost data.**Everything that happens to money has to be tracked and persisted. We protect this with full precision, at-least-once deliveries, event sourcing, audit trails and immutability.**No trust.**Trust neither external providers, internal components, nor the world. We uphold this by verifying webhooks, cross-checking data across sources and failing loudly on broken assumptions.
Representing money
Before you can move or record money, you have to represent it. These are the decisions about how a monetary value is modeled, stored, computed and converted. Getting them wrong means every layer above inherits the error.
Precision handling
Money representation is one of the most fundamental decisions in financial systems. There are four primary ways to do it:
**Floating-point.**Built-in float or double types. This can create unpredictable precision losses and is almost never a good idea. But it’s the fastest and most memory efficient, and requires no additional libraries or data structures.**Arbitrary precision.**Types like Java’sBigDecimal
let you control the precision of a computation precisely. The code is predictable and we get to decide where and how rounding happens. It fits intermediate work like FX or pricing math, where many operations chain together.**Minor-units precision.**For most fiat currencies it’s ok to keep only a fixed precision, the same that is used in the connected central banking system. The number of digits is described by ISO 4217 (don’t assume it’s always 2, it’s not!). In practice this means storing the amount as an integer in its smallest unit - €12.34 becomes1234
. Crypto uses the same integer-smallest-unit idea (satoshis for BTC, wei for ETH), but with two twists: the precision is per-asset and defined by the token itself (e.g. an ERC-20’sdecimals
), often 18 digits, and the resulting magnitudes overflow 64-bit integers, so you need arbitrary-width integers to hold them.**Rational numbers.**When no precision loss is acceptable. This is the most powerful approach but comes with its own caveats. First, it’s slower than the alternatives. Second, it cannot be converted to other formats without losing precision. Third, it usually requires a custom datatype or a library.
Selecting one or the other depends on the class of the system and its responsibilities. There is no rule of thumb here, other than not using floating points. These representations are not mutually exclusive either - how you store an amount and how you compute with it are separate decisions, and a system often combines them, e.g. integer storage with BigDecimal
for intermediate computation.
The same care applies when an amount is being serialized. A bare JSON number is an IEEE-754 double in most parsers, so serializing money as a number reintroduces the floating-point problem at the edge, no matter how carefully you represent it internally. Send money either as a string ("12.34") or as an integer in its smallest unit.
Principles touched:
**No lost data.**The wrong representation silently drops precision that can never be recovered.
Rounding strategies
**Rounding is inevitable.**It should be done explicitly: any division, currency conversion, fee, interest or rate application, or move between precisions might require rounding.**It’s a business decision.**Different rounding strategies have different implications. Sometimes you have to be conservative (e.g. not to spend what you don’t have) and round down; sometimes you care about statistical effects and use half-even. Deciding who gets the fraction might have legal/tax implications.**Round as seldom as possible.**The longer you keep full precision, the more options you have to make the right decision in the right context. Rounding should usually happen on boundaries, e.g. before numbers are persisted or shown to the user.**Rounding breaks sums.**If a number is split into parts and rounding is applied, the sum of the parts might no longer equal the original number. Depending on the context, this might require explicit handling - e.g. an explicit rounding account.
Principles touched:
**No lost data.**Residuals must be tracked, not dropped.**No invented data.**Rounding must never mint money that wasn’t there.
Currency handling
Money can’t be represented as a number alone - it comes paired with a currency. There are a few nuances when it comes to handling currencies.
**Pack amount and currency together.**AMoney
newtype (struct, class, record etc.) minimizes the chance of errors.**No cross-currency arithmetic.**Your system should prohibit adding two amounts in different currencies. Conversion should happen very explicitly with a strictly controlled rate.**Use a controlled currency set.**A custom config entry, JDK database, dedicated service. Never accept arbitrary currency codes; validate at the boundaries of the system.**Codes identify fiat only.**Currency codes are unique and usable as identifiers only for fiat. For crypto currencies you will have to use a more complicated approach like(network, contract address)
or similar.**Currencies carry metadata.**Symbol, precision, name, etc. You will usually need those details for display purposes but rarely for business logic.**Pegged is not the underlying.**Pegged, bridged and wrapped crypto currencies are not equivalent to the underlying ones.
Principles touched:
**No trust.**Validate currency against the controlled set at the boundary.**No invented data.**Treating distinct currencies/assets as interchangeable conjures value.
FX Rates
FX (Forex, foreign exchange currency market) rates allow us to convert money between currencies.
**A rate is always directional.**The EUR/USD rate is not the same thing as the inverted USD/EUR rate. On an exchange, buying and selling are two different orders at different prices (the bid/ask spread), so the two directions don’t simply invert.**The time of the rate is critical.**While you can technically use a rate from any point in time, the most commonly used are:**Current-time rate.**Used to calculate current holdings or the value of a transaction as if it happened right now.**Value-date rate.**Used to calculate change in value or a tax amount.
**Two kinds of rate matter for conversion:****Transactional rate.**The rate a real conversion happened at. You don’t store it directly - it falls out of the original and result amounts.Reference rate(mid-market or central bank). One used for valuation and equivalence (what holdings are worth right now, or a tax base at the value date) and not a price anyone actually trades at.
**There is no canonical rate.**Rates come from markets and vary between venues or calculation methods. The closest to canonical are central bank rates, which can be used only as a reference rate, and even there we can have alternative sources which are just as valid.
Principles touched:
**No lost data.**Keep the amounts (and, for reference rates
Source: Hacker News












