A trading bot whose backtest is the live run.
An event-driven trading bot in Rust. A backtest and a live run execute the same strategy code through the same engine loop; only the data feed and broker are swapped. Binance spot today, built so equities, options and perpetuals can follow without a rewrite.
Status: v1. Backtesting and paper trading (Binance testnet or Demo Trading, and paper on MAX's live bars) are the supported paths. Real-money trading is behind three separate locks and has not been exercised against mainnet. No strategy here has a demonstrated edge: backtest results come from a stated fill model and are not a promise.
Safety model
These are invariants in the code, each pinned by tests, not conventions.
Money is exact
Prices, quantities, balances and fees are Decimal in unit types (Price,
Qty, Notional), never floats. Config and data files take money as strings; a
bare number is an error.
No lookahead
A strategy sees data only up to the current closed candle, and its orders fill no earlier than the next one. A test rewrites every candle after t and requires nothing up to t to change.
Secrets from the environment
API keys come only from BINANCE_API_KEY / BINANCE_API_SECRET. No config field
can hold them: api_key in a file is an unknown-field error.
Three locks for real money
The default is paper on testnet. Live needs mode = "live",
exchange.network = "live" and --i-understand typed on the command line.
How it works
One-way data flow. The strategy is a pure decision; the provider is the only thing that differs between a backtest and a live run.
Each event, in order
- The broker settles pending orders against the new event, so a decision at t fills at t+1.
- Fills update the strategy's position before the bar joins its history.
- The strategy decides; risk rounds to lot and tick (never more aggressively) and checks venue filters.
Stated fill model
- Market orders fill at the next candle's open, taker, with slippage you must specify.
- Resting limits fill only when price trades through them; a touch is not a fill.
- Stop and target in one candle: the model assumes the worse.
Built-in strategies
Each implements one trait and registers itself, so it runs in backtests and live unchanged.
Each one says what it bets on and how it fails. Compare any of them against buy_and_hold on the
same coin after costs.
| Name | Bets on | Fails when |
|---|---|---|
| buy_and_hold | Nothing: the benchmark. Buys once, holds forever. | The coin falls. |
| dca | Spreading entry over time (hour, day, week, month). | Removes timing risk, not direction risk; buys all the way down. |
| sma_trend | Trends: long above the SMA, cash below. | Ranges cause whipsaws. |
| dual_ma_cross | Trends: long while the fast average is above the slow one. | Ranges and lag. |
| donchian_breakout | Breakouts beyond the prior channel continuing. | False breakouts in ranges. |
| avwap | Price holding above its anchored VWAP. | Chop around the anchor. |
| rsi_reversion | Ranges: buys oversold, sells overbought. | Sustained downtrends (no stop, by design). |
| momentum_rotation | Recent winners among several coins keep winning. | Sharp reversals, choppy leadership. |
| ratio_hop | Rotating coins only when units ratchet up after fees, with a freefall guard. | Maximizes units, not value. |
| grid | Oscillation inside a price range. | Trends either way: holds every level falling, sells out rising. |
Quick start
Prebuilt binaries for Linux, macOS and Windows are on the
releases page. Building from source needs only
rustup; the toolchain is pinned.
# Cache public history (no API key needed) and backtest on it
make fetch-data SYMBOL=BTCUSDT FROM=2024-01-01
make backtest STRATEGY=buy_and_hold SYMBOL=BTCUSDT \
FROM=2024-01-01 TO=2024-06-01 SLIPPAGE_BPS=5 PARAMS=my-params.toml
# Paper trade against Binance testnet
export BINANCE_API_KEY=… BINANCE_API_SECRET=…
make run-paper
Venues
Binance spot: testnet, Demo Trading and live. MAX (MaiCoin): backtests and paper trading on live bars, never orders.
Books that survive restarts
Orders are journaled before submit and fills recorded by venue trade id. On start and on every exit the bot reconciles with the exchange.
Observable, read-only
Prometheus metrics, JSON logs, a Grafana stack in deploy/, and an HTTP API that can never
place or cancel an order.