My wife and I used to trade from separate Robinhood accounts. One of us would buy something, tell the other about it later, and hope we hadn't done the same thing twice. We could each see our own account. Seeing what we owned together took extra work.
Robinhood is fine for one person. It doesn't do anything with two.
So I built something that did. The first version took about two hours. The rest of this post includes work we added later.
The newer automation is running with simulated trades. The screenshots show examples, not evidence of investment returns.
Why we built this
We had the accounts. We had the positions. What we couldn't do was look at both at once. No API to query, no shared history, no way to ask what the combined picture looked like without manually reconciling two apps on two phones. One of us would research something; the other would make a separate call the same day.
We needed an account that spoke HTTP.
Most retail traders don't know that Charles Schwab has a free, fully documented developer API. You register on their developer portal, wait a day or two for approval, and you have OAuth2 access to your own brokerage account: quotes, positions, order placement, transaction history, all of it. We transferred both our Robinhood accounts to Schwab. The whole migration took a week, including the ACATS transfer.
What we built
The first version had about 1,500 lines of Python in the trading core, with no pip dependencies, and another 1,400 in the Telegram bot.
The CLI has about twenty commands: quote, research, theme, buy, sell, pnl, audit, and more. It talks directly to Schwab's REST API, with no third-party trading library in the way.
A risk reviewer that runs before every order. Hard limits live here in Python, not in a prompt: 2× maximum leverage, $250k maximum notional per order, a 10% daily-loss kill switch, a 20% drawdown kill switch. These cannot be overridden, regardless of what the AI says.
A Telegram bot that wraps the CLI and is the shared UI. Both of us are in the same group, both can ask questions and see the same answers. Claude Sonnet does the reasoning, with a hard daily AI spend cap of $5.
OAuth tokens live in Cloudflare KV. Orders are recorded in a local JSONL file before they reach Cloudflare D1, so a database outage alone should not erase the record.
The risk check
The Reviewer is code, not a prompt.
I wanted the risk check to run before either of us could confirm an order.
So the Reviewer runs in Python before any call to Schwab's order API. It fetches the current portfolio snapshot, estimates the post-trade leverage, checks every hard limit, logs its verdict to the database, and either lets the order through or kills it with a reason. The LLM proposes; the code decides if it is safe to proceed.
Propose → Review → Confirm → Execute: the AI proposes, the code reviews, you confirm, Schwab executes.
The decisions that follow from it
Once the Reviewer is in place as immutable code, a few other things fall into place.
The Reviewer enforces limits per order, so a large position that would otherwise hit the per-order share cap needs a different path. The system runs a full account-level risk check on the total quantity first, splits into cap-compliant legs, then gates each leg through the Reviewer independently before submitting to Schwab. Valid large trades go through. Genuinely oversized ones do not.
A conviction sub-portfolio lets us set different per-order and concentration limits for part of the same account. The account-wide limits on leverage, losses, and buying power still apply to every order.
In the workflow described above, exit rules run automatically: hard stop at 8%, trailing stop, time stop after five days, take-profit at 15%. A new entry waits for a Confirm tap in Telegram before it goes to Schwab. The later automation experiments are described in the build notes below.
The research prompts can improve over time, but there is a written contract in the codebase that defines what is evolvable and what is not: prompts can change, soft parameters can tighten, the Reviewer and every hard limit cannot. Any proposed change needs passing tests and human sign-off. One-tap rollback if something does not work out.
Every Schwab API call and AI call is logged by role, with a daily cap on AI spend. The costs command shows exactly what the week's research cost, broken down by who asked for what. If the cap is hit, AI spend stops. The money the system costs is as visible as the money it manages.
A decision not to trade is logged to the same ledger as the orders that went through: passes, rejected theses, Reviewer blocks. You can ask why the system did not trade just as easily as why it did.
How we use it
We have a shared Telegram group: me, my wife, and the bot.
She'll message the group asking about a stock. The bot fetches a brief: price, analyst targets, thematic context, risk flags. Both of us read the same answer in the same thread. We talk about it. If we want to do something, she proposes a trade. The bot sizes it, runs the Reviewer, and shows the verdict before anything is sent to Schwab.
She taps Confirm. The order goes to Schwab. Both of us see the fill confirmation in the group. Both of us can check P&L and see the same picture.
We could finally look at the same numbers and make the decision together.
What changed
Does any of this make us better investors? I have no idea. Markets are hard. Anyone who says their system reliably beats them is either lying or has not run it long enough.
Before, investing was something one of us did and mentioned to the other after the fact. Now it's a shared conversation. She asks a question, I read the same answer, we disagree on something, we ask a follow-up, and we decide together or not at all.
The AI is the third voice in that conversation. It does the research, runs the numbers, surfaces what the Reviewer raises. We make the call.
That was worth the two hours.
Build notes and current status
Built since the first version
Everything below shipped after this post first went up, on top of the Reviewer and the Telegram flow above.
- An autopilot loop. A scheduled cycle watches the market, the positions, and the account state. Deterministic exits (hard stop, trailing stop, time stop, take-profit) fire with no model involved. New ideas go through a Scanner, then a Strategist that proposes, then a Critic that only ever sees the proposal and the market data, never the Strategist's reasoning, and tries to kill it. Anything both of them clear still needs your tap before it touches the account.
- Trade plans, not just single orders. Ask for something like "split 10K across four chip names" and the bot builds one plan across all four legs, dry-runs the Reviewer on each, and shows you the whole thing before a single share moves. One Confirm executes every leg in sequence. Plans expire on their own: five minutes for a Telegram request, thirty for an autopilot entry. Neither ever fires on timeout.
- Long-term memory, in two tiers. Every decision and event gets written to an append-only log, no exceptions. A curator only promotes something to a lesson after it has shown up in at least three similar cases, and it expires after 45 days unless it holds up again. A smaller layer of consolidated beliefs merges duplicates, treats a contradiction differently depending on the market regime instead of overwriting it, and fades with time. Ask
/why NVDAin the group and it pulls its own decision history on that name. - A self-review that can't rewrite history. Every entry locks its thesis, invalidation point, and conviction before the outcome exists, in a log with no edit function. The post-trade grade scores the decision and the outcome separately, so a bad call that got lucky and a good call that got unlucky land in different buckets instead of both counting as a plain win or loss. A calibration report checks whether the times it said 70% confident actually won about 70% of the time.
- Prompts and settings that can propose their own changes. The Strategist and Critic prompts, and seventeen soft parameters, can be proposed for change by the system itself, against a written contract listing exactly what's evolvable and what never is: the Reviewer, the Trader, any hard limit. A candidate runs against real historical cases in shadow first, with no money and no live decision affected. A human still has to tap approve, and there's a one-command rollback to any earlier version.
The newer automation is being tested with simulated trades. The master switch and the auto-entry switch are off by default. In the confirmed workflow above, only risk-reducing exits can act without a human tap.
Still unproven
The autopilot loop has been running with simulated trades. Automatic entry would change the approval rule described above; enabling it for real money requires my decision and the staged rollout, starting with paper results and small positions.
The memory system is honest about being young. It won't write a lesson until it has seen the same pattern three times, and nothing sticks around past 45 days unless it happens again. Right now it has opinions on almost nothing, which is what a system that hasn't earned any lessons yet should look like.
Self-evolving prompts exist in code, not yet in daily use. A changed prompt has to run in shadow against old decisions and beat the current version before a human even sees a rollback button. We haven't run that cycle enough times to have a real before-and-after story.
Macro and news awareness (FOMC, CPI, earnings calendars, headlines) is built and wired in, but off until we've confirmed a data provider and a budget for it. Live web search for the Critic's research sits behind the same kind of flag.
The rollout starts with paper trading, then a small real position. Increasing it depends on the results.
There's still no backtest against historical price data. There is now a paper-trading mode, the autopilot default, plus a shadow-eval harness that runs a candidate prompt against past decisions before it ships. Neither establishes how the system would perform across several years of historical prices.
Once a week, one of us has to log in again. It takes thirty seconds. I still find it annoying.
The Telegram bot now runs as an always-on background worker on Render, not the Mac mini. The Mac is local-dev only; if it's off, nothing breaks.
We have only run this in roughly normal market conditions. The kill switches exist and have been tested in development. Will they behave the same way under a real crash? I don't know. Untested is untested.
I did not go into this knowing how to build a trading system. I knew Python and had worked with APIs before. The hard part was deciding what the system should refuse to do and putting those limits in code.
I haven't open-sourced the code yet. The trading core has no external dependencies and could run on most machines, but the OAuth flow and Cloudflare infrastructure are specific to how we have set things up. If you want it, email me at [email protected]. If there is enough interest I will clean it up and publish it properly. If you have built something similar, or transferred from Robinhood to Schwab for API access, happy to compare notes.
Related projects
schwagent is the closest project I found: same stack (Python + Schwab + Telegram + LLM), open source. It is much larger: 35+ technical indicators, options strategies (wheel, iron condors, covered calls), Monte Carlo backtesting, a web dashboard. It is a serious system.
Ours is intentionally smaller. No indicators, no backtesting, no options. Zero pip dependencies for the core. We wanted something we could deploy in an afternoon and trust because we'd read every line of it.
If you want full autonomy, read the agent-run trading fund post on HN. There the AI makes every call, with no human in the loop. The author documented every dead strategy with the same rigor as the wins. That discipline is admirable. We were building something different: a tool for two people to think together, with the AI helping and the humans deciding.