back
220 comments
I glanced, and I found this handbook shallow and - in some areas - even bad advice.

E.g. If I ever see a monetary value stored in something else than integers I'm going to run away screaming (thank you Rust decimals represented as JSON floats). It's always integers unless you have a VERY good reason to do otherwise (though exported view can be in anything, even in weird bitcoded formats).

FX exchange. Resolution of FX isn't a point-in-time thing, things like buyer rate-in-time, seller rate-in-time, agreement, agreement tolerance, agreed upon resolution timestamp come in the effect.

Immutability - that's why you want to have event sourcing everywhere that touches money:

    # Resolved stream
    A -> B -> E

    # Actual stream
    A0 -> Edit(A0, A) -> B -> C -> D -> Rollback(B) -> E

Though in the end Fintech != Fintech. I worked at Fintech where money was treated like a baggage, and in other where money was a central point of everything.
> E.g. If I ever see a monetary value stored in something else than integers I'm going to run away screaming (thank you Rust decimals represented as JSON floats). It's always integers unless you have a VERY good reason to do otherwise (though exported view can be in anything, even in weird bitcoded formats).

That really overstates the issue. Whole domains of finance run just fine on doubles.

If you're doing Monte Carlo options pricing over interest rate paths, and you're interested in the risk metrics, like durations, convexity, vega, and so on, no one cares what your rounding convention is. doubles are just fine, thank you. How are you going to force `exp(-rt)cashflow` to be an integer? Or the normal CDF?

Yes, there are domains where ints make sense. But it's certainly not universal, you just need to make the right engineering choice.

What are you referring to with the integers/floats comment? The article says clearly that the rule of thumb is not to use floats and that they’re “almost never” a good idea, that they cause unpredictable precision loss, and recommends integer or BigDecimal types in multiple places. Are you also talking about rationals? So what is the bad advice here, exactly?

For FX, it seems like you’re reinforcing what the handbook says, that there’s no canonical rate. Aside from that, it’s talking about post-resolution records and you’re talking about how to resolve, no? That’s valid nuance of a separate goal, and it’s a fine goal of yours, but doesn’t seem like a demonstration of something missing or wrong.

The article appears to make the very same point about immutability? What are you saying that’s different?

> It's always integers unless you have a VERY good reason to do otherwise

I don't really agree. This seems like one of those outdated "greybeard rules" which people love to cargo cult, but to me it just comes with its own set of trade-offs, like now you have to think about exponents everywhere and getting them wrong comes with orders-of-magnitude consequences.

If you have a modern DB and your languages handle its decimals well then I'd use them. You can translate as needed for imports and exports, but the core of your system is always sound. Switch to the more basic formats if and only if there was some perf problem with decimals, which for most fintechs will be a VERY long time with modern DBs.

What really pushes me towards decimals is the consequence of getting something wrong. Relying on comparing raw ints, with variable exponents baked in, can lead to catastrophic errors with multi-order-of-magnitude gaps if that implicit exponent isn't carried along correctly. It's a massive footgun always waiting to happen. Decimals avoid that whole class of problem. So the goal is to maximise the "safe" decimals as source of truth everywhere you can, and have very well-tested "in and out" paths for any producers or consumers with different representation preferences. To me, that's good system design. And when, not if, you're manually inspecting DB records to track down a bug, having everything normalized in a glanceable, obvious format like decimals will let you recognise errors faster and more intuitively.

This goes extra for crypto, especially stablecoins, where one USD stablecoin might be exp-6 and another might be exp-9. And you're representing them in the same DB! Off by a factor of a thousand if you miss that exponent! Decimalize that immediately says I.

> thank you Rust decimals represented as JSON floats

What do you mean? JSON doesn’t have floats, it has numbers, and how they’re used after being parsed is not part of the spec.

> If I ever see a monetary value stored in something else than integers I'm going to run away screaming

That’s good, then we’ll likely not be working on the same system :) I consider running from “amounts as integer” systems these days (but usually unfortunately can’t). In an idealized codebase that only seasoned financial programmers are allowed to touch, it can go well, but such a system is usually either overly exclusive or risks becoming brittle.

You can't do everything you need with an integer. There are values you might want to display or calculate with that are smaller than cents. In some places you'll need things like BigDecimal, which are immune to floating point errors in most cases.

It's also safe to return decimal values for displaying values.

That was the first thing that popped out and made me distrust the whole wiki; there's only One Right Way to store money (as integers[1], as you said) and it should have been explicit about that.

You can also use fixed-point if whatever you're using supports it but it's still technically integers.

Word of advice to anyone considering the "minor-units precision" strategy for representing monetary amounts: Don't (or at least, don't use it as an interchange/API data format).

It seems like a clever idea (fast integer math, no rounding problems for addition and subtraction), but it'll bite you incredibly hard if you ever stumble upon an edge case such as working with a partner that has a different implied number of digits for a given currency. This is especially relevant for stablecoins, which often have a different number of implied decimal digits than the "fiat" currency they represent.

Also, consider representing amounts as a string type in JSON-based APIs. JSON does not specify decimal precision, so you (and all your users/vendors) will always have to make sure your parser/serializer doesn't internally lose precision by going via floating point. This can get ugly fast, and while a string seems conceptually less neat, it completely bypasses that problem. (Some will call this an anti-pattern [1], but I'd rather not fight this particular battle for ideological purity on the shoulders of my users or shareholders.)

[1] https://blog.json-everything.net/posts/numbers-are-numbers-n...

The only real correct solution here is to send mantissa and exponent as two separate integers. It's trivial to convert between exponents for whatever math you want, it can be as correct as you want, and is unambiguous.

In the HFT space you save some wire space if you can commit to a consistent exponent for some {slice} up front (think instrument/tick-size/asset-class/exchange/feed/server/whatever/...) such that you only need to send the mantissa and your clients can have a hard coded exponent. However, in similar spaces it's often worth the extra uint32 to send a on-the-wire exponent such that things _can_ change and you aren't hamstrung later by earlier "we only need cents now!" design choices when, e.g., you suddenly need to support bitcoin/... prices to full precision. (your users will thank you when they don't have to coordinate a breaking change when you want to adjust your fixed exponent)

Having done HFT / low-latency in C++ with a browser based (read: JavaScript) management front-end: Go ahead and use integer cents everyone. It’s practically an industry standard and it works just fine. Anything else is a worse compromise.
> but it'll bite you incredibly hard if you ever stumble upon an edge case such as working with a partner that has a different implied number of digits for a given currency

Why would that be a problem? You just transform the values when interacting with their API.

I think I’m agreeing with you whole-heartedly if I say that article’s conclusion is at best extreme and unrealistic when it says “the parsers need to be fixed. They should support extracting any numeric type we want from JSON numbers and at any precision.”

This sounds like an unreasonable position to take. “Any” is an unachievable standard that could require an unlimited engineering budget with no demonstrable value in practice.

It is good to identify the lack of a standard, and to talk about what parsers do in practice, and good to discuss the gaps and unmet use-cases. It would be a good idea to suggest that there should be a more reasonable standard, perhaps. It’s just not a good idea to demand that everyone support “any” possibility when no one really needs that, no one knows what it means, and it’s not actually possible to achieve.

What do you recommend instead? Standard floating-point ("float"/"double"), fixed-point arithmetic with thousandths (or smaller) of the minor unit, arbitrary-precision decimal numbers, or something else entirely?
What is with this Twitter esque style of discussion? Post some vague comment with no real stake in the ground, but just reply to follow ups asking for clarifications about the right way. It's exhausting. Why not put all that effort into the initial comment?

Vague-posting seems to becoming more popular

Nice. The book contains a bunch of good information that could already be found elsewhere but collecting it is quite practical. I highly suggest to read Kleppmann's Designing Data-Intensive Applications. The first edition was very good, a second one came out recently.

I was CTO of a FinTech where I built the whole software stack from scratch: the lessons in the book are mostly correct. I say mostly, because as always, there is a lot of "it depends" to take into consideration for your particular project. For example, I chose to not use event-sourcing to avoid the whole state computation issue. A standard append-only audit trail can do the job.

You can't guarantee exactly-once delivery but you can construct effectively-once processing, and that is what you really want.

Store every request and response : absolutely, and not only when consuming APIs, but when collecting any information from the outside world (and, if you can, also log every intermediate transformation step within your perimeter). Content-adressed buckets + a relational table are great for this.

The text also does not mention anything about data lineage. What happens if a vendor updates some data mid-day that you absolutely need to be aware of? You need to be able to account for that, while also re-playing computations that used the old values and get the same result. It's not a particularly hard problem to solve, but it takes some thought.

Why content-addressed storage? I'm assuming this is mostly for auditability after the fact? Wouldn't it make sense to use some business identifier to be able to refer it back easily?
As a programmer, what I feel when I see fintech programmers each speaking from their own different experiences and perspectives is that it makes me wonder what it really means to be good at programming.

What user xlii said about not storing monetary amounts as floats is a common IEEE 754 issue. And while it's true that financial tracking should be done through immutable logs or event-based records, I don't think every surrounding service needs to be built with event sourcing. I think it's enough to apply it only to core logic like ledgers, settlements, orders, and executions. Looking at xlii's comment, it seems like a technique that only becomes viable when the modeling is successful.

User lxgr's comment points out that it's a minor-unit issue. If JSON numbers are parsed as floats by the language or parser, precision can be lost. Usually people send values with a separate decimal places field. However, I've heard that in HFT, they don't do that because the overhead itself is too costly.

And antonymoose's comment aligns with what many books say. That's why designs like this are common in FX or API contexts. It feels like protocol design, doesn't it?

Putting it all together, everyone's right within their own domain. While I think it'd be great to have someone like xlii as my senior programmer, I also feel like I wouldn't be able to design such a complex system myself. In that sense, everyone's statements are valid, and it's interesting to see how opinions diverge depending on the domain. Is this what expertise looks like

Looking at all this, it seems like you can roughly infer where a programmer is coming from based on their experience. Sometimes programming doesn't feel like finding the right answer, but more like choosing a worldview

Watching how programmers model their domains on HN is always fascinating. Sometimes I click on their profiles and add their domain knowledge to my own personal wiki, thinking I might use it someday

Fintech/finance is a very big industry and there are a lot of sub-domains. A HFT system programmer thinks about how to colocate their prod machines in NYSE and use zero alloc techniques to ingest marketdata as fast as possible, while a crypto wallet app developer spend more time designing cool UI to attract users and working out all the L1/L2 quirks. And I'm sure they have very different answers to whether to use integers and how many digits to preserve
> However, I've heard that in HFT, they don't do that because the overhead itself is too costly.

I've worked at several HFTs and 2 had independently settled on 64bit signed fixed point with a implied 10^9 scaling factor between internal systems.

However in-process one just used 'double' for all FX conversions, scaling etc. With 15 digits of precision and careful rounding choices it's fine.

Dealing with the outside world you'd obviously just convert as cheaply as possible - and there are some crazy fast algorithms doing fast binary float -> correctly rounded decimal conversions these days

I think most of this applies to software engineering generally, not just fintech.

For example the parts talking of retries, idempotency, event ordering, etc. This applies to all systems that require any degree of accuracy, even if no money is directly involved. I've seen so many systems built on the assumption that "we can always retry", but you can only retry if you fail cleanly in the first place, and if the downstream system offers the same level of idempotency that you think it does. Quite often these are not put to the test.

> even bad advice

That's putting it politely. Honestly, I think this "handbook" was mostly written by an LLM.

For example, in the immutability section we have this:

   "Separating PII from financial data lets you honor erasure without losing the financial history you’re obliged to keep."
In a financial organisation the two go hand-in-hand for obvious KYC/AML reasons.

Keeping the financial data whilst trashing the customer names, addresses etc. instantly on-demand before the expiry of the relevant time periods is going to leave your entire organisation with a very bad day in the office if a $lawful_body comes knocking for the data to trace a crime.

People going to work in a Fintech should not be relying on a random "Handbook" written by an unknown person in an unknown jurisdiction.

People going to work in a Fintech should only ever work in accordance with their employer's internal handbooks/guidelines/etc which will have been written in conjunction with their firm's lawyers and compliance people to ensure it complies with the laws and reporting requirements in the jurisdiction(s) in which their employer operates.

> Money can’t be created out of nowhere

Well, in case of a bank it can. And it happens on a daily basis. In fact thats how most private bank money enters the system in the first place either when a bank makes a loan or when it buys any other asset like e.g. a corporate bond (you can conceptualize a loan as an asset purchase as well, the bank buys a promise to pay from the borrower which is recorded as an asset of the bank). Both are a balance sheet extension from a double-entry bookkeeping point of view.

The 4 operations double-entry bookkeeping allows are:

1. balance sheet extension (e.g. making a loan) 2. balance sheet reduction (e.g. loan repayment) 3. asset swap (e.g. a bank buying a government bond with central bank reserves) 4. liability swap (e.g. transferring money from one account to another)

So it may be more accurate to just say that every event that affects the balance sheet must be one of those 4 operations.

Also see "Money creation in the modern economy" - Bank of England Quarterly Bulletin 2014 Q1

The idempotency keys section alone is worth the read most devs learn that lesson the hard way.
A Plaid balance check is NOT a guarantee that the ACH debit you're about to submit will go through.

I don't care if the balance is one million, before that ACH can process, every single dollar can be (a) wired out, (b) cleared out by yesterday's ACHs (bills, autopay, whatever) and checks, or (c) spent at debit/ATM.

I probably shouldn't tell you why I know that some fintechs don't address this.

All this debate about floats or ints or decimals, obscures the fact “reconciliation” is an essential part of dealing with money - essentially some separate implementation of the money movement that detects when your rounding strategy or floats end up creating or destroying money, but all the accounts balance at then of the day.
I have just left a fintech company after 5 years and I can say after reading this, it looks legit to me (not AI slop as someone asked). These are the same sort of lessons I learned during my time in the industry.

I would recommend anyone starting in fintech to take some time to understand accounting principles and the ledger in a bit more depth than just debits vs credits - this is likely what is most unfamiliar to programmers.

Also financial software is very data-heavy and I learned more about databases in my time working in fintech than the 15 years before that. I think going into a bit more detail about even the basics (indexes) will save a lot of headaches.

Similar style and message to https://shapeofthesystem.com/
Anyone know of resources like this but for capital markets? Things that would allow engineers new to trading equities, options, FX, bonds, and commodities to learn about different flows, market structure, common architectures, and other things that normally you learn from years of experience.
I wanted to say thanks for this resource.

I'm founding a fintech startup and have been thinking about these things a lot. It is very helpful to have some general validation and guidance to make sure I'm not making huge mistakes.

I really wanted event sourcing, but thought it gets complicated when events have complex validation rules. I ended up with a mix of state and audit logs which works ok for now.

I really enjoyed this. This year I joined a fintech company, coming from a non-financial domain. This is the kind of information that I had to learn from experience, other devs, and random blogs/articles. The fact that this is a centralized location is awesome! I wish I knew about this when I was onboarding.
Does anyone have more learning resources in this field? Any model implementations, pet projects, anything to get going?
I don’t work in fintech (yet) but I’ve studied finance recently and quite a lot of these pieces of advice are just intuitive when you know the business domain. Learning the “customer” of your software helps too
"Fintech" is extremely broad, and most of what gets called "Fintech" is really communication. Communication between firms, between traders, between systems, between ledgers, etc. There is no industry-wide "right" way to program anything, because the right way is ultimately whatever way the other party you're communicating with will understand.

If you're dealing with a party who tracks currency in cents, then tracking currency with more precision than that is going to lead to rounding disagreements. Vice versa if you deal in cents but they deal in tenths of cents. And so on for all the other advice in this document.

Does fintech here mean "crypto" and central bank currencies transactions?
> Webhooks are the most common way to receive signals from external systems, but processing them safely is not trivial

I see webhooks documented all the time, but I have yet to use them in practice, nor have my customers requested them. Is the above not true, or are they widely used in some sectors and not others?

The whole avoid floats thing just isn't true. I have 20years experience in fintech and most of it used doubles. Excel uses doubles. Your frontend will use doubles. All your db supports doubles. Your stdlib knows how to parse doubles. Json uses doubles (not in theory but in practice). Many ERP system uses doubles

The thing for working with currency with doubles is that you have to keep in mind that it can hold 15 digits of precision in total. As long as your numbers don't use more digits than that, like 123456789.01 or 123.456789, you can have perfect decimal precision in your financial math. You just have to always round the result to within 15 digits of precision after each computation, and before each comparison. That's what excel does.

The biggest advantage of doubles is that 1) they're widely supported and 2) you can mix different precision in your system, which will appear if you do international finance or advanced financial products. Some accounting require precision up to the thousandth, some need to be rounded to multiples of 0.25. So at the end of the day you'll never use basic math but some specialised accounting math library and that library can perfectly use float as a backend.

Thank you. It came at a much needed time.
First half didn’t sound so bad.
> For whom?

It's refreshing to see someone using the correct phrasing.

The often-seen, stupid way is 'who is this book for'.

Thank you so much for this. It came at the needed time
Hey, author here. Happy to take feedback or answer questions.

P.S. I have no clue how HN works, I posted it myself yesterday and it got 6 points. ¯\_(ツ)_/¯ Anyway, glad for the reach.

Sorry have to ask these days. Is this carefully written down information from years of experience in the field or AI slop?