QuantForge

Start Here: Run QuantForge in 5 Minutes

This page takes you from an empty directory to a working backtest and a live-updating strategy loop — without touching an exchange account, and without risking a cent.

Everything up to and including dry-run mode needs no API keys at all. You only add credentials when you deliberately decide to.

This is the hands-on path. For the story behind the project — why Rust, why CLI-first, why determinism is the whole point, and where it's heading (including the planned Python research bridge) — read About QuantForge.

What is QuantForge?

QuantForge is a CLI-first trading systems framework written in Rust. It gives developers a deterministic path from idea to execution:

sync data → validate → backtest → dry-run → live (only when explicitly enabled)

Each stage writes to a local SQLite file you own and can inspect. There is no hosted service, no account to create, and no UI — the CLI is the product.

Four design promises shape everything you'll run below: decimal math (never floats), closed-bar evaluation (no look-ahead), local-first state, and dry-run as the default. You'll feel each one in practice on this page; the About page explains the reasoning.

Today's scope: Binance Spot, SQLite storage, one built-in SMA crossover strategy, strategies written in Rust. (A Python research bridge — exporting data to notebooks, and eventually defining strategies from Python — is on the roadmap; the About page explains the plan.)

Before you start

You need a recent stable Rust toolchain:

rustc --version

If that's missing or badly outdated, install or update via rustup.rs. (If your toolchain is too old, the build will say so clearly — rustup update fixes it.)

Quickstart

1. Install

cargo install quantforge

That gives you a quantforge binary. All examples below use it directly.

Prefer to run from source while the project is moving quickly?

git clone https://github.com/formaldehid/quantforge.git
cd quantforge
cargo run -- --help

Everything below then works the same, with cargo run -- in place of quantforge.

On --db: every command accepts --db, but you rarely need it. It defaults to data/market.sqlite and the parent directory is created automatically. Set QF_DB in your environment to change it globally.

2. Sync some history

This is the step most people get wrong, so it's worth thirty seconds of attention.

data sync with no time bounds starts at the current moment and then polls forever — it never exits, and it gives you no history to backtest against. For a first run you want a bounded window instead:

quantforge data sync \
  --symbol BTCUSDT \
  --interval 1m \
  --start 2026-07-01T00:00:00Z \
  --end 2026-07-08T00:00:00Z

Once --end is set, the sync pulls the whole range in batches, prints a summary, and stops:

iterations: 1
written: 10080
last_open_time: 2026-07-07T23:59:00Z

A week of 1m candles is about 10,080 bars — plenty for the backtest below. Timestamps are RFC3339; supported intervals are 1s, 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w.

3. Validate what you stored

Never backtest on data you haven't checked:

quantforge data validate --symbol BTCUSDT --interval 1m

market: binance_spot BTCUSDT 1m
candles: 10080
issues: 0

This catches duplicates, gaps, out-of-order bars, and OHLC values that don't make sense. Investigate anything above issues: 0 before trusting a result — a gap in the series quietly distorts every downstream number.

4. Run your first backtest

quantforge backtest \
  --symbol BTCUSDT \
  --interval 1m \
  --fast 20 \
  --slow 50 \
  --cash 10000 \
  --fee-bps 10

The output has this shape (your numbers depend entirely on your window):

strategy: sma_cross
final_equity: ...
total_return_pct: ...
max_drawdown_pct: ...
trade_count: ...
trade: entry=... @ ... exit=... @ ... qty=... gross_pnl=...

That's the full loop: sync → validate → backtest, entirely offline, no credentials.

A note on window sizes: --fast must be strictly smaller than --slow, and the strategy produces no signal until it has seen --slow closed bars. With --slow 50 the first 50 candles are warmup. Sync a window comfortably larger than your slow window or you'll get trade_count: 0 and wonder why.

Dry-run vs live mode

This is the distinction the whole design turns on.

Dry-runLive
Flag--mode dry-run (default)--mode live (explicit)
CredentialsNot neededRequired
Orders sentNoneReal spot market orders
Everything elseIdenticalIdentical

Dry-run

Dry-run runs the real strategy loop against real live-updating candles and records real run state — it simply never submits an order. It is the default, and it needs no API keys:

quantforge trade run \
  --symbol BTCUSDT \
  --interval 1m \
  --fast 20 \
  --slow 50 \
  --quote-order-qty 100 \
  --mode dry-run \
  --poll-secs 5

run_id: qf-3f2b1c9a4d5e4f8a9b0c1d2e3f4a5b6c
processed_bars: 42
submitted_orders: 0
closed_trades: 0
last_processed_open_time: 2026-07-29T09:14:00Z

Use this to shake out strategy logic, order generation, risk assumptions, and operational behaviour under a feed that actually moves. It runs until you stop it with Ctrl-C, or you can bound it with --max-loops.

Treat dry-run as the default development path. Use it far longer than feels necessary.

Live

Live mode places real orders. It requires the explicit flag and credentials from the environment:

export QF_BINANCE_API_KEY="..."
export QF_BINANCE_API_SECRET="..."
export QF_BINANCE_BASE_URL="https://testnet.binance.vision/"
⚠️ --binance-base-url defaults to https://api.binance.com/ — production. If you set API keys and --mode live without also setting QF_BINANCE_BASE_URL, you are trading real money on the real exchange. Set the testnet URL explicitly and confirm it before every live run.
quantforge trade run \
  --symbol BTCUSDT \
  --interval 1m \
  --fast 20 \
  --slow 50 \
  --quote-order-qty 100 \
  --mode live \
  --bootstrap-enter \
  --poll-secs 5

Start on Binance Spot testnet. Stay there until the behaviour is boring.

Operating a run

Once a bot is running, these commands inspect and control it. All monitor commands require API credentials, since they query your account.

Balances, run state, open orders, recent fills:

quantforge monitor status --symbol BTCUSDT

Poll that view continuously:

quantforge monitor watch --symbol BTCUSDT --poll-secs 5

List open orders, or recent trades:

quantforge monitor orders --symbol BTCUSDT
quantforge monitor trades --symbol BTCUSDT --limit 20

Cancel an order, or exit the bot-managed position:

quantforge monitor cancel-order --symbol BTCUSDT --order-id 123456789 --yes
quantforge trade close --symbol BTCUSDT --yes

Destructive commands are dry by default. Without --yes they print exactly what would happen and exit without sending anything. Run them once without the flag to preview, then again with it.

When the first run doesn't work

  • data sync never finishes. You omitted --end. With no end boundary the loop polls indefinitely by design. Add --end, or cap it with --max-loops.
  • no candles provided. The database has nothing for that symbol and interval. Usually the sync ran against different --symbol/--interval values, or wrote to a different --db.
  • trade_count: 0 from the backtest. Data loaded, but too little of it for the warmup — you need well over --slow bars before the first signal can appear. Check that data validate reports the candle count you expect.
  • fast window must be smaller than slow window. --fast must be strictly less than --slow; both must be non-zero.
  • monitor commands require QF_BINANCE_API_KEY and QF_BINANCE_API_SECRET. Expected — monitor and trade close always talk to your account. data, backtest and trade run --mode dry-run never do.
  • Nothing in the database. Check you're pointing at the file you think you are: --db defaults to data/market.sqlite relative to your current directory.

Turn up detail on any command with --log-level debug.

Safety disclaimer

QuantForge is engineering software. It is not financial advice, not investment advice, and not a promise of profitability. It does not recommend trades.

Trading involves substantial risk, including total loss of principal. You alone are responsible for your decisions, infrastructure, credentials, risk controls, exchange rules, tax position, and legal obligations. Backtested results are not evidence of future performance; they reflect one strategy over one historical window, before slippage and real-world execution.

Before you ever pass --mode live:

  • Run dry-run mode extensively — days, not minutes.
  • Use Binance Spot testnet first, and verify QF_BINANCE_BASE_URL points there.
  • Start with position sizes small enough that a total loss is irrelevant to you.
  • Never commit API keys, .env files, or credentials to source control.
  • Restrict your exchange API keys: no withdrawal permission, IP-allowlisted where possible.
  • Confirm which mode you are in before every run. The default is dry-run; the risk lives in the flag that changes it.

Where to go next

You've run the loop. Here's how to go deeper — and how to help.

Get involved

  • ⭐ Star the repo — the clearest signal that this is worth continuing
  • 🐛 Open an issue — bugs, friction, or anything in this guide that didn't work
  • 💬 Join the discussion — questions, strategy ideas, exchange requests
  • 📈 Follow the build log — every commit, tested in CI

Go deeper

  • About QuantForge — the vision (Python for research, Rust for verification and execution), the design bets, and the public roadmap
  • API documentation — the sdk module is where you write your own strategy
  • Contributing guide — cargo fmt, cargo clippy, cargo test, small reviewable commits
  • Changelog — what shipped, and what's next

One ask: if you followed this page, tell me where you got stuck. The single most valuable contribution right now is making the first run smoother for the next developer — and you just ran it with fresh eyes.