A real-time paper trading competition platform for Indian (NSE) markets
Practice trading NSE stocks risk-free. Compete on seasonal leaderboards. Write custom analysis scripts. Let AI agents trade alongside you.
| Feature | Description |
|---|---|
| Paper Trading | Buy / sell NSE-listed stocks with ₹1,00,000 virtual capital per season |
| Live Market Data | Real-time OHLCV data via yfinance, streamed over WebSockets through Redis Pub/Sub |
| TradingView Charts | Full TradingView Advanced Chart widget embedded for professional-grade charting |
| PineScript Editor | Write and run PineScript-lite scripts — a custom scripting engine that supports SMA, EMA, RSI, MACD, Bollinger Bands, and more |
| Indicator Overlay | Script outputs are rendered on a lightweight-charts instance with multi-pane support |
| AI Agents | Background Gemini-powered AI agents that autonomously analyze and trade |
| Seasonal Competitions | Time-boxed seasons (default 30 days) with separate leaderboards |
| Trader Scoring | Multi-factor scoring: returns, risk management, consistency, discipline |
| Leaderboard | Ranked standings with live PnL tracking |
| Auth System | JWT-based registration and login with bcrypt password hashing |
┌──────────────────────────────────────────────────┐
│ Frontend (Vite + React) │
│ ┌──────────┐ ┌────────────┐ ┌────────────────┐ │
│ │Dashboard │ │Leaderboard │ │ Script Editor │ │
│ │ + Chart │ │ │ │ (Monaco+LWC) │ │
│ └────┬─────┘ └─────┬──────┘ └───────┬────────┘ │
│ │ │ │ │
│ └──────────────┼────────────────┘ │
│ │ REST + WebSocket │
└──────────────────────┼────────────────────────────┘
│
┌──────────────────────┼────────────────────────────┐
│ Backend (FastAPI + Uvicorn) │
│ ┌─────┐ ┌──────┐ ┌──────────┐ ┌──────────────┐ │
│ │Auth │ │Trade │ │ Market │ │ Scripting │ │
│ │ │ │Engine│ │ Data │ │ Engine │ │
│ └──┬──┘ └──┬───┘ └────┬────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ┌──┴───────┴──┐ ┌────┴────┐ ┌────┴────────┐ │
│ │ PostgreSQL │ │ Redis │ │ Gemini AI │ │
│ │ (SQLAlchemy│ │ (cache+ │ │ (agents) │ │
│ │ async) │ │ pubsub)│ │ │ │
│ └─────────────┘ └─────────┘ └─────────────┘ │
└──────────────────────────────────────────────────┘
NSE_ARENA/
├── backend/
│ ├── main.py # FastAPI app & lifespan events
│ ├── config.py # Environment / app configuration
│ ├── database.py # SQLAlchemy async engine & session
│ ├── db/
│ │ └── models.py # ORM models (User, Trade, Season, etc.)
│ ├── api/
│ │ ├── dependencies.py # Auth & DB dependency injection
│ │ └── routes/
│ │ ├── auth.py # Register / Login / JWT
│ │ ├── trades.py # Place & manage trades
│ │ ├── portfolio.py # Holdings & PnL
│ │ ├── leaderboard.py # Season rankings
│ │ ├── seasons.py # Season management
│ │ ├── scripts.py # PineScript execution API
│ │ ├── ai.py # AI agent endpoints
│ │ └── websocket.py # Live price WebSocket
│ ├── market_data/ # yfinance fetcher & Redis broadcaster
│ ├── services/
│ │ └── trading.py # Validated trade execution (used by both
│ │ # the human /trades route and the AI agent)
│ ├── engine/ # Standalone order-matching exercise (SortedDict
│ │ # order book, price-time priority) — NOT wired
│ │ # into the live trade path, see engine/matching.py
│ ├── scoring/ # Trader scoring algorithm
│ ├── scripting/ # PineScript-lite parser & indicators
│ ├── ai/ # Gemini-based AI trading agents
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── App.jsx
│ │ ├── tokens.css # Design tokens (dark theme)
│ │ ├── screens/
│ │ │ ├── Dashboard.jsx # Main trading dashboard
│ │ │ ├── AuthScreen.jsx # Login / Register
│ │ │ ├── Leaderboard.jsx # Season rankings
│ │ │ ├── AIFeed.jsx # AI agent activity feed
│ │ │ ├── ScriptEditor.jsx # PineScript editor screen
│ │ │ └── Profile.jsx # User profile & stats
│ │ ├── components/
│ │ │ ├── TradingViewChart.jsx # TradingView widget wrapper
│ │ │ ├── IndicatorChart.jsx # lightweight-charts for scripts
│ │ │ ├── MonacoEditor.jsx # Monaco code editor
│ │ │ ├── OrderPanel.jsx # Buy/Sell order form
│ │ │ ├── PositionsTable.jsx # Open positions table
│ │ │ ├── NavBar.jsx # Navigation bar
│ │ │ └── ...
│ │ └── hooks/
│ │ └── useAuth.js
│ ├── nginx.conf # Static-file + SPA-fallback config for the Docker image
│ ├── Dockerfile # Multi-stage build: vite build -> nginx
│ └── package.json
├── Caddyfile # Reverse proxy: routes API/WS paths to backend, rest to frontend
└── docker-compose.yml # Full-stack Docker setup: caddy, frontend, backend, postgres, redis
Two ways to run this: pieces run natively on your machine (steps 1-3, useful for active development — hot reload, debugger, etc.), or the whole stack in Docker with one command (step 4, closest to how it'd actually be deployed).
For running natively (steps 1-3):
- Node.js >= 18
- Python >= 3.11
- Redis (optional — app degrades gracefully without it)
- PostgreSQL (or use the default SQLite for local dev)
For the Docker path (step 4), only Docker + Docker Compose are required — Postgres, Redis, and both app servers run in containers.
git clone https://github.com/DhyeyTandel/NSE_ARENA.git
cd NSE_ARENAcd backend
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure environment — SECRET_KEY is required, the app refuses to boot
# without it. Everything else has a working default for local dev.
cp .env.example .env
# Edit .env: set SECRET_KEY at minimum, plus your Gemini API key, DB URL, etc.
# Run the server
uvicorn main:app --reload --port 8000The API will be available at http://localhost:8000.
Swagger docs at http://localhost:8000/docs.
cd frontend
# Install dependencies
npm install
# Start dev server
npm run devThe app will be available at http://localhost:5173.
# From the project root
cp .env.deploy.example .env.deploy
# Edit .env.deploy — POSTGRES_PASSWORD, REDIS_PASSWORD, and SECRET_KEY
# are required; docker-compose refuses to start any of those services
# without them.
docker-compose --env-file .env.deploy up --buildThis starts Caddy (reverse proxy, ports 80/443), the frontend (Vite build
served by nginx), the backend, PostgreSQL, and Redis together — the whole
app, not just the API. Open http://localhost (or your DOMAIN) and
Caddy routes you to the right place: API/WebSocket/docs paths
(/auth, /trades, /price, /ws/prices, /health, /docs, etc. — see
the Caddyfile) go to the backend, everything else goes to
the frontend. The frontend image is built once with no backend URL baked
in — it talks to the API via same-origin relative paths, so it works
unmodified behind localhost or any real DOMAIN.
Postgres, Redis, the backend, and the frontend do not publish ports to
the host — Caddy is the only entry point, reverse-proxying over the
internal compose network and handling HTTPS automatically once DOMAIN in
.env.deploy points at a real domain (it serves plain HTTP for local
testing when DOMAIN is unset or localhost).
.env.deploy variable |
Required | Description |
|---|---|---|
POSTGRES_PASSWORD |
Yes | Postgres password for the user account |
REDIS_PASSWORD |
Yes | Redis requirepass — also needed since other containers share the Docker network |
SECRET_KEY |
Yes | JWT signing secret |
GEMINI_API_KEY |
No | AI trading agent is disabled without it |
DOMAIN |
No | Domain for Caddy's automatic HTTPS; defaults to localhost |
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/register |
Create a new account |
POST |
/auth/login |
Get JWT access token |
GET |
/price/{ticker} |
Fetch OHLCV data for an NSE stock |
POST |
/trades/ |
Place a buy/sell order |
GET |
/portfolio/ |
Get current holdings and PnL |
GET |
/leaderboard/ |
Season leaderboard standings |
GET |
/seasons/active |
Current active season info |
POST |
/scripts/run |
Execute a PineScript-lite script |
GET |
/scripts/templates |
List built-in script templates |
GET |
/score/{user_id} |
Get trader score and grade |
GET |
/health |
Health check (incl. Redis status) |
WS |
/ws/prices |
Live price stream via WebSocket (see Auth Model for handshake auth) |
The scripting engine supports a subset of PineScript v5 syntax. Write scripts in the built-in Monaco editor, hit Run, and see indicator overlays rendered on the chart.
Supported indicators:
ta.sma(source, length)— Simple Moving Averageta.ema(source, length)— Exponential Moving Averageta.rsi(source, length)— Relative Strength Indexta.macd(source, fast, slow, signal)— MACDta.bb(source, length, mult)— Bollinger Bandsta.crossover(a, b)/ta.crossunder(a, b)— Cross signals
Example script:
//@version=5
indicator("Golden Cross", overlay=true)
fast = ta.sma(close, 50)
slow = ta.sma(close, 200)
plot(fast, "SMA 50", color=color.orange)
plot(slow, "SMA 200", color=color.blue)
buySignal = ta.crossover(fast, slow)
sellSignal = ta.crossunder(fast, slow)
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite 8, TradingView Widget, lightweight-charts, Monaco Editor |
| Backend | FastAPI, Uvicorn, SQLAlchemy 2.0 (async), Pydantic v2 |
| Database | PostgreSQL 15 (prod) / SQLite (local dev) |
| Cache and Pub/Sub | Redis 7 |
| Market Data | yfinance |
| AI | Google Gemini (generativeai SDK) |
| Auth | JWT (python-jose) + bcrypt (passlib) |
| Containerization | Docker + Docker Compose |
| Testing | pytest + pytest-asyncio (backend), Vitest + Testing Library (frontend) |
JWTs are delivered to browsers in an httpOnly, SameSite=Lax cookie
(Secure in production) set by /auth/login, /auth/login/json, and
/auth/register — never stored in localStorage, so an XSS payload
cannot read the token. POST /auth/logout clears the cookie.
- Cookie-authenticated mutating requests must send
X-Requested-With: XMLHttpRequest(CSRF guard — a cross-site form can make the browser send the cookie, but can't set a custom header). - API clients and tests can instead pass
Authorization: Bearer <token>(the login/register responses still returnaccess_tokenin the body); Bearer auth skips the CSRF header requirement since it can't be forged cross-site. - The
/ws/pricesWebSocket authenticates the handshake with the same cookie; non-browser clients may instead send{"token": "<jwt>"}as the first message frame within 5 seconds.
See backend/.env.example for the full list with comments.
| Variable | Default | Description |
|---|---|---|
ENV |
development |
production refuses to boot against a sqlite DATABASE_URL |
DATABASE_URL |
sqlite+aiosqlite:///./nse_arena.db |
Async DB connection string |
REDIS_URL |
redis://localhost:6379 |
Redis connection URL |
SECRET_KEY |
(none — required) | JWT signing secret; the app refuses to boot without it |
GEMINI_API_KEY |
(empty) | Google Gemini API key for AI agents |
docs/roadmap/ holds the security/production-readiness review and the prompt-by-prompt fix list that took this from prototype to hardened, plus the forward roadmap (CI, deploy, resting order book, backtester, live bots, and more).
This project is for educational and personal use.