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.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?
The FX critique is saying that it's glossing over a lot of the complexity. I'd say the same is true for the treatment of DE ledgers, and it borders on bad advice (e.g. "Balance is never stored. It’s derived from the movements of money.")
The article clearly communicates this sentiment, no? What else needs to be said? How much further does it need to go, and why?
It might be a mistake for either us or the handbook to be absolute or dogmatic about floats. It’s not a sin to mention that they exist, and it’s a fact that some people in fintech use them for some reasons that have a defensible engineering position and well considered tradeoffs. I’ve been on the side of assuming people don’t use floats for money and then been surprised when I bumped into people here on HN who report using floats in finance routinely.
BTW, is your quote “almost a bad idea” a typo? There’s a world of difference between ‘almost a bad idea’ and ‘almost always a bad idea’. The actual words in the article, if we’re quoting the article, are: “almost never a good idea” in reference to using floating point types.
> it’s glossing over a lot of the complexity.
Of course it is, that’s a good thing. It’s not pretending to be a spec or rules, it’s an introduction and general principles. The article is already introducing new complexities that people outside of fintech might not be aware of. But do we really have to mention ALL complexity? The biggest problem with Wikipedia is that it’s overrun by nuance and complexity, so much that you often can’t read an article on a topic without already being an expert on that topic. This is why experts are often bad teachers. Being unable to gloss over some complexity is not good for learning and doesn’t make a good environment for newcomers. Let’s allow people to write for non-experts and make room for learning. We don’t have to avoid glossing over some of the complexity; it’s useful to get the general direction and gist correct while leaving out some of the detail.
> it borders on bad advice
Be specific. What’s wrong? Note that contributions are invited.
You can also use fixed-point if whatever you're using supports it but it's still technically integers.
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.
I think that's the problem they were trying to describe. Without a formal spec, systems won't agree on how to handle floats. JS engines treat numbers as 53 bit signed floats, so passing a well defined decimal there through JSON means losing precision at the edges.
Money stored in integers gets around the issue by simple virtue of not really needing more than 53 bits to accurately represent the values anyone is going to encounter.
There are downsides like all the extra math or functions to handle doing the math everywhere money is manipulated or displayed, but this is the sort of thing where static typing is really helpful, and isn't too hard for juniors to understand that they should always use money functions to work with money data.
If you're only working in a single currency, there's usually no issue.
Currency codes can be found in ISO 4217.
Redundancy can be great, but it's not a panacea, since it's not guaranteed to be used in an optimal way.
In the context of Fintech, how do you otherwise resolve floating point rounding issues if not representing amounts with integers?
Do people not know what a floating point number is?
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...
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)
That’s essentially the same thing as a String-serialized big decimal, just less readable, no?
For added fun, you can introduce division. Some systems will allow you to sell 12345.55 USD to buy EUR at a rate of 1.12345.
The article’s “no lost data” tenet is not really viable when this sort of division is involved. Are you going to track your account balance is a rational number with an absolutely immense denominator forever?
You can have the blockchain team be an expert in converting integer cents, or the forex team be an expert in sub-cent conversions. You don't want to require _every team_ to have expertise in float math, by default.
Why would that be a problem? You just transform the values when interacting with their API.
Let's say I operate with a 4 decimal expectation and your API expects 6, is there any way to reconcile that outside of documentation and or metadata ? (which would be the same issue I guess whatever representation is used ?)
Still, even if you do: Chances that your users are just going to assume you're conforming to ISO 4217, some national standard, or your competitor that they're already integrated with are pretty high, so I wouldn't take the chance. Pick something that doesn't have to be documented instead.
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.
I largely agree with TFA: Round explicitly and consistently whenever you cross a boundary, i.e. database persistence and internal API calls.
Use whatever works for your required business case internally (i.e. inside of procedures calculating some function of one or more input amounts). This can be regular old floats/doubles if you absolutely know what you're doing, or BigDecimal if you aren't and would rather suffer slightly slower performance than having to talk to an auditor about IEEE 754 rounding modes, or even minor-amount integers (yes, even though I just said to not use them – but you'll want to ABSOLUTELY NEVER leak them outside of your system, including your data/analytics pipeline, which might have different ideas about financial amounts than your business logic implementing a nice custom monetary type).
Floating-point precision has too many gotchas for being suitable to store Decimal types, especially for the Currency use case.
{
"price": {
"amount": 1000,
"decimal_places": 2,
"currency": "USD"
}
}The semantics for your string “10.00” are complex - is it considered equal to “10”? To “10.000”? To “10.001”?
A user interacting with an API that uses such a string might make all sorts of assumptions about what it supports.
A user interacting with an API that has an explicit decimal places concept is being told ‘decimals matter! They can vary! Here be dragons!’
Yes, but "10 USD" would be a non-canonical representation and you probably serialized incorrectly.
> To “10.000”?
Yes, but same caveat as above applies.
> To “10.001”?
Obviously not, and any system you'd ever want to use in a financial context will tell you so.
In C# e.g., there is type decimal for those computations.
The art is in making those points well-defined and rare enough to not cause large discrepancies, but frequent enough to avoid ballooning arbitrary-precision numbers across databases and services that might not be able to handle them.
Vague-posting seems to becoming more popular
I can't design everybody's systems here, but I was hoping that sharing some war stories that have cost me days or weeks of work might sensitize somebody to a few non-obvious footguns.
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
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.
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.
I would prefer to read a defense of something more radical like "database per account." Something that has unique tradeoffs within fintech.
Also, the main advice I would give to fintech engineers/founders is to take risk and compliance seriously from day one.
Financial systems are based around trust. If you don't provably mitigate risks you will lose trust and, eventually, your entire business.
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.
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.
Where does TFA recommend that?
As I see it, it recommends separating PII data you'll eventually have to delete from that you'd probably want to keep forever (including data factoring into your accounting equations/invariants), so that you can delete the former after the relevant recordkeeping periods have elapsed.
> People going to work in a Fintech should not be relying on a "Handbook" written by an unknown person in an unknown jurisdiction.
Sure, but they should also not blindly ignore any ideas and practices presented, or avoid looking beyond their own organization. Ideally, they'll then try to reconcile what they saw with their own knowledge and local regulations etc.
> 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.
Sure, in a world in with only perfect and error-free organizations, that seems like a reasonable approach. But how does one get there without having a conversation such as this one?
Unless its your job to architect stuff, in a financial firm you don't go looking around for ideas and practices.
You comply with your employer's practices end of story.
If you like looking up ideas and other people's practices then a heavily regulated environment is probably not the place for you.
> how does one get there without having a conversation
"having a conversation" about new ideas/practices in a regulated firm will involve lawyers and the compliance department.
More than likely that "conversation" will be above most people's pay grade. So you're better off just not wasting your time and adhering to your employer's existing practices.
And for everyone else, its an expensive and high-friction conversation to have if you want to change existing practices.
> You comply with your employer's practices end of story.
What if you're the employer ("first engineer" etc.), and there are no practices yet? Fintech almost by definition sometimes includes doing things from scratch because some existing solution or incumbent organization isn't working that well anymore.
> Unless its your job to architect stuff
Which seems to be the target audience/scenario for TFA.
In that scenario the practices will still come first. You're not going to be doing any coding or systems engineering until you've got compliance signed off. You're going to be spending lots of time with lawyers and compliance people.
> Fintech almost by definition sometimes includes doing things from scratch
Yes, but cut through the noise of the typical Fintech fancy website and app and you're still staring straight down the barrel of spending 80% of your time on regulatory compliance.
Try as you might there are only so many ways you can re-invent the wheel for dealing with hard-facts legislation.
And if your lawyers and compliance people are actually telling you that you can absolutely not do any financial processing yourself, that the only possible way to be compliant is to license <incumbent product xyz> (unfortunately only available in COBOL) etc., you might not actually be working in a fintech, or at least not in the kind this guide seems to be targeted to.
Frankly, this kind of attitude is exactly why banking and payments is as fossilized as it is in some countries, and why fintech is eating their lunch in many cases. There has to be a balance between trying new things and doing what everybody else is already doing.
Many of these predate the widespread knowledge of idempotency, so often idempotency keys are hacked together by joining various, hopefully globally unique fields, except that they never quite are. (You can look behind the curtain sometimes, e.g. when your bank does not let you transfer the same amount to the same recipient account on the same calendar day.)
That way I can debug and have a last resort of compliance, but also save time by not building the first resort of compliance.
I've spent many hours explaining how idempotency is supposed to work, and why it's important. Most teams understand the need for it, but very few thought about it up front.
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.
It's refreshing to see someone using the correct phrasing.
The often-seen, stupid way is 'who is this book for'.
To learn how and why these things are traded, however, read this book, the only (good) truly beginners guide to fixed income:
https://www.jdawiseman.com/books/pricing-money/Pricing_Money...
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.
Any good resources you would recommend to learn more about this?
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?
Its at least 80% organic artisanal writing and maybe 20% AI when I needed help with grammar, completeness, broader perspective and everything around.
They mostly need replacing with a full stop or a colon.
E.g.
"In practice this means storing the amount as an integer in its smallest unit - €12.34 becomes 1234"
->
"In practice, this means storing the amount as an integer in its smallest unit: €12.34 becomes 1234."
or
"In practice, this means storing the amount as an integer in its smallest unit (e.g., €12.34 becomes 1234)"
I just published Fintech Engineering Handbook distilled from 6 years of tears, sweat and swears.
It’s a free ~25-page resource with various hints and patterns around handling money.
Tell me what you think!
other than that, peruse the commits on the source [1], or wait for the author to respond.[0]: https://mas.to/@krever/116814803588993437
[1]: https://github.com/Krever/fintech-engineering-handbook/commi...
P.S. I have no clue how HN works, I posted it myself yesterday and it got 6 points. ¯\_(ツ)_/¯ Anyway, glad for the reach.