Skip to content

Repository files navigation

Kurfürst

Regime-aware Binance spot trading bot (BTC/ETH/SOL vs USDC), built for 24/7 unattended operation on a Raspberry Pi 4.

Primary goals, in order:

  1. Generate a high-quality dataset of market states and bot decisions.
  2. Survive (capital preservation, robust unattended operation).
  3. Profit — a byproduct of 1 and 2, never a reason to relax them.

See kurfurst_claude_code_prompt.md for the full spec.

Architecture

Four decision layers, each pure (no I/O), so the SAME code runs in backtest, paper and live — only the executor differs:

  1. Regime Engine (4h): TREND / RANGE / CHAOS. CHAOS = no trading.
  2. Signal Engine (1h): regime selects the playbook (trend pullback / range edge).
  3. Veto Layer: abnormal candle, spread, BTC correlation, min RR 1.5 after fees.
  4. Pressure System: LIVE → REDUCED → SHADOW, kill-switch at −20%.

The event loop (kurfurst/core/event_loop.py) wires these behind the live data feed, executor, state store and observer. The backtester (kurfurst/backtest/engine.py) drives the identical decision functions over history.

Development

python -m venv .venv
.venv\Scripts\pip install -e .[dev]
.venv\Scripts\ruff check .
.venv\Scripts\pytest

CLI

# Backtest + Monte Carlo over a parquet of 1h candles
kurfurst backtest --config config/v0.1.yaml --parquet BTC/USDC=tests/fixtures/btc_1h.parquet

# Analytics report (P&L, per-regime, veto counterfactuals, feature importance)
kurfurst report --config config/v0.1.yaml --parquet BTC/USDC=tests/fixtures/btc_1h.parquet

# Run the live loop — PAPER is the default
kurfurst run --config config/v0.1.yaml --mode paper

# Watch it: local read-only dashboard at http://127.0.0.1:8000
kurfurst dashboard --config config/v0.1.yaml

Set KURFURST_DB_PATH in .env so run and dashboard point at the same SQLite file. The dashboard opens the DB read-only and never disturbs the bot.

Modes

  • paper (default): real Binance mainnet market data (read-only keys), fills simulated internally with the same fill model as the backtester. No orders.
  • testnet: places real orders on the Binance sandbox to verify order mechanics only. Never use testnet prices for paper data — they are not real.
  • live: real money. Refuses to start unless started with --mode live (or --live) AND KURFURST_LIVE_CONFIRM in .env equals exactly I_UNDERSTAND_THIS_TRADES_REAL_MONEY, AND the clock is verified synced.

Deployment (Raspberry Pi 4, ARM64)

DB and logs MUST live on an external USB SSD (SD-card corruption is a known failure mode). chrony (NTP) is mandatory — the bot refuses to go live with an unverified clock.

sudo ./deploy/deploy.sh        # installs to /opt/kurfurst, paper mode, systemd
# edit /opt/kurfurst/.env (KURFURST_DB_PATH -> /mnt/ssd/kurfurst/kurfurst.db)
sudo systemctl start kurfurst
journalctl -u kurfurst -f

systemd restarts on crash (Restart=always); on restart the bot reconciles its state against the exchange (it never trusts local state alone).

Runbook

Graceful stop

systemctl stop kurfurst (SIGTERM) — the loop finishes its cycle, persists state, and exits. Restart re-reconciles.

Restart while a position is open

On startup the bot loads its persisted position/pending order, then (live/ testnet) queries the exchange and reconciles:

  • holding confirmed → resume (stops, breakeven/trailing state preserved);
  • no holding → position treated as closed externally;
  • unexpected holding with no record → HALT for manual review;
  • orphan orders → cancelled.

Stops are software-managed (no resting exchange stop, to keep backtest == live). A crash leaves the position briefly unprotected until restart — acceptable on a small account; the loop persists a moved stop BEFORE acting on it so a restart never resumes a looser stop than intended.

Kill-switch

At −20% of the configured initial equity the bot liquidates, halts, and requires a manual restart. Investigate before restarting.

The offline learning loop (how the strategy improves)

The bot is deterministic and never self-tunes. Learning is offline and versioned:

  1. Collect — let paper trading run (target ≥ 100 closed trades; at this frequency, several months).
  2. Export & reviewkurfurst report --config config/vX.Y.yaml .... Read: P&L per pair/regime, veto frequency and counterfactuals (is a veto saving or costing R?), pressure timeline, and the DESCRIPTIVE-ONLY feature-importance report (ignored when n < 100 — noise).
  3. Decide — a human proposes parameter changes. Strategy-level parameters (playbook entries, RR, SL/TP) only after ≥ 100 closed trades on the current version; veto/regime thresholds may be reviewed earlier.
  4. Version — copy config/vX.Y.yamlconfig/vX.(Y+1).yaml, change the parameter, add a CHANGELOG.md entry explaining WHICH report motivated it. Every DB row is stamped with the config version, so results stay attributable and versions are comparable (config_version_comparison in the report).
  5. Re-run — restart the bot on the new version. Never edit a released version in place.

Promotion to live (gate)

Only consider live after: ≥ 100 closed paper trades (ideally 200+), max drawdown within Monte Carlo expectations, no unexplained backtest-vs-paper divergence, and zero unhandled crash states. Then set the .env confirmation phrase and start with --mode live.

Known limitations (tracked for hardening)

  • Kill-switch and daily limit use the loop's REALIZED equity (tracked internally, += pnl on each close — like the backtester). This avoids the live-cash-collapse bug, but a deep UNREALIZED drawdown still halts only when the position closes; there is no intra-trade mark-to-market (auditor E3).
  • Order size is not rounded to the exchange lot/precision step; the loop skips sub-min_notional_quote orders but does not snap to the lot size, so a live order could still be rejected for LOT_SIZE (auditor E1).
  • A partial market-sell on exit keeps the residual position and retries next cycle; it does not aggressively re-sell within the same cycle.

About

aew

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages