Cross-venue arbitrage trading bots for crypto (Binance, Coinbase) and prediction markets (Polymarket, Kalshi), in Python. Paper-trading only for the initial build — simulated fills against live orderbooks, no real capital at risk.
"Beat the market" doesn't mean pretty demos. It means a system that is honest about fees, slippage, and latency — so when it says there's an edge, you can trust it before risking a dollar.
- Streams orderbooks from every enabled venue in parallel.
- Detects cross-venue arbitrage opportunities:
- Crypto: buy BTC/ETH on the cheaper venue, sell on the richer one.
- Prediction (YES+NO): buy YES on venue A and NO on venue B when their combined cost is strictly below $1 (risk-free guaranteed payout at settlement).
- Prediction (same side): classic cross-venue arb when the same outcome trades at different prices.
- Walks the books level-by-level to compute the maximum profitable size, stopping at the first loss-making increment.
- Applies a honest-slippage haircut: configurable latency penalty, exchange-specific taker fees (including Kalshi's exact per-contract formula), and a cap on how much of top-of-book depth you can eat in one shot.
- Runs every opportunity through a risk gate stack (min net edge, per-trade notional, daily loss kill switch, gross exposure).
- Executes paper fills against the real book and writes every fill, skip reason, and balance delta to SQLite.
yello pnlprints realized/unrealized PnL, balances, recent fills, and — critically — a histogram of why opportunities were skipped.
# clone, cd, then:
uv venv --python 3.11
uv pip install -e ".[dev]"
cp .env.example .env # paper mode needs no real keys
# verify the build
uv run pytest tests/unit -q
# sanity-check live endpoints
uv run yello doctor
# one-shot scan: prints top-of-book and any net edge for a config pair
uv run yello scan --pair BTC-USDC
# full loop (Ctrl-C to stop)
uv run yello run
# report
uv run yello pnl --hours 1src/yello/
core/ models, money, fees, clock
adapters/ Binance, Coinbase, Polymarket, Kalshi
matching/ cross-venue symbol registry + fuzzy prediction matcher
arb/ walk-book edge math + opportunity detectors + sizer
execution/ paper fill simulator + settlement poller
risk/ gate functions + persistent kill-switch state
runtime/ async loop, book bus, feeds
pnl/ realized/unrealized accounting + rich CLI tables
storage/ SQLite schema + typed repo
cli.py typer entry points
config/
yello.yaml venues, fees, pairs, risk, execution
market_map.yaml confirmed prediction market pairs (source of truth)
tests/unit/ 46 deterministic, offline unit tests
M1 (crypto, shipped): Binance + Coinbase adapters, crypto arb detector, walk-book paper executor, risk gates, full CLI, PnL reporting, 46 unit tests.
M2 (prediction, shipped): Polymarket + Kalshi adapters, YES+NO and
same-side arb math, fuzzy title matching for yello markets suggest,
YAML-confirmed pairs as source of truth for trading.
M3 (not shipped): Full risk state persistence across restarts,
Coinbase level2 WebSocket with sequence-gap handling, yello backtest
from recorded book snapshots, prediction-market settlement poller.
- Retail fees kill the crypto arb edge. Coinbase retail taker is 60
bps; BTC cross-venue spreads rarely exceed 30–50 bps. Expect the paper
PnL curve to be slightly negative at retail tiers. That is a feature —
it's the system telling you the edge isn't there. Bump yourself to an
advanced fee tier in
config/yello.yamlto see how the numbers change. - USDT ≠ USD. Binance is USDT-denominated and Coinbase is USD. They're not the same asset. Either price the basis into the edge, or trade USDC-on-both pairs (the shipped config does the latter).
- Prediction market matching is semantically hard. Polymarket and
Kalshi frequently phrase "the same" event with different resolution
sources, cutoff times, and rounding.
yello markets suggestproposes candidates via fuzzy title match, but never auto-trades fuzzy matches — a human has to confirm each pair intoconfig/market_map.yaml. - Kalshi fees are dynamic per-contract, not a flat percentage. The
exact formula
ceil(0.07 * price * (1 - price) * contracts * 100)cents is implemented insrc/yello/core/fees.py. - Polymarket live trading needs Polygon USDC and wallet signatures. Paper mode deliberately does not support that. Level-0 (unauth) public reads work fine.
- Coinbase level2 WebSocket has sequence gaps in long-running sessions. The M1 implementation polls REST at ~1 Hz to sidestep this; full WS handling lands in M3.
- Clock skew matters. A REST-snapshot arb that looked profitable
300 ms ago may be gone. The
book_latency_mshaircut is the defense. Crank it up if you're suspicious of ghost edges.
All non-secret config lives in config/yello.yaml. Secrets (if any)
live in .env. Every number that affects PnL is tuneable:
execution:
book_latency_ms: 150 # how much top-of-book depth to treat as stale
taker_only: true
walk_fraction_cap: 0.25 # max fraction of top-5-level depth per trade
risk:
max_notional_per_trade_usd: 500
daily_loss_limit_usd: 100
min_net_edge_bps_crypto: 5
min_net_edge_usd_prediction: 0.01uv run pytest tests/unit -q # 46 tests, no network, sub-secondThe test suite covers: crypto walk-book math, prediction YES+NO and same-side math, fill simulator, fee schedules (including exact Kalshi formula), crypto symbol normalization, risk gates, and SQLite repo smoke tests.
MIT.