← Back to Products
Live - beta

StockBot

From a chat exploration to an options-alert bot running solo and free — and discovering that building the agent was the easy part: the hard part is getting it right.

PythonpandasAlpaca Markets APITelegram Bot APIGitHub Actionscron-job.org
View product →

Quick summary

Role
Solo builder
Period
Sept 9 → October 2026
Tech stack
Chat exploration in Claude → implementation and deployment in Claude Code, on top of Python, Alpaca, Telegram, and GitHub Actions → shadow evaluation of the recommendations
Product
t.me/CelsoStockBot

The problem

IONQ is a stock I follow regularly. StockBot’s idea wasn’t to build an ambitious algorithmic trading system from day one, but something much smaller: stop having to watch the chart all day so I wouldn’t miss an entry, and instead get a Telegram alert when technical conditions (RSI, MACD, EMA) point to an options-buying opportunity (calls or puts).

I chose to start with a single stock — the one I already follow — instead of building something generic for any ticker from the start. The idea was to validate the full concept (signal → suggested contract → alert → position tracking) on a concrete case before thinking about scaling it to more symbols.

Building it

Exploration (September 9) — Claude chat

The whole architecture was thought through and written in a chat conversation with Claude (claude.ai), without running anything yet: the technical-analysis rules, the Alpaca integration for data and the options chain, the Telegram alert format, and the GitHub Actions deployment plan. The complete code came out in one shot, but a chat has no way to install dependencies, hit a real API, or tell you whether something actually works — it stayed a complete, unvalidated design.

Implementation and deployment (September 10) — Claude Code

This is where the project went from “code that looks finished” to “bot actually running.” The repo got set up, everything was tested with real credentials, the bugs that only show up at runtime got debugged, and it went into production.

  • Python + pandas — a technical-indicator engine (RSI, MACD, EMA, ATR) computed by hand, without pandas_ta, because that library doesn’t yet support the Python version used locally.
  • Alpaca Markets API — intraday candle data and the options chain, on the free Basic plan (IEX feed for stocks, indicative feed for options) with a paper account used only for reading data.
  • Telegram Bot API — the entry/exit alert channel, and later also reading incoming messages to know if anyone else messages the bot.
  • GitHub + GitHub Actions — the repo, version control, and the initial attempt at a free host to run the bot every 15 minutes via cron.
  • cron-job.org — an external trigger that calls the GitHub API to force the workflow run, added when Actions’ native cron didn’t work (see Scaling).

Shadow evaluation and first calibrations (September → October)

With the bot running, I let it make recommendations without putting real money behind them: the alerts were evaluated “in shadow,” as if I had followed them in a simulation. The first thing that surfaced was that the rule almost never fired: a 10-day backtest across the 4 symbols showed zero signals, because it required the extreme RSI, the MACD crossover, and the price’s position relative to its EMA to coincide on the same 15-minute candle — conditions that, in practice, happen at different moments of the same move. I removed the EMA filter and moved the extreme RSI to a window of the last 6 candles, and that’s when signals started showing up.

The problem is that “fires more” isn’t the same as “recommends well.” Evaluating in shadow what it actually recommended, the recommendations turned out to be pretty bad: it recommended buying some calls and the market dropped right after, and in the simulation a fair amount of money was lost. The calibration had been done by eye, tuning against only a few days of data and with the sole yardstick of “that it generates signals” — not “that those signals are any good.”

Scaling it

  • From “GitHub Actions’ native cron” to “external HTTP trigger”: Actions’ schedule never fired on its own, not once, over more than 2.5 hours — with the configuration 100% correct (branch, permissions, cron syntax, active workflow). The known mitigations were tried (offsetting the cron minutes to avoid the load spikes at :00/:15/:30/:45, and forcing re-registration by disabling and re-enabling the workflow) and none fixed it. The fix was to take GitHub Actions out of the “scheduler” role and leave it only as the executor: now a free external service (cron-job.org) calls the GitHub API’s workflow_dispatch endpoint every 15 minutes to trigger the run.
  • From “the agent works” to “the agent gets it right”: the most important break wasn’t an infrastructure one but a quality one. The bot ran without errors, sent alerts, and respected the filters, but the recommendations were bad and poorly calibrated. Tuning thresholds by looking at a few days of data isn’t enough to know whether a rule is good; you need to build evals to test it in the lab (backtests over a wide history, with outcome metrics and not just signal counts) and production observability to measure the quality of the outputs once deployed (what was recommended, what happened afterward, how much would have been won or lost).

Results

Stage Period Result Cost
Exploration Sep 9, 2026 Complete code and architecture, never run $0
Implementation and deployment Sep 10, 2026 Bot in production, responding with real information (TA signals, market status) $0 (free plans: Alpaca + GitHub Actions + cron-job.org)
Shadow evaluation Sep → Oct 2026 The recommendations turned out bad: it recommended buying calls right before a market drop and the simulation lost a fair amount of money. Poorly calibrated rule $0 in infrastructure; the loss was simulated only, no real money

There’s no consolidated figure for the loss in the simulation, nor a systematic record of each recommendation against what happened afterward — precisely what still needs to be built. Until evals and observability exist, there’s no way to say with data how good or bad the rule is beyond these specific cases.

Lessons learned

  • Standing up the agent is the easy part; the hard part is ensuring the quality of the response. With StockBot, having something running, connected to real data and sending alerts took a couple of days. Knowing whether what it recommends is any good is still unsolved for me: the shadow recommendations were bad and the rule was poorly calibrated. Before trusting an agent you need evals to test it in the lab and production observability to measure the quality of its outputs over time.
  • It’s better to test and discover blockers and their fixes as you go, rather than trying to foresee them all upfront. Neither the credentials problem nor the GitHub Actions scheduler one could have been anticipated during design — they only showed up once running, and got solved on the fly.
  • An LLM chat with no real execution can produce code that looks finished but never ran. The entire exploration in claude.ai generated a complete, coherent project, but the only way to know if it really worked was to run it — something that environment doesn’t allow.
  • Verify credentials with a real API call, not with the absence of errors. A misloaded secret can return “no exception” and still be pointing at the wrong token or chat — you need to confirm with a concrete response (getMe, /v2/account) before assuming something was configured correctly.
  • Don’t assume a familiar piece of infrastructure (GitHub Actions’ cron) will work as documented. When it failed with no identifiable cause, having a simple plan B (an external HTTP trigger) resolved it faster than continuing to insist on the broken piece.

Want to see how it turned out?