A full-stack virtual stock trading platform with a transaction-safe trading engine, Redis-cached live market data, Celery background workers, and REST APIs. Built for scale — the architecture supports thousands of concurrent users in simulated trading competitions.
Browser (HTML/Bootstrap/Chart.js)
│
Fetch / REST APIs
│
┌─────────────────────┐
│ Django REST Framework │
└──────────┬──────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
Auth Service Trading Engine Portfolio Engine
(JWT/Session) (ACID trades) (Real-time value)
│ │ │
└──────────────────┼───────────────────┘
│
┌──────────┴──────────┐
│ Analytics Engine │
│ (returns, sectors, │
│ leaderboard) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ Stock Price Service │
│ (Redis cache layer) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ PostgreSQL DB │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ Redis + Celery │
│ (cache + workers) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ Yahoo Finance API │
│ (live market data) │
└─────────────────────┘
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
│ users │ │ stocks │ │ holdings │
├─────────────┤ ├──────────────┤ ├──────────────────┤
│ id (PK) │◄──┐│ id (PK) │◄──┐│ id (PK) │
│ username │ ││ symbol (UQ) │ ││ user_id (FK) │
│ email │ ││ company │ ││ stock_id (FK) │
│ cash_balance│ ││ sector │ ││ quantity │
└─────────────┘ ││ exchange │ ││ avg_buy_price │
│└──────────────┘ │└──────────────────┘
┌─────────────────┐ │
│ transactions │ │ ┌──────────────────┐
├─────────────────┤ │ │ portfolio_snapshots│
│ id (PK) │ │ ├──────────────────┤
│ user_id (FK) │◄─────────────────┘ │ id (PK) │
│ stock_id (FK) │◄──────────────────────┤ user_id (FK) │
│ type (BUY/SELL) │ │ date │
│ price │ │ portfolio_value │
│ quantity │ │ cash_balance │
│ timestamp (idx) │ │ holdings_value │
└─────────────────┘ └──────────────────┘
TradeSim/
├── accounts/ # User model, registration, JWT auth
├── stocks/ # Stock model, search, price endpoints
├── portfolio/ # Holdings, portfolio snapshots
├── transactions/ # Buy/sell execution, trade history
├── watchlist/ # User watchlist management
├── leaderboard/ # Rankings by portfolio value
├── news/ # Market news from Yahoo Finance
├── alerts/ # Price threshold alerts
├── services/ # Business logic layer
│ ├── trading.py # ACID trade execution
│ ├── portfolio.py # Portfolio valuation & allocation
│ ├── stock.py # Price fetching with Redis cache
│ ├── analytics.py # Dashboard & leaderboard
│ └── tasks.py # Celery background workers
├── api/ # URL routing for all endpoints
├── core/ # Django settings, Celery config, WSGI
├── docker/ # Nginx config
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register/ |
Create account |
| POST | /api/auth/login/ |
Get JWT tokens |
| POST | /api/auth/refresh/ |
Refresh access token |
| GET | /api/auth/profile/ |
Get user profile |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/stocks/ |
List known stocks |
| GET | /api/stocks/search/?q=APP |
Search by symbol/name |
| GET | /api/stocks/{symbol}/price/ |
Get current price |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/transactions/buy/ |
Buy stocks (ACID) |
| POST | /api/transactions/sell/ |
Sell stocks (ACID) |
| GET | /api/transactions/ |
Trade history |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/portfolio/ |
Holdings + performance + allocation |
| GET | /api/portfolio/history/?days=30 |
Daily portfolio snapshots |
| GET | /api/dashboard/ |
Aggregated dashboard data |
| Method | Endpoint | Description |
|---|---|---|
| GET/POST/DELETE | /api/watchlist/ |
Manage watchlist |
| GET | /api/leaderboard/ |
Top 100 users |
| GET | /api/news/ |
Latest market news |
| GET/POST | /api/alerts/ |
Price alerts |
Buy/sell operations use transaction.atomic() with select_for_update() on holdings to prevent race conditions. A buy never partially succeeds.
with db_transaction.atomic():
self.user.cash_balance -= total_cost
self.user.save(update_fields=["cash_balance"])
holding = Holding.objects.select_for_update().get_or_create(...)
holding.quantity += quantity
holding.avg_buy_price = recalculated_average
holding.save()
Transaction.objects.create(...)Without caching, 1000 users checking their portfolio would trigger 1000 Yahoo Finance API calls. With Redis:
- Stock prices cached for 60 seconds (configurable via
STOCK_PRICE_CACHE_TTL) - Batch fetching: uncached symbols fetched once, served to all users
- Search results cached for 1 hour
- Rate-limited Yahoo Finance access (5 calls per 60 seconds)
Business logic lives in services/ — not in views. This means:
- Views are thin: parse request → call service → return response
- Services are testable independently
- The same service can be reused in views, Celery tasks, or management commands
sync_stock_prices(every 60s): Pre-warms Redis cache for all known stockstake_portfolio_snapshots(midnight): Records daily portfolio value for historical chartscheck_price_alerts(every 60s): Triggers user-defined price alerts
Strategic indexes on user_id + timestamp (for trade history queries), symbol (for stock lookups), and user_id + stock_id (for portfolio queries) ensure sub-millisecond lookups even with millions of rows.
# Clone and enter
git clone <repo> && cd TradeSim
# Copy env and edit secrets
cp .env.example .env
# Start everything
docker compose up --build
# Run migrations
docker compose exec web python manage.py migrate
# Create a test user
docker compose exec web python manage.py shell -c "
from accounts.models import User
User.objects.create_user('demo', password='demo1234')
"
# Open browser
open http://localhost:8000/api/docs/# Production build
DJANGO_DEBUG=False docker compose -f docker-compose.yml up -d
# Scale workers
docker compose up -d --scale celery_worker=4- WebSocket real-time price updates
- Real-time leaderboard
- Paper trading competitions with configurable start balances
- Social features (follow traders, copy trades)
- Advanced charting (moving averages, RSI, MACD)
- CSV import/export for trade history
- Rate limiting per user on API endpoints