Since 1 September, banks have been trading interest rate products on a platform I designed, wrote, secured and took to production. I did not manage this project: I built it. The matching engine, the anonymity model, the price pipeline, the authentication service, the high availability, the network architecture for public exposure. Alone, in six months, while running the multi-site infrastructure of a financial firm.
I am writing it up in detail because it proves something I believe: the separation between infrastructure, security and application development is an org chart invention. Remove it and one person ships what is assumed to need a team, often better, because no decision gets lost between two departments.
Here are the seven workstreams that had to be held at once, and at every step the decision that separates a demo from a system banks commit money on every day.
1. Translating a business into a machine
In this market, the price is still made by voice. One bank calls and wants to buy a maturity at a level, another wants to sell the same one slightly elsewhere, and the broker holds both threads until a meeting point exists. It has worked for decades, it does not scale, and two or three legged structures are painful to assemble that way.
The chosen principle is elegant: start from the middle of the market and open three tradable levels around it. Not a bid, a mid and an ask: three prices, and on each one a trader can buy or sell. Nobody knows what anyone else wants. The session runs for a fixed time, prices are frozen at the open, everyone watches the same clock, and the broker keeps the tempo.
Behind that apparent simplicity sit an order book, a matching engine, a real-time channel to every screen, and several dozen rules that cannot be guessed. Strict first-in, first-out priority. Partial fills down to a minimum, with the residual staying at the traded level rather than its original one. An absolute ban on a bank trading against itself. And one refinement I insisted on implementing: two orders from the same bank on opposite sides at the same price are rejected even when they would not pair with each other, because they would give the market a false impression of activity.
Above that sit spreads between maturities, then the mechanism I am proudest of: legging. Two outright orders placed on two different maturities together form a tradable spread, which the platform surfaces on its own. Conversely, a spread order executes as two legs against two distinct counterparties, who will never know they were used to assemble somebody else's structure. Above that again, three legged structures, executed atomically in a fixed size ratio.
These rules come out of the head of a broker who has made this market for twenty years. My value was not inventing them: it was extracting them, writing them down, and refusing to code a single one I could not state in one sentence. That discipline is what held the project together, and it is what I would recommend to any team attacking a domain it does not know.
That work took a form: the Rule Book. Every expected behaviour written in plain business language, numbered, and locked in by an automated check replayed before each release. Anonymity, order pairing, spreads, structures, session lifecycle, compliance, infrastructure. Close to eighty rules today, and a side effect I had not anticipated: the document became the contract with the business, and the answer sent to a client asking how the platform behaves in a specific case.
The broker once asked me the only question that really matters: "is it simply going over it again and again until the system is 300% robust?" I answered yes, without sugar coating, and added that there would be incidents and hotfixes even after that, because it is true of every trading platform, including the ones that cost millions in licence fees. What separates a mature platform from a fragile one is not the absence of defects, it is how rare they are, how fast they are detected, how small the impact is, and never seeing the same one twice. Promising anything else to a client is scheduling their disappointment.
2. Designing a system whose job is to say nothing
The platform is anonymous by design. A trader sees that a level is active, never who is behind it, and above all never in which direction. Put that way, it sounds like a display instruction. It is in fact the most structuring constraint in the entire system.
Because the rule does not apply to the screen: it applies to every byte that leaves the server. The standard web reflex, send generously to the browser and decide at render time, is a design fault here. Anything that leaves for a client is public, however elegant the interface.
So I treated anonymity as a property of the server, and went through every path by which information could leave: API responses, the real-time channel, confirmation exports, right down to the technical cancellation events that still carried identifiers. Every exit point now returns a redacted view, computed for the caller and according to their role.
The most revealing case is the smallest one. The interface shows a discreet triangle when somebody else has joined your level. The natural way to code it is to send the list of participants and render only a triangle: correct on screen, catastrophic in the browser console. The server computes the boolean and sends only the boolean. The broker, on the other hand, receives the full state, because he has an oversight duty. Two different answers to the same question, depending on who is asking.
3. Diagnosing a data source I do not control
The financial core is not mine. The curves are built in a workbook, by a third-party pricing engine, on a broker's desktop. The workbook exports text files onto a network share, a collector reads them every two seconds, normalises them and pushes them into the engine. On an architecture diagram, that is inelegant. In market finance, the spreadsheet is the system of record, and denying it does not make it go away. The only professional stance is to treat its output as what it is, an untrusted input, and watch it accordingly.
One morning the broker reports inconsistent levels in the platform. I demonstrated file by file that my entire chain was healthy, and that the prices were wrong at the source. Being able to prove the problem is not yours, in minutes and with evidence, is worth as much as being able to fix it.
The signature was a detail few people look at: the timestamp inside the file was going backwards. A file written by a single producer has a clock that moves forward; when it alternates between two values, two producers are writing the same file in turn and every reader sees one view out of two. I traced the attribution down to the write lock: the lock gives a process, the process gives a user and an address. Two workbooks were publishing in parallel, one of them frozen an hour in the past.
One trap worth flagging, because it costs whoever ignores it: the suspicious timestamp was UTC+2, which means central Europe in summer, but equally a southern hemisphere trading floor. A time zone identifies nobody.
The obvious fix is a lock: one publisher only. Except that a few days later the sole publisher stopped while keeping its network session open. Nothing looked broken, and nothing published, because no other workbook was allowed to take over.
So I set the requirement as two inseparable parts: exactly one publisher at a time, and another that takes over automatically when that one stops updating. This is leader election, the most classic problem in distributed systems. Except that here the nodes are spreadsheets opened by humans on three continents, and the protocol has to be explained to and accepted by people who have never heard of leader election. That translation is part of the job.
4. Taking back control of identity
Until the summer, authentication went through Microsoft's identity directory: the default choice, the one nobody gets blamed for in a steering committee. Then came a test with a large international bank. Three traders, a fixed slot. Not one login succeeded.
The analysis took under an hour and revealed no software defect at all. An external guest who has not yet accepted the invitation does not exist for the login page. That bank's first.last addresses match no account in their own directory, they are only mail aliases. And the invitation mail had never been sent. In other words, to let a client in, I depended on a third party's directory, on a message crossing two banking spam filters, and on a gesture from the user before their first connection.
I made the call the same day: drop it entirely, host authentication ourselves, shipped within the day. Because "we built our own authentication" is a sentence that should worry you, here is exactly what it covers: argon2id password hashing, RS256 signed tokens with a short lifetime and rotating refresh tokens, optional per-account second factor, single-use time-limited activation links, lockout after repeated failures, uniform responses so accounts cannot be enumerated, server-side session revocation, an append-only audit trail, and a private key that never leaves the two machines allowed to sign.
The detail that changes everything is not cryptographic: no email is ever sent. The activation link is handed to the broker, who passes it to his client through the channel they already use. No banking spam filter anywhere in the onboarding chain. And a back office so the broker opens a client account in thirty seconds, without me. The service runs active/standby on two separate machines, with automatic promotion in about fifteen seconds.
I am not saying corporate SSO is a bad choice: for internal access it remains the right one, and I run it elsewhere. But for external clients whose directory and mail system I control neither, self-hosting gave me back the only gesture that matters commercially: opening an account when the client is available, not when a third party is willing.
5. Making downtime impossible
A platform used at fixed times, in sessions of a few minutes, does not get to say "I am restarting, it will be back in ten minutes". If the engine falls at the opening, the session does not happen and the broker loses face in front of his clients. So I treated availability as a design requirement, not an operations option.
A second engine runs on another physical node, with the database replicated every five seconds. A watchdog polls the primary's health every three seconds and, on the fifth failure, promotes itself and starts the service: about fifteen seconds end to end, with no human involved.
What separates real high availability from a failover script is the fencing. The primary refuses to start while the standby answers. And if the primary reappears during a failover, the watchdog raises a critical alert but never demotes anyone on its own: a human decides, because the two databases may have diverged and no automation should choose which truth to throw away.
Then I killed the engine for real, in production. The application failover behaved exactly as designed, and the test revealed what no code review would have found: a firewall rule to the standby's address was missing. The twin was serving perfectly and the outside world would never have reached it. That is precisely why you test for real: an unproven recovery plan is not a plan, it is an assumption.
6. Going public without conceding anything
Exposing the platform to client banks is a discipline of its own, and I treated it as such: a dedicated demilitarised zone physically isolated from the office network, a proxy with no right to reach the internet at all, inspection of the inbound encrypted stream, flood protections, a single authorised destination towards the application, dedicated certificate and address.
The network chain was ready, everyone was waiting for the opening, and the audit I ran concluded no.
Because the blocker was not the network. The application had been written for a trusted network, and read with an attacker's eyes it allowed simple things: booking an order in the name of any bank, reading identities and directions, cancelling other people's orders, injecting prices. None of those doors mattered behind the internal boundary, with fifteen banks under contract. All of them became critical once publicly exposed.
I held that no under schedule pressure: a full hardening sprint, a second adversarial review, and only then a single DNS record. On go-live day the operation took thirty seconds and consisted of two reversible gestures. When the go-live is a non-event, it means the work was done beforehand.
7. Making production talk
A platform is not judged on what it does when everything is fine, but on what you can see when it goes wrong. So I put my own monitoring through an audit, with the same adversarial method as the rest of the system. It is the piece of work I am most satisfied with, because nobody ever asks for it and it changes everything on the day it matters.
First blind spot, the classic of the trade: a log collection agent running, enabled at boot, green on every dashboard, and completely silent. Its inherited configuration only read two files, while the engine writes to the system journal. Fixed by restricting collection to the one unit that matters, so as not to flood the log platform in the process.
Second: an alert rule loaded, visible in the interface, and inert because of an indentation in a configuration file. An alert you have never seen fire is not an alert, it is an intention. Since then, every new rule is validated by actually triggering its condition.
Third: monitoring covered disk, service, network and certificates, but not backup freshness. The age of the last restore point became a first-class metric, on a par with availability.
Fourth, and the one I would put in place first anywhere else: nothing guaranteed that the code served in production matched the repository exactly. I built a drift detector that continuously recomputes the fingerprint of the production files, compares it to the reference, exposes it as a metric and alerts if the gap lasts more than twenty-four hours. Same principle as for prices: you do not prevent drift, you make it visible before it grows.
One last principle, as much about client relationships as engineering: a point-in-time check is not a guarantee. "The price feed is healthy" at 9:35 says nothing about 10:00 if nothing is watching the interval. So I removed the dependency on somebody remembering to check: freeze detection went from thirty minutes to four, and the alert now goes to the data publishers themselves, not only to IT. The right reflex is not to word an answer better, it is to make sure the question no longer needs asking.
Where the platform stands
Six months after the first line of code, here is what runs:
- Live in production with real banks, in fixed-time sessions, several times a day.
- Close to eighty business rules written in plain business language, each one locked in by an automated test replayed before every release.
- An active engine and its standby twin on two distinct nodes, with fencing against dual activation and a failover validated by a real shutdown in production.
- A redundant in-house authentication service, optional second factor, server-side revocation, and a back office that opens a client account in thirty seconds.
- Public internet exposure behind an isolated demilitarised zone, with inspection of the inbound encrypted stream and a proxy denied any outbound access.
- One non-negotiable operating rule: no engine restart while a session is open. Releases are planned around the market, never the other way round.
Built, secured and operated by one person, with no vendor behind it, on infrastructure I own end to end. And the rest did not wait during those six months: a financial firm's virtualization migrated to open source without stopping the trading floor, and the backup strategy was redesigned to survive an attacker who is already admin. Those are two other articles on this blog.
What this project says about the job
Beyond the platform, three convictions come out reinforced, and they are the ones I apply everywhere.
- Airtight specialisation costs more than it returns. On this project, the same person wrote a matching rule in the morning and a firewall rule in the afternoon. No interface meetings, no specification lost in transit, no waiting between two teams. That is what makes six months possible where eighteen would be expected.
- The bottleneck is never the speed of writing code. It is understanding the business, the quality of architectural decisions, and the ability to say no at the right moment. Those are exactly the three things that cannot be delegated.
- Rigour is an accelerator, not a brake. The Rule Book, server-side redaction, fencing against dual activation, the drift detector: each one cost time and each one returned it tenfold, because none of those subjects ever came back a second time.
The rest fits in four principles I repeat to anyone who will listen: confidentiality is a property of the server, never of the interface; a system is never better than its least governed input; automation without fencing manufactures duplicates; and green does not mean alive, so check the output, never the status.
And where does the AI fit
That is the thread of this blog, so let me be precise. The AI wrote a large share of this code, and I own that completely: without it the arithmetic does not work. One person, six months, with a multi-site infrastructure to carry alongside, is impossible by hand. I learned to use it early and intensively, and that is a skill in itself.
But go back through the seven workstreams. Not one was solved by code generation. Deciding to drop the SSO on the day of the failed test. Choosing fencing over elegant failover. Understanding that a timestamp going backwards is the signature of two concurrent writers. Refusing to go public while the network was ready and everyone was waiting. Turning a broker's twenty years of experience into eighty testable rules. That is judgement, it stayed entirely human, and that is where the value sits.
The AI gave me the speed of a team. It did not give me the decisions. That is exactly what I wrote six months ago, and six months of production with real banks have not contradicted it.