Overview
Ledgerbell is the small, boring part that sits between an AI-assisted trading idea and an Alpaca paper account. The first thing that can go wrong with a trading bot is the bot, not the market: a model that suggests a 10,000 share order, a rerun that sends the same order twice, a script that crashed before its stop was placed. Ledgerbell puts the rules where the strategy cannot get around them.
I built it against a local test server written from Alpaca's public documentation, then ran it on a free Alpaca paper account. The README says exactly what ran and what did not.
Key Features
- Five scheduled routines. Premarket research, market open, a midday scan, an end of day recap and a weekly review, scheduled with plain cron on the New York clock.
- One order gateway. Every order, from the example rule or from the model, goes through a single function that applies the risk rules. It is the only place an order endpoint is called.
- Hard risk rules in code. A maximum position size, a maximum number of open positions, no short selling, a daily loss limit and a stop on every entry that lives at the broker, so it survives the program crashing.
- A kill switch. Engaging it writes a file first, then cancels every order and closes every position, then alerts. Nothing trades until a person clears it.
- A research step that can suggest and never place. Claude Code runs headless with every tool turned off and has to answer in strict JSON. The reply is validated, re-asked once with the exact error, and dropped if it is still wrong. Whatever it suggests goes through the same risk rules as any other order.
- Alerts, a dashboard and a ledger. Email and text alerts, a dashboard of positions, profit and loss and bot activity, and an append-only log of every action.
- A FastAPI app on Modal. It answers whether a proposed order would pass the risk rules and sends alerts, behind an API key, and it can never place an order. It runs on Modal's free plan.
- Amazon SES alerts and a Netlify status page. Alerts can go out through Amazon SES next to email, a chat webhook and text. A Netlify Function answers 200 while the bot is running and 503 when it has gone quiet, for an uptime monitor.
- Safe reruns. Order ids are built from the routine, the date, the symbol and the side, so a second run of the same routine cannot place the same order twice.
Technical Achievements
In a full trading day on stand-in prices against the local test server, the bot opened three positions with stops, the price tape was dropped 40 percent overnight, the daily loss limit tripped the kill switch, and the next open was refused with the account holding no positions and no orders. The tests drive the real client over HTTP against that server, covering orders with stops, a duplicate order id, a 401, paging through bars, stops firing on a moving tape and the loss limit tripping the kill switch.
On a Monday evening with the market closed, the routines ran against a real Alpaca paper account: account, clock and daily bars, a one share entry with an attached stop, the same order id refused on a second send, then everything cancelled and closed. That run found a bug the test server had hidden.
A second round of real runs, on the paper account with the market open, added an options leg, a second price provider and a run log. The options leg can only buy one long call or put to open, inside a premium cap and an expiry window, with a stop on the premium; a short leg, a spread, a sell or a second entry with no stop is refused with a named reason. The runs read 3,804 active contracts for one symbol, bought one contract with its stop accepted, refused a second at the premium cap and closed everything. Finnhub supplies prices from its free plan, which has no candles, so Alpaca still supplies the bars. Each routine appends a row to an Airtable table through its REST API. The real runs found two more bugs: the free feed's quotes are modified, so an entry priced at the ask rested unfilled, and the midday scan measured options against the share stop. Both are fixed with tests. A later round added a Polygon price provider next to Finnhub and Alpaca, with its key sent as a Bearer header, and a text message alert through Twilio next to email and the chat webhook. The email alert is tested over a real SMTP conversation with a small local mail server, and the Polygon and Twilio code runs against local stand-ins written from the vendors' documentation. The suite is now 271 tests with 86 deliberate breakages, each caught. Alpaca's bars default to today, so daily bars came back empty. I fixed it and added a test.
The last round added three serverless pieces, each run for real. A FastAPI app deployed on Modal's free plan answered its first call in 4.6 seconds and about 1.1 seconds once warm from the Philippines, refused calls with no key or a wrong key, cut an oversize order to the size limit and refused a symbol off the watchlist. Two alerts were delivered through Amazon SES to a verified address, with a dedicated key that can only send from one verified address. A Netlify Function status page answers 200 at its public address. The SES code is tested over real HTTP against a stand-in written from the documentation, the API with FastAPI's test client and the status rules with Node's test runner, and 15 more deliberate breakages cover these pieces.
What I Learned
A test server written from the documentation proves the code does what the documentation says, and a real account shows where the documentation and reality differ. Run both. I also learned to keep a model's job small: when it can only propose, and a plain function decides, a bad answer costs a refused order and nothing more.
Limits
Fills, the stops and the kill switch were seen on a real paper account across two sessions, the second with the market open, but a stop firing at the broker and the daily loss limit on a real account are tested against the local test server only, SES alerts go to verified addresses, the limit of an SES account on its starting plan, and the 60 to 90 days of paper running a real trading project needs have not been done. Stocks are long only and whole shares, options are single long calls and puts bought to open, and nothing here is investment advice.
Tech Stack
Python, FastAPI, Modal, Amazon SES with boto3, a Netlify Function, requests, pytest, ruff, Claude Code in headless print mode, plain cron, GitHub Actions.