For a long time my trading "system" was three tabs: a charting site, an exchange demo account, and a spreadsheet journal I never kept up to date. So I built one page that does all three: Keelstone. It is in open beta and free.
This post is about the parts that were interesting to build.
The idea: a journal you cannot forget to fill in
You open a chart, press Trade and take a practice position with virtual funds. When you close it, the trade is already in your journal: entry, exit, leverage, result, the reason it closed, and a picture of the chart at both moments. Win rate, expectancy, profit factor, drawdown and the equity curve build themselves.
The detail that changed how the journal feels: a trade opened from the chart cannot be edited or deleted afterwards. Only the tag and the note can change. Once I could no longer tidy up my own history, the statistics finally told the truth.
Do not trust the browser with prices
The first version let the page send the fill price. That makes every result, and the monthly competition board, trivial to fake.
Now the server fills every practice order at the price it sees itself. A request whose price is more than a couple of percent away from the server's is refused. Stops, targets and liquidations are only accepted if the price range since the position opened really crossed the level. A position cannot lose more than its margin, the way an isolated position behaves on an exchange.
One process, one file
The whole backend is a single FastAPI process with SQLite in WAL mode, behind Caddy and a Cloudflare tunnel, in Docker Compose on one small machine. No queue, no cache server, no separate database.
That sounds reckless until you measure it. Live prices go straight from the exchanges to the browser over their public websockets, so the server never relays ticks. What is left is page loads, saved layouts, journal rows and a chat poll. In a load test with 600 simulated members, each sending a request every two seconds, the median response was 12 ms and the process used about two thirds of one core and 100 MB of memory.
Things that helped:
- Keep hot state (rate limits, who is online, short caches) in memory and accept that it resets on restart.
- Version static scripts by file time in the page, so a CDN can cache them for hours and a deploy is still picked up at once.
- Write the boring housekeeping early: cap container logs, prune the build cache, keep the last 14 database backups. My disk grew by 9 GB in a day of deploys before I noticed.
Connecting a real exchange without holding anything
Members can connect their own Bybit Demo account and send orders from the chart. The server stores the API key encrypted and only passes instructions on; it refuses any key that is able to withdraw or transfer funds, and refuses read-only keys too, so a connected key can trade and nothing else. Positions opened from the chart are journaled with the exchange's own result once they close.
Security that fits a small project
- Sessions are random tokens stored only as hashes; passwords use Argon2.
- Two-step sign-in is plain TOTP from the standard library (hmac, struct, base64), with one-time backup codes stored as hashes.
- The administrator console only opens in a session that gave an authenticator code in the last twelve hours, so a stolen password alone is not enough.
- A tiny guard scores suspicious requests (probes for .env files, injection strings, bursts of 404s) and shuts an address out for an hour.
What I would do differently
I would write the "walk every endpoint as every kind of user" test on day one. Late in the project it found an admin page that had been quietly returning errors for days because a helper function had gained a parameter.
Try it
Charts for crypto and stocks, about 70 drawing tools, bar replay, a small script editor with backtests, a markets page with funding and open interest, and the journal: keelstone.online. Every account currently gets the top plan while the beta lasts.
It is software for studying markets and practising, not investment advice. I would love to hear what breaks.
