Skip to content
Friday, 7 August 2026 · LondonENع
Rayan Azhari.Sustainability · Energy · Carbon · Built EnvironmentOccasional detours into philosophy, religion or programming, wherever curiosity leads
Production Quant Trading

One Arrow, Drawn Once: The Architecture That Keeps Research Out of Production

The most dangerous code in a trading repo is the throwaway research notebook. One structural rule, dependencies that flow in a single direction, keeps it out of production and turns invisible coupling into a number you can grep.

Rayan AzhariChartered Environmentalist, MISEP · 9 min read
Title card for the essay One Arrow, Drawn Once, showing research and scripts directories both importing inward at the library, which imports nothing above it.

The most dangerous code in a trading system is the throwaway notebook where you found the edge. It is fast and permissive precisely because it is allowed to be wrong, and it is allowed to see the whole dataset at once. That is exactly the code you never want anywhere near live capital. Yet the default trajectory of almost every system walks it straight there: someone discovers a signal in a notebook, the notebook grows a broker connection, the connection grows a position-sizer, and one Tuesday the notebook is trading real money, carrying every exploratory shortcut with it.

There is one structural decision that prevents most of this, and it is not a framework or a design pattern. It is a single rule about which way your import statements are allowed to point.

Sort code by what it is allowed to assume

A trading system is not one program. It is at least four, with very different lifespans and very different blast radii. The useful way to tell them apart is not "what does this code do" but "what is it allowed to assume?"

Figure

The four-layer arrow

Sort code by what it is allowed to assume. Everything points inward at the library; the library points at nothing above it.

Discovery

disposable · research/

May assume it sees the whole dataset. Notebooks, sweeps, audits. It may import the library; nothing may import it.

Intent

versioned · config/

States what runs and with which parameters. It is data, read at runtime, never imported. Diff it to see a deployment change.

Library

the asset · titan/

The tested implementation that touches capital. It imports only itself and depends on nothing above it. This is the sink every arrow points at.

Entry points

glue · scripts/

Wire the library to a broker and press go. Inert until called; the only place a broker connection is ever constructed.

Invert one arrow and the most reckless code in the repo is suddenly load-bearing.

Source: Building a Production Quant Trading System (Titan)
  • Discovery may assume it sees the future: the whole series at once, a normaliser fit on all of history, a winner chosen with the same bar it earns. Notebooks, sweeps, audits. Disposable by design.
  • Intent is the versioned configuration that states what runs and with which parameters. It is data, read at runtime, not code you import.
  • Library may assume neither the future nor a broker; only its inputs and its own tests. This is the packaged, tested implementation that actually trades. It is the asset.
  • Entry points may assume a broker connection exists. They wire the library to a clock and a venue and press go.

Code that wants to assume both the future and a broker connection is two files wearing a trenchcoat. The layer test pulls them apart before they merge.

One arrow, and it only points one way

Now the rule. Dependencies flow in exactly one direction: discovery and entry points both point inward at the library, and the library points at nothing above it. Research may import the tested code; the tested code may never import research. Entry points may import the library; the library may never contain an entry point. Drawn as a graph, the whole system is acyclic with the library as the sink.

Why is the direction load-bearing, rather than just the existence of folders? Three reasons, each a class of bug it retires:

  1. It localises blast radius. A dependency is a contract: change me, and I might break whatever points at me. Because the library points at nothing above it, you can rewrite a notebook, delete a sweep, or rename a config key with zero risk to the code that trades. Invert one arrow, let the library import a research helper, and the most reckless file in the repo is suddenly on the critical path to your account.
  2. It makes look-ahead structurally harder to ship. Discovery code is allowed to fit a z-score over the whole series. The library lives in the world of one bar at a time, decisions before returns. When the boundary is one-directional, that leak cannot ride an import from the permissive layer into the strict one, because the strict layer cannot import the permissive one. Be precise about what this buys: it closes one path for look-ahead to travel, not all of them. A causal-but-wrong normaliser you write inside the library still has to be caught downstream by walk-forward and the metric suite. Layers and validation each shut a different door.
  3. It separates intent from implementation. The lookbacks, thresholds and instrument lists that constitute your edge belong in versioned config, read as data, never baked in as literals. A reviewer diffs the config directory and sees exactly what changed about a deployment without reading a line of Python.

The real payoff: coupling you can count

Here is the part most architecture write-ups miss, and it is the reason the rule earns its keep even when you break it.

A one-way dependency rule does not magically prevent coupling. What it does is make coupling legible. Every illegal dependency becomes a single grep. Your architecture's compromises stop being a vague feeling and become a countable list you can put a number on and watch trend.

Figure

A 30-second architecture audit

Four greps tell you whether the arrow holds. No deep reading required.

from scripts in titan/

must be EMPTY

A library that imports an entry point has inverted itself completely. This should return nothing, ever.

from research in titan/

your debt ledger

Library coupling to disposable code. A countable number you drive toward zero, wired into CI as a budget that only ever lowers.

from titan in research/

expect MANY

Research reusing the exact tested code the live system runs. If this is empty, you have copy-paste, not architecture.

from titan in scripts/

expect MANY

Entry points composing the same asset. Reuse-inward is the point: one implementation, not two that drift apart.

The rule does not prevent coupling. It makes coupling countable.

Source: Building a Production Quant Trading System (Titan)

You can audit a layered system in about thirty seconds, no deep reading required. A library importing an entry point (from scripts inside the library) should return nothing, ever: a library that imports its own runner has inverted itself completely. A library importing discovery code (from research inside the library) is your coupling debt, enumerated, a number you drive toward zero. And research and entry points importing the library should both return many hits, because reuse-inward is the entire point: research and operations compose the same tested implementation rather than re-deriving it. If those come back empty, you do not have architecture, you have copy-paste, two implementations of your edge that will drift apart until live no longer equals research.

That is the quiet power of the rule. A system without it has exactly the same coupling; it just has no way to see it, because everything imports everything and no arrow was ever supposed to point any particular way.

The war story: the back-edge that ate the build

None of my systems have a perfectly clean arrow, and pretending otherwise teaches the wrong lesson. A strategy was once promoted to live by having its library wrapper import the original signal function straight out of the research package, instead of moving that function down into the library. It worked. It passed review, because the import resolved and the tests were green.

The cost surfaced at deploy time. The production image would no longer build from the library alone; it now had to bundle the entire research tree, and a build-time import check meant an unrelated edit to an exploratory research file could break the production build. The shape of the bug is coupling that points the wrong way: the asset taking a dependency on the disposable. The rule it bought is a good one to memorise: promotion means moving code across the boundary, not importing across it. An import research inside the library is a TODO, not a design.

And notice how the bug was found: not by re-reading anything, but by a grep that could count the back-edges. The rule made the debt visible the moment it was contracted.

Enforcement, not etiquette

A grep you run by hand is a convention, and a convention drifts the first time a deadline bites (the war-story passed review precisely because everyone was trusting etiquette). To make the arrow a real constraint, wire it into CI:

  • An import-linter contract that declares the library may not import research or scripts, and fails the build on any violation. This is the strong version: the compiler does your code review.
  • Or, as a lighter ratchet, a test that asserts the back-edge count is at most N, for a budget N you only ever lower. The build, not your memory, holds the line, and "watch the number trend to zero" becomes something CI will not let rise.

Either way, the boundary stops being a promise and becomes a property of the build.

The takeaway

You do not need a clever framework to keep research out of production. You need one arrow, drawn once and enforced forever: discovery and entry points point inward at the library; the library depends on nothing above it. Sort code by what it is allowed to assume, keep intent in config as data, and wire the direction into CI so it holds under deadline. The rule will not make your coupling zero. It will make it a number, which is the first step to driving it down.

This is the second chapter in the foundations of the system these essays come from. The full treatment, Architecture and the one-way dependency rule, is free to read, with the layer table, the honest wrinkle where the arrow bends, and the intent-as-data turnstile. You can see the layout in the sanitised companion framework on GitHub, which is Apache-2.0. That chapter opens Building a Production Quant Trading System; the complete book, a living digital copy on Leanpub and a print paperback on Amazon, is where sizing, portfolio construction and live operation are worked out end to end.

This is an engineering essay, not investment advice, and it contains no tradable strategy.

Figure

The four-layer arrow

Sort code by what it is allowed to assume. Everything points inward at the library; the library points at nothing above it.

Discovery

disposable · research/

May assume it sees the whole dataset. Notebooks, sweeps, audits. It may import the library; nothing may import it.

Intent

versioned · config/

States what runs and with which parameters. It is data, read at runtime, never imported. Diff it to see a deployment change.

Library

the asset · titan/

The tested implementation that touches capital. It imports only itself and depends on nothing above it. This is the sink every arrow points at.

Entry points

glue · scripts/

Wire the library to a broker and press go. Inert until called; the only place a broker connection is ever constructed.

Invert one arrow and the most reckless code in the repo is suddenly load-bearing.

Source: Building a Production Quant Trading System (Titan)

Figure

A 30-second architecture audit

Four greps tell you whether the arrow holds. No deep reading required.

from scripts in titan/

must be EMPTY

A library that imports an entry point has inverted itself completely. This should return nothing, ever.

from research in titan/

your debt ledger

Library coupling to disposable code. A countable number you drive toward zero, wired into CI as a budget that only ever lowers.

from titan in research/

expect MANY

Research reusing the exact tested code the live system runs. If this is empty, you have copy-paste, not architecture.

from titan in scripts/

expect MANY

Entry points composing the same asset. Reuse-inward is the point: one implementation, not two that drift apart.

The rule does not prevent coupling. It makes coupling countable.

Source: Building a Production Quant Trading System (Titan)

Further reading

Office energy, part 4 of 25

Related posts

Migrating from WordPress to Next.js: A Field Guide

A practical, end-to-end guide to moving a content site from WordPress to Next.js without losing your search rankings: the URL-preservation rule that governs everything, a content pipeline that survives the move, bilingual and RTL handling, the SEO and security work, and a cutover you can roll back.

· 20 min

Essays in your inbox

New writing on Syria, sustainability and finance, a few times a month.

Unsubscribe anytime. Read by 4,200+ professionals.