Live — trading
arbor
A program that trades my own money on sports outcomes, works out for itself which bets are worth taking, and keeps an honest score of how it has done.
A quant trading system on Kalshi that trades its own money, rewritten from the outside in after its risk gate turned out to be advisory rather than structural.
- Platform
- Python, VPS
- Status
- Live — trading
- Demonstrates
- Systems accountable to money
- Launched
- April 2026
02
What it is for
For me, with my own money on the line, which is the entire point of it.
It started in April 2026 as a cross-exchange scanner: read Kalshi and Polymarket, find the same event priced differently, take the spread. That worked as an idea and not as a business, because the two venues rarely list genuinely comparable contracts and the gap closes before a retail account can reach it. That era is discontinued and quarantined in the repository under legacy.
What replaced it needs one venue. It trades a portfolio of named sub-strategies on Kalshi, sizes with a Kelly fraction, and runs a research loop that grades its own conclusions against the exchange ledger rather than against its own logs.
03
Why it exists
Cross-exchange arbitrage on prediction markets works until enough people run the same scanner, and then it does not. It is a crowded, capacity-limited trade, and defending a closing edge by cutting latency and adding venues is a race that ends the same way for everybody in it.
The question underneath was better: not whether this contract is mispriced against that one, but whether it is mispriced against reality. Kalshi contracts pay a dollar if an event happens, so a price is already a probability — which makes forecasting the same shape of problem as arbitrage, without the capacity limit.
04
What it does
Probability pricing
Prices sports events directly, rather than looking for the same contract quoted twice.
Single order path
In the rewrite, one execution package reaches the exchange. Nothing else has the capability.
Structural risk gates
Concentration, side exposure and dutch-book checks that an order cannot route around.
Fill reconciliation
Intended against actual, on a schedule, because a system that grades its own homework passes.
Daily P&L validation
A guardrail that runs as a self-test in the daily digest rather than on request.
Exchange limit monitoring
Tracks the venue's own position limits separately from its internal caps.
Small-sample guards
Explicit checks for the artifacts that make noise look like edge — single-row skew, tiny-cohort Kelly.
Deadman switch
Guards that halt the system rather than log a warning nobody reads.
05
What it runs on
Engine
- 01
- Python
- 02
- Node for the order path and guards
Data
- 01
- Postgres
- 02
- An odds feed
- 03
- Kalshi market data
Operations
- 01
- A VPS running continuously
- 02
- Scheduled reconciliation
- 03
- Telegram alerting
06
How it's built
It started as something else. The first two versions were cross-exchange arbitrage scanners — read Kalshi and Polymarket, find the same event priced differently, report the spread — and the second narrowed that to the Bitcoin hourly market where the two venues list genuinely comparable contracts. Neither ever placed an order. When the spread compressed, which is what happens to every arbitrage once the scanner is easy to write, I retired the strategy rather than optimise it, and quarantined the code so nobody would rediscover it as a good idea.
What replaced it asks a different question: not whether this contract is mispriced against that one, but whether it is mispriced against reality. That is not capacity-limited in the same way, and it is much harder to know when you are right.
The live system is Python on a VPS — engines that produce candidate positions, a scheduler that fires them, Postgres for state, and reconciliation that runs whether or not anyone is watching. The rewrite inverts the shape: typed contracts first, and a single execution package that is the only thing permitted to reach the exchange.
07
Architecture
One figure, read left to right. Border treatment carries the node type; the color is the project's.
- Service
- Store
- External
- Model
08
A piece of the code
bot/lib/shared_position_guard.mjs
/** * Shared Position Guard - cross-process order placement coordinator * Prevents: * - Per-market concentration * - Per-side overbetting * - Dutch books (buy + sell above 1.0 for guaranteed contract) * - Owner-blocked symbols * * All order-placement systems MUST consult this before placing. */export async function loadPortfolioState(api) { try { const result = await api("GET", "/portfolio/events"); return result.events || []; } catch (err) { console.error("[guard] portfolio fetch failed:", err.message); return []; }}Excerpt — trimmed for reading, not a full file.
09
The hard parts
01 — Problem
The system had a shared position guard. It was correct — it enforced concentration limits, blocked dutch books, honoured a block list — and its own header said in capitals that every order-placement system must consult it before placing. It was also advisory. "Must" is a word in a comment, and eighty-six files in that repository reach the order endpoint. Exactly one imported the guard.
Approach
The rewrite makes the guard the only route rather than the documented one. One order path, one accountant, one writer per artifact, attribution at birth, and a strict separation between the thing that proposes a trade and the thing that validates it. Agents cannot place an order because they do not have the capability, not because a comment asks them not to.
Tradeoff
A complete rewrite while the original keeps trading, which means running two systems and reconciling them. The loose arrangement is what let me try eighty-six things quickly; the strict one will make some of those experiments slower to reach production. That is the trade: velocity of experiment for safety of execution, made after finding out which one the money cares about.
02 — Problem
The project was good at cross-exchange arbitrage, and then the spread compressed. The tempting move is to defend it — cut latency, add venues, shave fees — because the edge is checkable and the machinery already exists.
Approach
I retired the strategy instead and quarantined the code with a note explaining why, so it could not be rediscovered as a good idea. The system re-pointed at forecasting, which is not capacity-limited in the same way.
Tradeoff
Arbitrage is verifiable in advance: the spread is either there or it is not. Forecasting needs a model, a sample, and the patience to tell edge from variance — and it can be wrong for a long time while looking fine. I traded a small certain edge for a larger uncertain one, and most of the machinery that exists now is there to tell noise from signal.
03 — Problem
Slice trading history finely enough and something always looks like an edge. A cohort going twenty-nine for twenty-nine implies, under Kelly, betting everything — which is a fact about twenty-nine observations, not about the world.
Approach
The guards are written down as checks rather than intentions: the Kelly caveat sits at the point of computation, and a single-row skew test asks whether one row is more than forty per cent of a total before any aggregate is trusted. Position size is a bounded fraction rather than a Kelly output, so the model's confidence cannot translate directly into exposure.
Tradeoff
It leaves money on the table when an edge is genuine and large. That asymmetry is deliberate: over-betting a false positive with live capital is unrecoverable in a way that under-betting a true one is not.
04 — Problem
A trading system that grades its own homework will report that it is doing well. Fill assumptions, slippage estimates, which trades count as settled — every layer is somewhere to be accidentally generous.
Approach
Reconciliation and validation are scheduled work rather than something I run when curious. A fill reconciler compares intended against actual, a P&L validation guardrail runs daily as a self-test, and the audit documents record data defects and trust reviews as artifacts rather than as notes.
Tradeoff
A large part of the codebase does no trading at all. It exists to check the part that does — and it is the honest reason the repository is as sprawling as it is. Of the tracked Python files, the overwhelming majority are one-off analysis scripts kept from questions I asked it. That is what a system looks like when it has to keep trading while being understood.
Ask about arbor
Answers are drawn from the notes and source behind this case study, and cite what they read.
10
What it did
138
Days trading unattended
2,637
Commits
107
Active days
Four
Generations
Measured 26 August 2026