Built & tested · paper mode only · not trading real money
A trading bot where the interesting part isn't the strategy.
A broker-agnostic execution engine for a single daily-bar setup. The strategy is about forty lines. The other 95% of the codebase exists to answer one question: what happens when the process dies halfway through placing an order?
160
passing tests
pytest
60
paper trades required before live
0 logged · gate shut
4
broker adapters, one interface
paper · alpaca · rh_crypto · robinhood
$0
real capital deployed
journal on disk: zero orders
The Problem
Automated trading fails at the plumbing, not the signal.
Every hobby trading bot has the same failure modes, and none of them are about picking the wrong stock. It crashes between sending an order and recording it, restarts, and sends the order again. It fills an entry and never gets a stop in. It sells a position twice because two exit orders were live against one lot. It quietly manages a position you opened by hand. Each of those loses real money in a way a backtest will never show you.
What I Built
Six invariants, enforced structurally.
Duplicate orders are impossible
Every signal maps to a deterministic client_order_id, and a SQLite journal claims that id before the broker call. Crash and restart, the same id is re-derived and the order is skipped rather than re-sent. A test fires twelve concurrent writers at one journal from separate connections and asserts exactly one wins the claim and eleven lose — the real failure mode being a scheduled job firing twice, or a retry racing the original run.
It fails closed
DRY_RUN=true and ALLOW_LIVE=false are the defaults. A live-capable broker adapter refuses to start unless you explicitly opt in. There is also a halt kill switch that blocks all new entries.
Nothing sits unprotected
A stop goes in immediately after a fill. Failing to place one is logged as an error, never swallowed. The stop and the 2R target are linked, so one filling cancels the other — otherwise the stale leg eventually sells shares you no longer own.
It only touches what it opened
Bot-owned positions are recorded in a bot_positions table at fill time, keyed by strategy, symbol and entry bar. Any position without a journal row is assumed to be mine and is never managed or exited.
Strategy code never imports a broker
Everything goes through one Broker interface, so paper and live run identical code paths. You are never testing something different from what you run.
Repair is a first-class command
reconcile rebuilds the journal against the broker after a crash, and manage is fully idempotent — safe to run on a cron as often as you like, because it never places a second copy of an order.
How It Works
Scan, size, place, protect, repair.
cli.py
scan # signals only — never places an order, no API keys needed
status # account, guards, open positions
run # scan + trade + manage (DRY_RUN=true just logs the intent)
manage # maintain exits on bot-owned positions — idempotent
halt / resume # kill switch
reconcile # repair the journal against the broker after a crash
The OCO problem, handled per broker. On Alpaca the two exit legs are a single broker-native OCO order, so the losing leg is cancelled server-side even if my process is dead — there is no window where both are live. The other adapters can't express that, so they get a software OCO: two GTC orders linked in the journal, with the sibling cancelled on the next manage pass. That is strictly weaker, and the code and docs say so rather than pretending the two are equivalent. The cancel itself is crash-safe: the leg is marked cancel_pending in the journal before the broker call, so a crash in that gap gets repaired instead of leaving a live sell order against shares that are already gone.
Install & Quickstart
Starts on a simulator, with no keys.
The repository is private today, so there is no public clone URL — access is by request rather than a link. Once you have a copy, setup is a virtualenv, an editable install and a copy of the example environment file. The default broker is a local paper simulator: no network, no credentials, nothing that can reach a real account.
setup
uv venv && uv pip install -e .
cp .env.example .env # .env is gitignored
cli.py
python cli.py scan # signals only — never places an order, no keys needed
python cli.py status # account, guards, positions
python cli.py run # scan + trade + manage (DRY_RUN=true just logs intent)
python cli.py manage # maintain exits on bot-owned positions — idempotent
python cli.py halt # kill switch: blocks all new entries
python cli.py resume
python cli.py reconcile # repair the journal against the broker after a crash
Watchlist is one ticker per line in watchlist.txt, or pass --symbols SPY,NVDA. Broker adapters are optional extras, so only the one you pick gets installed. Start with scan — it reaches no broker and needs no credentials at all.
| BROKER= | Official API | Paper mode | Notes |
| paper | n/a | Yes | Local simulator. No network, no keys. Start here. |
| alpaca | Yes | Yes | Recommended for equities. Only adapter with broker-native OCO exits. |
| rh_crypto | Yes | No | Official Robinhood Crypto API. Crypto only; the setup was never tested on crypto. |
| robinhood | No | No | Unofficial equities adapter. Not a recommended path — see below. |
On the unofficial Robinhood equities adapter: it exists in the codebase and the README documents it as a bad idea. Robinhood publishes no public stocks API, so that adapter impersonates the mobile app — it breaks Robinhood's terms of service, accounts have been restricted for automated access, it can break without notice, it has no paper mode, and it requires storing a password and TOTP seed locally. It is disclosed here for completeness, not offered as an option. Use Alpaca.
The Stack
Small surface, deliberately.
Python
uv
SQLite journal
Alpaca API
Robinhood Crypto API
Local paper simulator
Deterministic client order IDs
Idempotent reconciliation
Promotion gate
pytest · 160 tests
Four adapters implement one Broker interface, so paper and live run identical code paths and you are never testing something different from what you run. The default is the local simulator. Alpaca is the recommended real adapter, because it publishes an official API, offers a genuine paper mode, and is the only one that can express a broker-native OCO exit pair.
Honest Status
It is not making money, and I won't pretend otherwise.
Built and tested
160 tests cover the safety properties directly — duplicate suppression under concurrency, promotion-gate enforcement, unprotected-position detection, ownership boundaries, closed-bar-only signals. The engine runs against the paper simulator today.
No real capital, ever
It has never traded live money and the journal on disk holds zero orders. The promotion gate is shut and stays shut until a paper record exists. No sales, no users, no returns are being claimed here.
Not a signals service
It does not publish alerts, sell picks, or run as a service for anyone. It is a program you run on your own machine against your own broker credentials, and it manages only the positions it opened itself.
No return promises
The setup it executes — AVWAP-reclaim — did not survive my own adversarial re-test: the signal's own contribution measured +0.030R with a 95% CI of [−0.014, +0.077], crossing zero. That is not a validated edge. This is a well-engineered execution engine wrapped around a strategy I do not believe in enough to fund.
⚠ Known gaps, from the README: positions opened before the ownership table existed are unmanaged by design; the software-OCO brokers have a real cancel-latency window; partial fills are handled conservatively rather than precisely; and market-hours checks on the non-Alpaca adapters use a simple clock that doesn't know about holidays. Nothing here is financial advice, and none of it is an invitation to run this with your money.
🔒
Repo access: the repository is private. Happy to walk through the journal design or the reconciliation logic on a call —
email me.
The reasoning behind the guardrails is written out on its own: what has to be true before paper becomes live, why the journal is the system of record, and which brokers publish an API worth building against.
FAQ
Common questions.
What does this trading bot actually do?
It executes one daily-bar setup end to end: it scans a watchlist after the close, sizes a position from a fixed percentage of equity risked against the entry-to-stop distance, places the entry, immediately attaches a stop and a 2R target, and then manages those exits until one fills or a 20-bar time stop fires. There is no intraday logic and none was validated.
Can it trade real money right now?
No, and it structurally refuses to. Two separate gates stand in the way: a live-capable broker adapter will not start unless ALLOW_LIVE is explicitly set to true, and beyond that the promotion gate blocks real funds until a genuine paper track record exists. The record today is zero closed paper trades, so the gate is shut.
What is the promotion gate?
It is a check inside the engine that refuses to trade real money until the journal shows at least 60 closed paper trades at a positive realized expectancy. The rule originally lived only in a markdown design doc, which meant a single environment variable could bypass it, so it was moved into code where it runs on every pass. Overriding it requires a deliberately named environment variable and it logs a warning when used.
What happens if the process crashes while placing an order?
Every signal maps to a deterministic client order id, and a SQLite journal claims that id before the broker call rather than after it. On restart the same id is re-derived and the order is skipped instead of being sent twice. A concurrency test runs twelve competing writers against one journal and asserts that exactly one of them wins the claim.
Which broker should I use?
Alpaca, for two reasons: it publishes an official API, and it offers a real paper-trading mode so the safety work can be exercised without money at stake. It is also the only adapter that can express a broker-native OCO exit pair, which means the losing leg is cancelled server-side even while this process is dead. Start on the local paper simulator, which needs no network and no keys.
Does it come with a profitable strategy?
No, and that is stated deliberately. The setup it executes did not survive an adversarial re-test: the signal's own incremental contribution measured +0.030R with a 95% confidence interval crossing zero, which is not a validated edge. This is an execution engine wrapped around a strategy that has not earned real capital.
Is it free and open source?
There is no paid tier, no account and no telemetry, and it runs entirely on your own machine. The repository is private at the moment rather than published, so access is by request instead of a public clone URL.