Edge Terminal
A prediction-market scanner built to say no: it hunts +EV edges across Kalshi and Polymarket, and sits empty whenever nothing clears the bar after fees, slippage, and uncertainty.
Overview
Edge Terminal scans sports prediction markets and asks one question of every market it sees: is the executable price sufficiently below a conservative estimate of true probability that the trade stays positive-expected-value after fees, slippage, liquidity effects, and uncertainty? If the answer is no, there is no signal. An empty scanner is the expected state, not a failure.
Problem
Finding disagreements between venues is trivial — they disagree constantly. Knowing whether a disagreement is an edge is the hard part, and it is where hobby scanners quietly cheat: they compute raw price differences, ignore the costs of actually taking the trade, and never settle their own signals, so nothing is ever proven wrong. A scanner that cannot be wrong is not a scanner.
Scan pipeline
Ingest
Normalize
Estimate
EV gate
Paper position
Settlement
The gate is the product. Everything upstream of it is plumbing, and everything downstream exists so a claim about expected value can be checked against a ledger later.
Decisions and tradeoffs
TypeScript for the quant layer, against the original plan
The specification preferred Python/FastAPI for EV, risk, and calibration. There was no Python service infrastructure here, while auth, database, deploy, and UI were all already TypeScript. For a solo build, splitting across two languages and two runtimes roughly doubles the surface area for no modelling benefit at this scale.
A Python service, which is the better answer the moment the modelling gets heavier than logistic regression. Written down as a revisit condition, not a permanent verdict.
Postgres unique constraints for execution locking
Idempotent execution needs a lock. A unique constraint on the natural key gives one for free, in a database the app already has, with no new dependency and no new failure mode.
Redis — which would have needed a marketplace ToS acceptance in a browser to provision, and buys nothing here that a constraint doesn't.
A daily Vercel Cron job instead of a persistent worker host
The long-running worker still exists and can be run locally at any interval, but production scanning is a scheduled function hit once a day. That fits the cadence the data actually changes on, and costs nothing.
A persistent droplet, which is the right answer for sub-hourly scanning and the wrong answer for a system that has not yet earned it.
A logistic-regression baseline, not gradient boosting
There is no historical out-of-sample data yet — the system only recently started running. A boosted model fit on data that does not exist would be a more sophisticated way of overfitting nothing.
LightGBM or XGBoost, deferred until there are settled outcomes to train against. A considered non-decision rather than a rejection.
Safety posture
This system touches money-shaped things, so the defaults are deliberate.
- Paper mode by default — live and auto trading sit behind flags that are hard-disabled
- No real-money execution path has been exercised against real credentials, by design
- Signed order placement exists as code for both venues and is gated off, so enabling it is a reviewable decision rather than a rewrite
- The optional LLM features degrade to an explicit “data source not configured” state rather than fabricating a summary when unconfigured
Results
- 245
- 12
- Daily
- Paper only
Lesson
In anything market-shaped, the ledger is the product. A strategy you cannot audit is indistinguishable from luck, and a scanner that never returns nothing is telling you about its threshold rather than about the market.