Skip to content
HN On Hacker News ↗

An oral history of Bank Python

▲ 177 points 71 comments by tosh 2w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 5 of 5
SEGMENTS · AI 0 of 5
WORD COUNT 1,568
PEAK AI % 0% · §3
Analyzed
Jun 25
backend: pangram/v3.3
Segments scanned
5 windows
avg 314 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,568 words · 5 segments analyzed

Human AI-generated
§1 Human · 0%

November 2021 The strange world of Python, as used by big investment banks

High finance is a foreign country; they do things differently there

Today will I take you through the keyhole to look at a group of software systems not well known to the public, which I call "Bank Python". Bank Python implementations are effectively proprietary forks of the entire Python ecosystem which are in use at many (but not all) of the biggest investment banks. Bank Python differs considerably from the common, or garden-variety Python that most people know and love (or hate). Thousands of people work on - or rather, inside - these systems but there is not a lot about them on the public web. When I've tried to explain Bank Python in conversations people have often dismissed what I've said as the ravings of a swivel-eyed loon. It all just sounds too bonkers. I will discuss a fictional, amalgamated, imaginary Bank Python system called "Minerva". The names of subsystems will be changed and though I'll try to be accurate I will have to stylise some details and - of course: I don't know every single detail. I might even make the odd mistake. Hopefully I get the broad strokes. Barbara, the great key value store The first thing to know about Minerva is that it is built on a global database of Python objects. import barbara

# open a connection to the default database "ring" db = barbara.open()

# pull out some bond my_gilt = db["/Instruments/UKGILT201510yZXhhbXBsZQ=="]

# calculate the current value of the bond (according to # the bank's modellers) current_value: float = my_gilt.value()

Barbara is a simple key value store with a hierarchical key space. It's brutally simple: made just from pickle and zip. Barbara has multiple "rings", or namespaces, but the default ring is more or less a single, global, object database for the entire bank.

§2 Human · 0%

From the default ring you can pull out trade data, instrument data (as above), market data and so on. A huge fraction, the majority, of data used day-to-day comes out of Barbara. Applications also commonly store their internal state in Barbara - writing dataclasses straight in and out with only very simple locking and transactions (if any). There is no filesystem available to Minerva scripts and the little bits of data that scripts pick up has to be put into Barbara. Internally, Barbara nodes replicate writes within their rings, a bit like how Dynamo and BigTable work. When you call barbara.open() it connects to the nearest working instance of the default ring. Within that single instance reads and writes are strongly consistent. Reads and writes from other instances turn up quickly, but not straight away. If consistency matters you simply ensure that you are always connecting to a specific instance - a practice which is discouraged if not necessary. Barbara is surprisingly robust, probably because it is so simple. Outright failures are exceptionally rare and degraded states only a little more common. Some example paths from the default ring:

Path Description

/Instruments Directory for financial instruments (bonds, stocks, etc)

/Deals Directory for Deals (trades that happened)

/FX Foreign exchange divisions' general area

/Equities/XLON/VODA/ Directory for things to do with Vodaphones shar es

/MIFID2/TR/20180103/01 Intermediate object from some business process

Barbara also has some "overlay" features: # connect to multiple rings: keys are 'overlaid' in order of # the provided ring names db = barbara.open("middleoffice;ficc;default")

# get /Etc/Something from the 'middleoffice' ring if it exists there, # otherwise try 'ficc' and finally the default ring some_obj = db["/Etc/Something"]

You can list rings in a stack and then each read will try the first ring, and then, if the key is absent there, it will try the second ring, then the third and so on.

§3 Human · 0%

Writes can either always go to the first ring or to the uppermost ring where that key already exists (determined by configuration that I have not shown). There are some good reasons not to use Barbara. If your dataset is large it may be a good idea to look elsewhere - perhaps a traditional SQL database or kdb+. The soft limit on (compressed) Barbara object sizes is about 16MB. Zipped pickles are pretty small already so this is actually quite a large size. Barbara does feature secondary indices on object attributes but if secondary indices are a very important part of your program, it is also a good idea to look elsewhere. Dagger, a directed, acyclic graph of financial instruments One important thing that investment banks do is estimate the value of financial instruments - "asset pricing". For example a bond is valued as all the money that you'll get for owning it, discounted a bit for the danger of the issuer of the bond going bust. Bonds are probably (conceptually!) the simplest instrument going and of much greater interest is the valuation of other, "derivative", financial instruments, such as credit default swaps, interest rate swaps, and synthetic versions of real instruments. These are all based on an "underlying" instrument but pay out differently somehow. The specifics of how derivatives are valued does not matter, except to say that there are both a lot of specifics and a lot of derivatives. The dependencies between instruments forms a directed, acyclic graph. An example hierarchy for some derivative financial instruments might look like this:

Some financial instruments derive their value from others. That makes them derivatives. You can get derivatives of derivatives and some derivatives derive their value from multiple underliers.

Dagger is a subsystem in Minerva which serves to help keep these data dependencies straight.

§4 Human · 0%

You write a class like so: class CreditDefaultSwap(Instrument): """A credit default swap pays some money when a bond goes into default"""

def __init__(self, bond: Bond): super().__init__(underliers=[bond]) self.bond = bond

def value(self) -> float: # return the (cached) valuation, according to some # asset pricing model return ...

Dagger tracks the edges in the graph of underlying instruments and automatically reprices derivatives in Barbara when the value of the underlying instruments changes. If some bad news about a company is published and a credit agency downgrades their credit rating then someone in bonds will update the relevant Bond object via Dagger and Dagger will automatically revalue everything that is affected. That might mean hundreds of other derivative instruments. Credit downgrades can be rather exciting. Individual instruments are composed into positions. The Position class looks a bit like this: class Position: """A position is an instrument and how many of it""" def __init__(self, inst: Instrument, quantity: float): self.inst = inst self.quantity = quantity

def value(self) -> float: # return the (cached) valuation, which basically is # self.inst.value() * self.quantity return ...

Again, note that a position is something you can also value. It is also something whose value changes when the value of things it contains changes. It it also automatically revalued by Dagger. And a set of positions is called a "book" which is an immensely overloaded word in finance but in this context is just a set of positions: class Book: """A book is a set of positions""" def __init__(self, contents: Set[Valuable]): # the type Valuable is a "protocol" in python terms, # or an "interface" in java terms - anything # with value() self.contents = contents

def value(self) -> float: # again, return the (cached) valuation, which is more # or less: sum(p.value() for p in self.contents) return ...

Books can contain other books.

§5 Human · 0%

There is a hierarchy of nested books all the way up the bank from the smallest bond desk to a single book for the entire bank. To value the bank you would execute: # this is the top level book for the whole bank which # recursively contains everything else in the whole bank bank = db["/Books/BigBankPlc"]

# this prints the valuation of the whole bank print(bank.value())

That's the dream anyway. In reality the CFO probably uses a different system to generate the accounts. Valuations of subsidiary books are still well used though. If you understand excel you will be starting to recognise similarities. In Excel, spreadsheets cells are also updated based on their dependencies, also as a directed acyclic graph. Dagger allows people to put their Excel-style modelling calculations into Python, write tests for them, control their versioning without having to mess around with files like CDS-OF-CDS EURO DESK 20180103 Final (final) (2).xlsx. Dagger is a key technology to get financial models out of Excel, into a programming language and under tests and version control. Dagger doesn't just handle valuations. It also handles the various "risk metrics" that banks use to try to keep a handle on how exposed they are to various bad things that might happen. For example, Dagger makes it relatively easy to find all positions on, say, Compu-Global-Hyper-Mega-Net Plc, which is rumoured to be going bust. That's counting all options, futures, credit instruments and all of it "netted out" to find the complete position on that company for the whole bank. Never again be surprised by your exposure to dodgy subprime lenders! Walpole, a bank-wide job runner I've said so far that a lot of data is stored in Barbara. Time to drop a bit of a bombshell: the source code is in Barbara too, not on disk. Remain composed. It's kept in a special Barbara ring called sourcecode. Not keeping the source code on the filesystem breaks a lot of assumptions. How does such a program run?