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.

quotesproposesmust passno other routeordersobservedhaltsOdds feedExternal pricesKalshiQuotes and executionfeatures/Per-event stateagents/Propose, never executecontracts/decisionOne typed shapeexecution/gatesStructural, not advisoryexecution/executorThe only order pathThe accountantAttribution at birthdeadmanGuards that act
Fig. 1the order path, and why it is the only one
  • 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 [];  }}
The decision this shows is the one I did not make. The guard is correct, and its contract is stated in capitals — and MUST is a word in a comment, not a property of the system. Eighty-six files in that repository reach the order endpoint; one imports this module. The gap between a rule that is documented and a rule that is enforced is what the entire rewrite exists to close.

Excerpt — trimmed for reading, not a full file.

09

The hard parts

01Problem

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.

02Problem

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.

03Problem

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.

04Problem

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