Self-improving algorithmic trading bot with Claude-Code-driven reflection.
Project description
hedger
A self-improving algorithmic trading bot. Runs on a single VPS, paper-trades by default, reflects on its own behaviour nightly.
pip install -e .
hedger install # creates a 600-mode envfile
hedger where-keys # prints the path + edit command
# ...edit the envfile, paste your ALPACA_* (and optionally ANTHROPIC_API_KEY)...
hedger doctor # round-trips the Alpaca paper API
hedger backtest --strategy sma_crossover --symbols SPY,QQQ # uses Alpaca data by default
hedger tick # one paper tick against alpaca:paper
hedger serve # start the scheduler
What it is
A layered, plugin-driven trading system that runs the same code in backtest, paper, and live. Strategies, data sources, brokers, sizers, tax policies, and notifiers are all plugin seams. Every artifact (bars, signals, decisions, orders, fills, positions, news, drifts) lives in a Mapping-shaped store on disk — no database to install, no external services to manage.
The nightly reflection cycle spawns Claude Code as a subprocess, hands it a daily brief of trading activity and the loaded skills, and lets it propose-and-test improvements between 22:00 and 06:00 local time. Every change is gated by pytest; failures roll back atomically via git.
What it isn't
- An HFT system. The sweet-spot cadence is 1–4 hours; below 5 minutes the infrastructure cost climbs sharply for diminishing returns.
- A "press button, get rich" tool. Read
MANUAL_CHECKLIST.mdfirst. Paper-trade for 2–4 weeks before any real capital. - A black box. The mall on disk is your full audit trail.
cat .hedger/decisions.jsonlshows you exactly what the bot decided and why.
Read these in order
MANUAL_CHECKLIST.md— the things only you can do (account signup, API keys, server provisioning).ARCHITECTURE.md— the data flow, the four plugin seams, and why the design choices.ALPACA.md— Alpaca API reference focused on this codebase.RESEARCH.md— the survey of frameworks, brokers, LLM trading patterns, and tax considerations that shaped the scaffold (Vancouver-cited).CLAUDE.md— the house rules the reflection cycle is bound by.hedger/misc/CHANGELOG.md— what's shipped and what's known to be missing.
Install
Requires Python 3.11+.
git clone <this-repo>
cd hedger
python -m venv .venv && source .venv/bin/activate
pip install -e .
The install pulls in everything you need to run paper trading and the LLM-news strategy: alpaca-py, anthropic, pandas, pyarrow, apscheduler, structlog, argh, vectorbt, ccxt, yfinance. Heavy / optional groups:
pip install -e '.[dev]' # pytest, ruff, mypy, ipython, jupyterlab
pip install -e '.[research]' # matplotlib, plotly, seaborn, scikit-learn,
# statsmodels, empyrical-reloaded, pyfolio-reloaded,
# alphalens-reloaded, quantstats — wired through
# hedger.research for the reflection cycle
pip install -e '.[nautilus]' # the heavier event-driven backtester
Configure
Two layers: config.toml for everything diffable; a 600-mode envfile for secrets. Secrets are never allowed in config.toml — load_config raises if it sees a *_key / *_secret / *_token / *_password / anthropic_* / alpaca_* key.
Secrets (the easy way)
hedger install # creates the envfile (mode 600), prints next steps
hedger where-keys # tells you the path, which keys are set, and the edit command
hedger install writes:
/etc/hedger.envwhen run as root, or$XDG_CONFIG_HOME/hedger/hedger.env(i.e.~/.config/hedger/hedger.env) otherwise.
Override with --envfile /custom/path if you want it elsewhere. The envfile is loaded automatically into os.environ whenever you invoke the hedger CLI (without overriding anything you export'd, so a systemd EnvironmentFile= always wins). To point hedger at a non-default path for one invocation: HEDGER_ENVFILE=/path hedger tick.
Long-running (systemd)
hedger install --systemd # also writes a unit file pointing at the envfile
# the command prints the exact systemctl daemon-reload / enable --now sequence
System scope (/etc/systemd/system/hedger.service) when run as root, user scope (~/.config/systemd/user/hedger.service) otherwise. The unit uses EnvironmentFile= so a systemctl restart hedger.service is enough to pick up rotated keys. The hedger binary path inside the unit is resolved at install time (shutil.which); nothing developer-machine-specific is baked in.
Keys you'll need
| Variable | When |
|---|---|
ALPACA_API_KEY, ALPACA_SECRET_KEY |
always, for paper or live trading |
ANTHROPIC_API_KEY |
only for llm_news strategy or the nightly reflection cycle |
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID |
only with [notify].kind = "telegram" |
HEDGER_WEBHOOK_URL |
only with [notify].kind = "webhook" |
config.toml
Copy the example and edit:
cp config.example.toml config.toml
Defaults are picked so a fresh checkout with the env vars set works against the Alpaca paper account out of the box. The fields most worth knowing:
universe = ["SPY", "QQQ", "BTC/USD"] # tickers; "BASE/QUOTE" => crypto
timeframe = "1h" # 1m | 5m | 15m | 1h | 1d
tax_policy = "none" # none | us_wash_sale | fr_pfu | crypto_lifo
[broker]
name = "alpaca:paper" # "paper" (in-memory) | "alpaca:paper" | "alpaca:live"
[data]
primary = "alpaca" # "alpaca" | "yfinance" | "ccxt:kraken"
timeframe = "1h"
[risk]
max_gross_exposure = 1.0 # 100% of NAV
max_position_weight = 0.10 # no single position > 10% NAV
max_daily_loss = 0.02 # circuit-breaker at -2% intraday
[notify]
kind = "log" # "log" | "webhook[:URL]" | "telegram" | "multi:a,b"
drawdown_alert_pct = 0.01 # notify on >= 1% intraday loss
[reflection]
enabled = true
cron = "0 22 * * *" # 22:00 every day, in your TZ below
timezone = "Europe/Paris"
max_minutes = 480 # 8h budget for Claude Code
claude_code_cmd = "claude" # path/binary
config.toml is loaded from the working directory by default; override with HEDGER_CONFIG=/path/to/file.toml or hedger <cmd> --config /path.
Verify
hedger doctor
Checks ANTHROPIC_API_KEY, ALPACA_API_KEY / ALPACA_SECRET_KEY are set, does a real round-trip to the Alpaca paper API to confirm authentication and account status, and notes whether the claude CLI is installed (only needed for the nightly reflection cycle). Pass --broker=paper if you only need the in-memory broker for backtests.
Operate
CLI commands
| Command | What it does |
|---|---|
hedger doctor |
Verify env, round-trip Alpaca, report missing pieces. |
hedger install [--systemd] |
Create the secrets envfile (600) and optionally a systemd unit. Idempotent. |
hedger where-keys |
Show the envfile path, current mode, set/missing keys, and the edit command. |
hedger list-strategies |
List registered strategies. |
hedger fetch SPY --days 30 |
Sanity-check a data source. |
hedger backtest --strategy sma_crossover --symbols SPY,QQQ --days 365 |
One-shot backtest, prints summary stats. |
hedger tick |
Run one full live tick (default alpaca:paper). Use --broker paper to override; --symbols A,B to override universe. |
hedger serve |
Block forever; ticks on cadence + nightly reflection. |
hedger status |
One-screen ops snapshot: NAV, positions, today's signals/decisions/fills, unfilled orders. |
hedger brief |
Print today's reflection brief without launching the cycle. |
hedger reflect --dry-run |
Snapshot + write brief without spawning Claude Code. Drop --dry-run to launch a real cycle. |
A normal day
hedger serve # leave running in the foreground (or under systemd)
hedger status # in another shell, anytime — quick health check
hedger brief # what would the reflection cycle see?
The scheduler ticks at the cadence in [data].timeframe (default 60 min) during market hours, fires the reflection cycle nightly at the configured cron, and:
- Risk middleware clips every decision to the caps in
[risk]. Vetoes are notified via the configured[notify]. - Tax policy annotates / vetoes decisions per
tax_policy.noneis the default;us_wash_sale,crypto_lifo,fr_pfuship. - Drawdown alert fires once per day when intraday loss crosses
[notify].drawdown_alert_pct(separate from, and earlier than, the harder[risk].max_daily_losscircuit-breaker that vetoes new orders). - Position reconciliation snapshots the broker's view to
mall["positions"]at startup and after every tick; differences vs the prior snapshot are logged asposition_driftand persisted tomall["drifts"]for the brief. - Fill streaming subscribes to Alpaca's
TradingStreamfor low-latency fill notifications, with auto-reconnect on disconnect (exponential backoff). Polling viaGetOrdersRequest(after=watermark)is the fallback.
Where state lives
Everything persistent goes under .hedger/ in the working directory (configurable via [data].cache_dir):
.hedger/
bars/ ← parquet, partitioned by (symbol, timeframe)
signals.jsonl ← every Signal a strategy emitted
decisions.jsonl ← every Decision (post-sizing, pre-execution)
orders.jsonl ← every Order submitted
fills.jsonl ← every Fill the broker confirmed
news.jsonl ← normalised news items (id, headline, symbols, ...)
positions.jsonl ← positions snapshot per startup + per tick
drifts.jsonl ← reconciliation drift events
reflections.jsonl ← per-session reflection-cycle records
briefs/ ← daily reflection briefs (json)
Inspectable on disk: head -1 .hedger/decisions.jsonl | jq .value. Mockable in tests: mall = {"bars": {}, "decisions": {}, ...} is a valid test mall.
Notifications
The [notify].kind setting controls where alerts go:
[notify]
kind = "log" # default — structured log lines only
# kind = "webhook" # POST {"text": ...} JSON to HEDGER_WEBHOOK_URL
# kind = "webhook:https://hooks.slack.com/..." # explicit URL
# kind = "telegram" # uses TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID
# kind = "multi:log,webhook" # fan-out
What gets notified: risk vetoes, tax-policy vetoes, intraday drawdown crossing the threshold (once per day), Alpaca fill-stream lifecycle (started / died / reconnecting), and reflection-cycle rollbacks.
Backtests and parameter sweeps
hedger backtest --strategy sma_crossover --symbols SPY,QQQ --days 365
Or, for parameter sweeps, use the Python API:
from hedger.backtest import param_sweep
from hedger.base import AssetClass, Symbol
from hedger.data.sources import make_source
from hedger.strategies.sma_crossover import sma_crossover
from datetime import datetime, timedelta, timezone
src = make_source("alpaca")
end = datetime.now(timezone.utc) - timedelta(minutes=20)
start = end - timedelta(days=400)
bars = {Symbol(t, AssetClass.EQUITY): list(src.bars(
Symbol(t, AssetClass.EQUITY), start=start, end=end, timeframe="1d"
)) for t in ("SPY", "QQQ")}
df = param_sweep(sma_crossover,
{"fast": [5, 10, 20, 30], "slow": [50, 100, 150]},
bars, max_workers=4)
print(df.head()) # sorted by sharpe desc
Going live
Read MANUAL_CHECKLIST.md Phase 4 first. The short version:
- Paper-trade for 2–4 weeks with
hedger serveand checkhedger statusdaily. - Confirm the nightly reflection cycle runs cleanly (briefs make sense; no rollbacks for spurious reasons).
- Generate a separate live API key from the Alpaca dashboard. Put it in the envfile on the live host (
hedger where-keysto find / edit it). - Flip
config.toml[broker].name = "alpaca:live". Restart the service. - Tighten
[risk].max_position_weightfor the first month (e.g. 0.05).
Plug-in seams
Add a new strategy / broker / data source / sizer / tax policy / notifier without touching the runner. See ARCHITECTURE.md for the patterns and .claude/skills/ for the conventions.
hedger/base.py ← dataclasses + Protocols (the trading vocabulary, SSOT)
hedger/config.py ← TOML + env-var config
hedger/data/ ← sources (Alpaca, yfinance, CCXT, AlpacaNews) + stores (the mall)
hedger/strategies/ ← plugin-registered; ships sma_crossover, llm_news,
donchian_breakout, bollinger_meanrev, xs_momentum,
pairs_zscore, pca_residual_revert, pead_drift
hedger/execution/ ← brokers, risk middleware, sizers
hedger/tax/ ← TaxPolicy plugins (none / us_wash_sale / fr_pfu / crypto_lifo)
hedger/backtest/ ← engine + param_sweep, sharing the live code path
hedger/research/ ← reflection-mode toolkit: performance metrics,
cointegration screening, signal IC, tear sheets
(optional — install via `pip install -e '.[research]'`)
hedger/live/ ← runner.tick() + APScheduler
hedger/reflection/ ← daily brief + Claude Code subprocess orchestrator
hedger/notify.py ← Notifier protocol + log/webhook/telegram impls
hedger/.claude/skills/ ← skills loaded by the nightly reflection cycle
Tests
pytest -q # ~170 tests, ~12 s
pytest tests/test_alpaca_integration.py -v # only runs when ALPACA env vars set
pytest tests/test_llm_news.py -v # includes a live Anthropic round-trip
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hedger-0.1.5.tar.gz.
File metadata
- Download URL: hedger-0.1.5.tar.gz
- Upload date:
- Size: 132.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cbf5aaa1891e630b87aaf3ae4f81511a8677a1d22cfbb400535b165ca7ca324
|
|
| MD5 |
9204de613366c84d03a4ffb68043b4c1
|
|
| BLAKE2b-256 |
bd066b46ac1c80cab80f51b4987a543da8cb83d7572b3aeba2d9d4cda0ad0628
|
File details
Details for the file hedger-0.1.5-py3-none-any.whl.
File metadata
- Download URL: hedger-0.1.5-py3-none-any.whl
- Upload date:
- Size: 124.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9babc1ac333bf749a7d2a7c93796c27cd267bac44e45bb6eaf30d018a5d8393b
|
|
| MD5 |
7c1c0d49be851b07e0641544a5f903b8
|
|
| BLAKE2b-256 |
798d24eebae5a01fde036d2e70e224c6b2b5f8cea63aa46641d11c41c2ace5c3
|