← Jeffery Vincent

Decisions worth writing down

The LLM proposes, deterministic code disposes

There's a tower in my office that listens, thinks, and stages options trades. Whisper hears me, two Ollama models reason, XTTS answers, and Alpaca gets the order. Every model weight lives on a single RTX 3090. Nothing leaves the house to make a decision.

The demo everyone wants is the one where I say "buy some NVDA calls" and it just does it. That demo is easy. It took an afternoon.

What took the rest of the build was making sure the language model never gets the last word about money.

The thing I actually believe

Language models are excellent at proposing and unreliable at deciding. They're good at reading a chain, weighing a catalyst, arguing a bull case against a bear case. They are also non-deterministic, occasionally confident about nonsense, and — the part that matters — capable of talking themselves into anything if you let the conversation run long enough.

At every point where a model's output touches money, something non-probabilistic gets the final say.

Not as a safety feature bolted on at the end. As the architecture.

Routing before reasoning

Every turn hits a deterministic intent router before any model sees it. Quant commands, trade commands, and UI commands route by pattern match. Tool-calling is the fallback, not the default — it only fires when nothing matches.

This sounds like a performance optimization. It isn't. It means a model having an off day cannot mis-route an order. "Close my NVDA position" resolves the same way on every run, forever, regardless of what the model thinks it heard. The class of failure where an LLM creatively reinterprets a command it should have executed literally is simply not reachable.

The committee, and the gate it can't argue with

Asking for a deep dive on a name convenes an actual desk.

Analysts run in parallel on the fast model: one on volatility (implied against realized), one on setup (chain structure and liquidity), one on catalysts (retrieved from my notes), one on my own standing thesis for that name if I've written one. Alongside them sits a regime read — Kaufman efficiency ratio, trending versus chop — which is the one vote in the room that no model casts.

Consensus forms a bias. The bias goes to a bull-versus-bear debate on the smart model. A reflection step reads how my past closed trades on that same name actually went. Then a judge model plays head trader and returns TRADE or PASS with a thesis.

And then the judge's verdict hits a rules gate that is ordinary Python.

The gate can force a PASS on a trade the judge wanted. It doesn't negotiate, it can't be persuaded by a well-argued thesis, and it doesn't care how confident the committee was. Four models can agree enthusiastically and still get overruled by a conditional.

I want to be precise about what happens next, because this is where people assume more restraint than exists. The system has an autopilot mode, and it has a robo trader that scans a universe, convenes the committee per name, and enters positions on strong verdicts without me. It manages its own exits by re-running the committee. It is genuinely autonomous.

What it isn't is unbounded. Autonomy here means the machine may act within a fence that deterministic code draws and the model cannot move.

The fence

Three stacked gates, each doing a different job.

The rules gate judges the individual trade on its merits. Confirm mode stages an order and reads it back to me for a verbal "send it"; autopilot submits when the gate passes. And underneath both sit portfolio-level circuit breakers that don't care about any single trade's quality: an order-rate throttle, a cap on open positions, a cap on gross exposure, and a daily-loss kill switch.

HAL's cognition overlay on a dark candlestick chart. A staged TSLA long-put order is laid out in a table — strike 400, expiry 2026-06-26, limit $9.13, stop loss $7.16, take profit $10.74, 8 contracts, max risk $1,432 — above a note offering to submit it as a buy-to-open limit order on the words "place it." A warning line reads: sized down to $1,432 to stay under your 6% account risk cap, open positions already risk $4,199.
Confirm mode, mid-stage. The trade idea is the committee's; the size isn't. Deterministic code cut it to $1,432 to stay under the 6% account-risk cap, and nothing gets sent until I say place it.

The kill switch latches. Once it trips, new entries are blocked and autopilot is demoted to confirm mode until I clear it by hand. A bad afternoon can't quietly resume being a bad afternoon.

One asymmetry I'd defend to anyone: exits are never blocked. Every risk control in the system restricts opening and getting bigger. Nothing restricts closing. A safety mechanism that can trap you in a position isn't a safety mechanism.

Letting a model tune a strategy without letting it grade itself

The research loop is the piece I'd point at if someone asked what the hardest judgment call was.

It works like RD-Agent: the smart model proposes the next parameter grid, the optimizer scores it, repeat. That's a system where a model is optimizing something that will eventually risk capital, which should make anyone nervous.

Three things keep it honest.

The model may only select values from an audited allow-list. It proposes parameters; it never authors code. The search space is one I defined and can read in full.

The optimizer is the referee, and it's deterministic. The model cannot score its own suggestions — it proposes, and something that doesn't care about its feelings measures the result.

And a lock-box slice of data is held back before the loop starts and tested exactly once, at the end. If the lock-box disagrees with the loop's conclusion, the loop overfit itself and the result is discarded. Not adjusted. Discarded.

The optimizer itself carries three anti-curve-fitting guards, because a sweep across many configurations will always find something that looks brilliant: a walk-forward in-sample/out-of-sample split, a sample-size shrink so a profit factor computed on three trades gets treated as the noise it is, and a significance dampener that scales configs by the t-stat of their trade returns. A t-statistic under about 2 gets faded no matter how good the equity curve looks.

That last one kills a lot of pretty results. It's supposed to.

One exit rule, two lifetimes

The structural detail I'm most pleased with is small.

strategy.exit_signal is a single code path shared by backtests and live bracket management. Change a stop-loss percentage in my strategy notes and both move together, because there is no second implementation to drift.

Most backtests are a fiction: you test one exit implementation and then trade a different one, and the gap between them is where your edge quietly lives and dies. Here, a backtest exercises the exact logic that will manage the real position. That's the difference between a backtest that's evidence and a backtest that's a story.

Why this generalizes

None of this is really about options.

Anyone wiring a language model into a system that does something consequential faces the same question, and mostly answers it with a prompt: please don't do anything dangerous. Prompts are not a control surface. They're a request, and the thing you're requesting from is the least predictable component you have.

The alternative isn't refusing to let models act. It's being deliberate about which decisions are probabilistic and which are not, and making sure the second category is the one that can say no.

Models propose. Code disposes. My house computer trades options, and the part of it I trust is the part that can't think.


HAL runs entirely on local hardware — Whisper, XTTS, and two Ollama models sharing 24GB. Alpaca in paper mode by default. Roughly 17,600 lines of Python across 46 modules, plus a Tauri/React front end.