A Small Firm of Specialists Part 1 of 7

Part One

I Built a Prop Desk Out of Language Models

It started as an 81-line document with no code in it. Three weeks later it was nine specialists who each had something they were forbidden to do.

I Built a Prop Desk Out of Language Models
The bridge. Nine seats, four models, three machines in my house.

The repository starts with a file that has no code in it. March 28, 2026, one commit, one markdown document, eighty-one lines. The thesis is in the third paragraph:

Strategy is the easy part. Psychology is the hard part. An LLM agent is the first technology that combines both.

Everything that followed — 469 commits, three months, 33,000 lines of Python and four times that in prose — was an argument with that sentence.

The same document wrote down its own risk assessment, and I want it on the record here at the start, because it matters at the end:

Worst case: A fascinating engineering project and deep learning in both LLM agent design and quantitative trading.
Best case: A genuine, private edge in the market — and those who find one don't share it publicly.

Two days later I had the obvious thing built: 1,787 lines of classical quant. RSI, MACD, Bollinger bands, support and resistance detection, trendline fitting, a prompt builder that took all of it and handed the model a tidy summary. One LLM, called on a fixed interval. It worked, in the sense that anything works on one month of data:

Tested on Jan 2023: 2 trades, +$32 P&L, profit factor 1.85.

On April 5 I moved the entire thing into archive/rev1/ and started over. The reason is in a planning document I later deleted, and it is the hinge of the whole project:

The agent IS the conversation. The backtest engine is just the world that feeds it data and executes its decisions. Strategy emerges from the model, not from code.

The indicator stack wasn't wrong. It was code deciding things. Every hand-tuned threshold was me overruling the model in advance and then wondering why the model had no opinions. The rewrite turned all of it into tools the model could call — get_candles, get_indicators, get_position — and let it decide what to look at.

The immediate result was the thing I keep coming back to. A week later I stripped the prompt down and let the model choose its own strategy, its own take-profits, its own timeframes. It beat every hand-tuned configuration I had built:

Results on 10-date smoketest: +$506 (+5.1%), best across all experiments… Walks away from chaos (COVID: 2 trades, 0 drawdown).

Walks away from chaos. I hadn't told it to do that.

One seat can't hold two jobs

The single agent had a problem that no amount of prompting fixed: it could not hold a strategic frame and manage a live position at the same time. Ask it to think about market structure and it stopped watching the trade. Ask it to watch the trade and it lost the plot.

So I split it. And then I split the splits.

By the middle of May the firm had nine seats, and the interesting part isn't that there are nine — it's what each one is forbidden to do.

An engraved plate showing nine crew members in a row: captain at a chart table, helmsman at the wheel, lookout in a crow's nest, bosun coiling rope, master-at-arms at a magazine door, cartographer with maps, chartist with headphones, relief officer with a page.
The roster. Note the captain: no wheel within reach.

The Captain sits on the bridge. It sees pace notes, structure updates, a state stamp, a heartbeat from the wheel. Under the co-driver flag it never sees the raw market dump at all. It decides regime, writes a brief, and marks a fairway — a zone where a play might develop. It has zero execution surface. There is no tool that lets the Captain open a trade. This is invariant 6 in the domain model:

Fairway is the only path to capital commitment. Captain has no direct-fire tool.

The Helmsman is at the wheel and is the only seat that can commit capital. It sees its fairway and nothing outside it. It picks the side, the setup, the entry, the stop. It can escalate to the Captain, but only when the container is wrong — not when it dislikes the trade.

The Lookout is in the crow's nest with the raw tape and decides nothing at all. It emits typed, direction-neutral observations with a time-to-live. It is explicitly forbidden from gating anything: "nothing the Lookout emits gates delegate_entry tool-side." It is a sensor, not a vote.

The Bosun works the rigging. When the Helmsman delegates an entry, the Bosun checks the live tick against the reference price and either fills within a slippage guard or bounces it back. Note: back to the Helmsman, never up to the Captain. A bounce is not an escalation.

The Master-at-arms guards the magazine. It is the sole owner of move_stop. Nobody else in the firm can touch a stop loss, including the seat that opened the trade. It walks the position through three monotonic phases — pre-floor, defence zone, runner — and past the target it stands down and lets a deterministic trailing rule take over.

The Cartographer does the pre-voyage chart work, out of loop. The Chartist is the new hire with headphones on: give it one PNG and it tells you the shape. It cannot read numbers off the chart and the prompt says so — "he can't count." Jack is the relief officer: when a session runs long, Jack takes the conn from a one-page handoff seed and never hands back.

And the Analyst, who writes the post-mortem, is not part of the firm at all. The Analyst is Claude. The firm is Qwen. More on that in Part Three, because the way those two interact turned out to be the most useful design decision in the project.

The words are load-bearing

The nautical vocabulary is not decoration, and I can prove it, because the previous vocabulary broke the system.

Before the maritime rename the roles were named out of rally driving and hunting: pilot, co-driver, hawk, sniper, cockpit, kill bounds. It read well. It also quietly wrecked the trades. From the decision record:

its archetype-verbs — pilots fly, hawks strike, snipers crosshair, cockpit drivers steer between cones — kept pulling the model toward picking points instead of marking fields. Observable consequence: set_ltf_delegation zones collapsed from structural playgrounds (1.5–3.8% wide early-session) to limit-order-shaped coordinates (0.10% wide late-session, auto-cancelled in two minutes).

A sniper picks a point. A hawk strikes a point. The model absorbed the verb and shrank the zone by an order of magnitude until the zones were so tight they auto-cancelled before price could reach them. The metaphor was doing arithmetic behind my back.

So on May 12 I rewrote every prompt line by line — not search-and-replace — into a maritime voice, and picked the words for their properties:

Choosing "fairway" over "zone" changed the width distribution of live trading zones. That is a real engineering result and it still feels illegitimate to write down.

The seat that didn't need a model

The Bosun — the seat that takes the Helmsman's plan and either fills it or bounces it — was a 122-billion-parameter language model. It had a system prompt. It deliberated.

It is now 205 lines of Python.

The 122B Bosun spent ~12s/wake to execute a predicate that fits in 200 lines of Python. The reject rate looked like intelligence; in retrospect most of it was the gate firing on conditions the plan's slippage_budget field can express directly. The model was a latency tax on a deterministic operation.

The reject rate looked like intelligence. That's the trap, and I fell in it for a month. A model that says no sometimes looks like it's exercising judgment. Measure what it says no to and you may find a comparison operator wearing a costume. Deleting it removed 744 lines, 35 lines of prompt, twelve seconds of latency per wake, and zero capability.

The loop

One session is one slice of tick history. Per wake:

The broker advances the clock and fires — a price alert crossed, a maximum wait elapsed, or a fill happened. Before anything is routed, automatic exits run: has price closed a 15-minute bar outside the fairway (lee shore), does the fairway need reassessment. Either one pops the fairway and stamps a note onto the Captain's next wake.

Then routing, which is a five-rung precedence ladder declared as data: open position goes to the Master-at-arms, else zone-exit, else reassess, else live fairway goes to the Helmsman, else the Captain. The ladder ends in an unconditional Captain claim "so exactly one participant owns every wake." No wake is ever unowned, and no wake is ever contested.

The chosen seat gets a freshly-built prompt — a wake banner, plus hard-pinned blocks of pace notes, structure, and position HUD, re-inserted from source every single wake so no seat can carry a stale snapshot forward. It thinks. It must end its response in exactly one tool call.

If that call is delegate_entry, the Bosun runs synchronously: fill opens the position, retires the fairway and constructs the Master-at-arms; bounce hands back to the Helmsman, maximum three times. If a position exists, the Master-at-arms owns every subsequent wake until it closes. Close writes a row to trades.jsonl, and the next wake routes back up to the Captain.

The Captain owns where and when. The Helmsman owns how. Nobody owns both, and the thing that enforces it isn't discipline — it's that the tool doesn't exist in the other seat's schema.

What it cost to run

Four models across three machines in my house. The 397B runs the Captain, Helmsman, Lookout and Jack — they're mutually exclusive in time, so two concurrent sequences is enough. The 122B runs the Master-at-arms, and that's a design choice rather than a cost one: "the position-defense loop benefits from frequent fast wakes more than from deep deliberation." A 35B does the charts out of loop, a 27B vision model reads the PNGs, and the Bosun runs on no model at all.

The sequential loop turned out to buy something I didn't design for: a 99.1% prefix cache hit ratio at 89,000 tokens, and a 40× speedup on time-to-first-token. Because the seats take turns instead of running concurrently, each wake's prompt is mostly the previous wake's prompt. The architecture that made the authority boundaries clean also made the inference cheap.

Where you need the big model is where integration happens — the seats that have to hold several kinds of evidence at once and produce a judgment. Where the job is bounded, small models are indistinguishable. And one seat needed no model at all, which I only discovered by measuring it.

The series

  1. I Built a Prop Desk Out of Language Models (you are here)
  2. Same Model, Opposite Personalities
  3. The Car Crash and the Seven Minutes
  4. We Put the Agents on Trial
  5. I Replaced the Trading Chart With a Picture of a Duck
  6. Only Bootstrap Imports Infra
  7. The Brief Is the Contract