Skip to content
Ali Akbari
Menu

Case study

ChartX

A chart-reading game platform I own and built the backend for alone: 13 Go services that generate timed questions from real historical prices, score players and run competitions. It reached a full development environment but did not launch.
Own productImplementedenvironment:Lab× 2 claims
Role
Owner; sole backend developer
Period
Jan 2025 – May 2026
Updated
11 October 2026
  • Go
  • Gin
  • PostgreSQL
  • TimescaleDB
  • PgBouncer
  • Redis
  • Kafka
  • Docker Swarm
  • GitHub Actions
  • Loki
  • Grafana
  • Nuxt
Jump to a section
  1. Overview
  2. Try the question generator
  3. Architecture
  4. Question generator
  5. Status
  6. Limitations
  7. Evidence index

Overview

ChartX is a game for learning to read charts. A player sees the last hundred one-minute candles of a real instrument at a moment in the past, plus two price limits, one above and one below the current price. The question is which limit the price touches first. There is a solo exam mode and a head-to-head mode, an in-game economy, progression, competitions, support and identity checks.

I own the product and wrote the whole backend; every commit in the backend repository is mine. The web frontend began as another engineer's Vue app, which I later rewrote in Nuxt. Much of the code was written with AI coding agents working from my designs and reviewed by me.

Try the question generator

Playable preview

Play questions made by the ChartX generator

Real output of the actual code, recorded once and replayed here.

EURUSD · 2019-02-04 03:20 UTC · question 1 of 12. Candlestick chart of the last 100 one-minute candles, with an up limit at 1.14522 and a down limit at 1.14507.

Score 0/0

1.145221.14507hidden100 × 1-minute candles

Which limit does the price touch first?

ChartX generator-service, commit e7fe646 (private repository); QuestionService.buildBestCandidateFromSegment, unchanged, with code-default settings and no database (static win-probability fallback). Data: Dukascopy BID 1-minute candles (EURUSD Feb 2019, BTCUSD Feb 2023), from the dukascopy-node test fixtures vendored in the repository. Generated 2026-10-11. Generated offline; this page reimplements only the chart and the answer reveal. Limits include random noise by design, so each run of the generator produces different questions. Market data © Dukascopy Bank SA.

The questions above were produced by running ChartX's generator code on real market data, outside the platform, and saving the results. The page replays them; it does not call the generator live.

Architecture

Thirteen Go services (about 159,000 lines, with about 220 test files), each built and deployed on its own:

AreaServices
GamesSolo exams, head-to-head games, competitions
Questions and dataQuestion generator, market-data service (TimescaleDB)
Players and moneyAccounts and authentication, in-game economy, progression, risk
OperationsAdmin, notifications, support

A Python service handles identity-document checks. Services that change shared state write their events to an outbox table in the same transaction and a relay publishes them to Kafka, so a crash can never leave a database change without its event. PgBouncer sits in front of PostgreSQL, several services use Redis, and candle history lives in TimescaleDB.

Each service has its own GitHub Actions workflow that builds it and deploys it to Docker Swarm from a self-hosted runner. Logs go to Loki and are read in Grafana.

Question generator

  1. Pick a window. A random moment with 100 visible candles and up to 100 hidden ones after it. Windows with gaps, impossible prices or no volume are rejected.
  2. Set the limits. The distance is a multiple of ATR (average true range, a volatility measure) chosen from the target difficulty and the current volatility. Limits are pulled inside the nearest swing high or low, then nudged by a small random amount so players cannot learn exact distances.
  3. Score difficulty. Six weighted factors: volatility, trend strength, distance, expected time to a hit, closeness to support or resistance, and volume.
  4. Check against what happened. The hidden candles decide the answer. If neither limit is hit, or both are hit in the same candle, or the hit comes too soon or too late, the limits are adjusted and checked again.
  5. Keep the best candidate. Up to 12 candidates per window are scored for closeness to the target difficulty, a low estimated win rate and a hit near the middle of the hidden window.

Status

The platform ran end to end in a self-hosted development environment and never launched. That environment is offline now. Development ran from January 2025 to May 2026.

Limitations

  • Not launched, so there are no usage figures, and nothing here has served real players.
  • The deployment workflows build and deploy but do not run the tests.
  • The preview uses the generator's default settings and its fixed fallback for win probability, because it ran without the production database.
  • The preview's market data is Dukascopy's (EURUSD, February 2019; BTCUSD, February 2023).

Evidence index

Every claim this case study relies on, rendered from the evidence manifests.

Implementedenvironment:LabSelf-reported · private source

ChartX backend, 13 Go services

Wrote the whole ChartX backend alone: 13 Go services (about 159,000 lines) on PostgreSQL and TimescaleDB, Redis and Kafka, where services that change shared state publish through a transactional outbox. Each service deploys to Docker Swarm through its own GitHub Actions workflow on self-hosted runners, with Loki and Grafana for logs.

GoOwn product · no public artifactsEvidence
Implementedenvironment:LabSelf-reported · private source

Question generator for chart-reading exams

Designed the generator that picks a playable window of historical candles and sets an up and a down price limit from ATR volatility, nearby support and resistance, a little random noise and a target difficulty, then keeps the best of up to 12 candidates. The playable preview on this site replays its real output.

BackendOwn product · no public artifactsEvidence

← All work