The short version
- What I built. A swing-trading system where the agent may only propose. Deterministic code verifies, sizes and risk-checks every idea, and an append-only ledger records each step. It placed its first real orders in September 2026, one share at a time.
- What I tested. Four ideas for an edge: a Claude agent, classic quant signals, insider buying from SEC filings, and the plain momentum rule the system runs now.
- What I found. No edge. Claude did not beat the free rule and cost about $27 a year, so I removed it. No classic signal survived out-of-sample testing and a correction for luck, and insider buying added nothing once sizing and stops applied.
- The uncomfortable part. The momentum rule returned +66.8% against SPY's +70.1% from 2022 to 2026, at about half the drawdown. Across the classic quant signals I tested, the best deflated Sharpe was 0.65, below my bar of about 0.95. I report that as the result, and the system stays in shadow mode.
The problem
I wanted a system that could run unattended without my having to trust a language model with money, and I wanted to know whether it had an edge at all. Models are fluent and sometimes wrong, so the design question was not "how smart can the agent be" but "how bad can a failure get, and how fast can I see it."
How it is built
In the version I tested, the only step that used a model was trade proposal. Deterministic code verified, sized and risk-checked every proposal. I have since removed the model, and the system now runs a rule-based momentum agent behind the same checks.
1 · Research, after the US close
- Evidence
- Agent proposes
- Verifier
- Sizing
- Risk engine
- Queue
2 · Execute, 09:45 New York time
- Fresh quotes
- Re-verify
- Re-size
- Full risk check
- Broker preview
- Place order
3 · Monitor, every 5 minutes
- Reconcile with broker
- Circuit breakers
- Stop, target and time exits
Blue marks the only step that was ever a model. After the experiment below, even that step is a rule.
Four decisions that matter
-
The proposer cannot size or send an order
An independent verifier recomputes what each proposal implies from my own data. It rejects anything ungrounded (every cited evidence ID must exist in this cycle, and the symbol's own price evidence must be cited), stale, priced outside a collar around the quote, with a stop too close or too far in ATRs, without enough reward for the risk, or without edge after costs.
Position size comes from quarter-Kelly on the stated edge, capped at 2% risk per trade and 6% of equity at risk across all positions. I used a quarter because the agent's confidence figures are not calibrated.
verifier.py·portfolio.py·risk.py -
The risk limits are a file the agent cannot write
Limits live in
config/risk_limits.yamland change only through a human git commit. The file's hash is recorded with every risk decision, so I can tell which limits applied to any trade. Examples: a 5% drawdown breaker, a 2% daily-loss check that blocks new entries, and at most five new trades a day. -
Execution is idempotent and never retries blindly
Client order IDs are derived deterministically from the decision, so a repeated submit finds the existing order. Each order is previewed first, and a preview cost that differs from the intended cost is rejected. If the broker's answer is ambiguous, the order moves to a reconcile state and is resolved by asking the broker, not by sending it again. After a fill, a broker-side stop is placed and re-armed if it goes missing.
execution.py -
The audit trail is tamper-evident, and the kill switch is a file
The
eventsandevidencetables are insert-only, enforced by SQLite triggers that abort any update or delete. Each event stores the previous hash and its own, so an edit breaks the chain. Tables such as orders and trades are projections that can be rebuilt from the events plus broker history.The kill switch needs no access to the running process: create
state/KILLand new orders stop, and open entry orders are cancelled on the next pass. Real orders also need three separate switches to be on, so any one being off turns entries into rejections.ledger.py·killswitch.py
Four tests for an edge
Each idea ran on liquid US stocks and ETFs, and each had to beat simply holding the index. None did.
| Idea | Result |
|---|---|
| Claude agent, against a free momentum rule | No edge. Sharpe of 1.39 against 1.36 for the baseline and 1.89 for SPY, a lower win rate, and about $27 a year. Removed. |
| Momentum, reversal, low volatility and trend following | No edge out of sample. Deflated Sharpe of 0.05 to 0.65, all below the bar. Trend following cut risk, not return. |
| Insider buying, from SEC Form 4 filings | No usable edge. A small raw effect, mostly survivorship and noise, gone once sizing and stops applied. |
| The momentum rule the system runs | Matches SPY and does not beat it: +66.8% against +70.1% from 2022 to 2026, at about half the drawdown. |
How I kept a negative result trustworthy
- No lookahead, and a test for it. The test rewrites the future of a price series and checks that no past signal moves.
- Walk-forward selection. Settings are chosen on two years of data, scored only on the next six months, then rolled forward.
- A correction for luck. The deflated Sharpe ratio counts every variant I tried, so each new grid raises the bar for the next.
- Stated biases. The universe is today's survivors, which flatters every absolute return, and each report lists that under its caveats.
The LLM study: did Claude earn its place?
I replayed 2024 (about 252 trading days) over the 100 most liquid US stocks plus an S&P 500 ETF, starting from $1,500. Both agents ran through the same verifier, sizing, stops, slippage model and kill switch. Before the run I set the bar: the Claude agent had to beat the baseline on both Sharpe ratio and win rate.
| 2024 | Claude agent | Momentum baseline | SPY buy-and-hold |
|---|---|---|---|
| Net return | +20.32% | +24.71% | +25.59% |
| Sharpe ratio | 1.39 | 1.36 | 1.89 |
| Max drawdown | −6.36% | −12.36% | −8.41% |
| Win rate | 43.9% | 47.4% | n/a |
| Profit factor | 1.42 | 1.64 | n/a |
| Trades | 107 | 133 | n/a |
Verdict: it failed my rule. Claude's Sharpe was 1.39 against 1.36 for the baseline, a lead of 0.03 that is noise. SPY's was 1.89. Claude's win rate was also 3.5 points lower. Its one real advantage was roughly half the drawdown. A measured research cycle cost about $0.11, which is about $27 a year, or 1.8% of a $1,500 account, before any benefit.
So I removed the LLM layer, kept the baseline, and kept the harness that told me the truth.
What I would not claim from this
- It is one year, and 2024 was a strong bull market for the benchmark.
- It is one run of a nondeterministic model, so a rerun would give different numbers. The Sharpe gap of 0.03 sits well inside that noise.
- With a $1,500 account, whole-share rounding and fixed order costs affect both agents.
- An early 49-call partial run showed a 65% win rate for Claude on a handful of trades. I discarded it as statistically meaningless once the full year existed.
What went wrong along the way
- My first comparison was invalid. The harness never loaded the API key, so every model call failed. The backtester swallowed the error as "no trade" and printed a fake 0% win rate for Claude. Now the harness loads its configuration, records the first error of each kind, and aborts if a model agent only produces errors.
- I killed healthy runs. I was watching the wrong cache directory, so a run waiting on the network looked hung. I stopped it after 40 minutes and stranded about $7.50 of work.
- The cache was fragile. Its key included account state that depended on earlier model decisions, so only an identical chain of decisions could reuse it.
- I had no visibility into spend. About $13 looked like nothing until I added a spend ledger with a hard cap, an abort after three failures in a row, and a request timeout.
Where it stands
The system placed its first real orders on 23 September 2026: one share at a time, each with a broker-side stop. I switched live trading off on 1 October. It now runs in shadow mode, which records decisions without sending orders, and the research since says to keep it that way. I would rather say that plainly than tune until a chart looks good.
A paper account seeded from the real one is compared with SPY each week. If I want an edge, the next place to look is a universe where small capital can compete, such as liquid small caps, with a point-in-time list of constituents to remove survivorship bias. Putting a model back in is not the plan.
Read the code
| Concern | File |
|---|---|
| Independent proposal checks | src/trading_agent/verifier.py |
| Sizing from edge | src/trading_agent/portfolio.py |
| Deterministic risk engine | src/trading_agent/risk.py |
| Idempotent execution | src/trading_agent/execution.py |
| Hash-chained ledger | src/trading_agent/ledger.py |
| Kill switch | src/trading_agent/killswitch.py |
| Backtester and harness | src/trading_agent/backtest.py, scripts/backtest_harness.py |
| Walk-forward research and luck correction | src/trading_agent/quant/ |
| Insider-buying study | src/trading_agent/insider.py, scripts/insider_study.py |
Source on GitHub (opens in a new tab) LLM backtest report (opens in a new tab) Quant research report (opens in a new tab)
An engineering project, not investment advice. Backtest results do not predict live performance.