Skip to content
All projects
In progress · Paper trading

Quant Lab

Runs rule-based strategies on US daily bars from Toss Securities' Open API, compares them in backtests, and paper-trades them on a schedule. No real orders, and every decision is explained in plain words.

Quant Lab home: the bot's status, the paper portfolio and today's decision in plain words
What
Web app + worker · for my own use
Since
2026
Role
Solo
Runs on
Docker Compose · private, over Tailscale

About

The end goal is automated trading. The first milestone is smaller on purpose: a bot that paper-trades on a schedule for two weeks with nobody touching it, and no code path to a real order at all.

It's built so someone who doesn't know stocks can follow along. Strategies have nicknames (“slow trend rider” is the 50/200 golden cross), settings are choices, and each decision reads like “It's trending up. Buy QQQ when the market opens tomorrow.” It's a private tool, so there's no link; the screenshots use synthetic data, since Toss market data can't be shared.

What it does

Daily bars
Pulls each symbol's full history into PostgreSQL and checks for gaps, duplicates, non-trading days and outliers. Re-syncs when adjusted prices change.
Backtests
Four well-known strategies — golden cross, Faber's 200-day line, Connors RSI(2), turtle breakout — each set against just holding.
Scheduled paper trading
Two hours after the US close, the worker decides on the finished bar and fills at the next day's open, with fees and slippage.
Telegram alerts
Signals, fills, warnings, restarts and the day's portfolio value. An outside heartbeat speaks up if the worker or server stops.
Trying a strategy: four strategy cards, a symbol and a period to test
A backtest result: the strategy against just holding, with a one-line summary
Year by year, the strategy against just holding
Price chart with the 50- and 200-day averages and the days it bought and sold

How it’s built

Backend
Python 3.14 · uv · FastAPI · Pydantic v2 · SQLAlchemy 2 · Alembic · pandas
Frontend
React · TypeScript · Vite · TanStack Query · Lightweight Charts
Data
PostgreSQL 17 · Toss Securities Open API (read-only) · exchange_calendars
Infra
Docker Compose · AWS Lightsail (Terraform) · Tailscale · GitHub Actions
Quality
pytest · ruff · mypy (strict) · oxlint

Notes

Backtest and bot share one function

Deciding and filling are the same code in backtests and in the worker. Replaying the scheduled run over past trading days on real QQQ data matched the backtest down to the fills.

Today's bar isn't finished yet

Toss starts filling in the day's bar before the US market even opens. Only bars two hours past the close count, and a later change triggers a re-sync.

One order, however many restarts

Unique keys on bot, symbol and trading day, one pending order per bot, and an advisory lock so only one worker runs. Evaluating the same bar again does nothing.

Trend following buys calm, not returns

On QQQ since 2000, the 50/200 cross made 10.5% a year with a −36% worst drop, against 8.1% and −83% for holding. Since 2010, holding wins on return.

Not investment advice. Every order is simulated, and the screenshots use synthetic data.