A modular, plugin-based stock analysis terminal. Type a ticker, pick a position type (Intraday / Swing / Delivery / Long-term), and every enabled indicator runs against live market data — their weighted contributions roll up into one configurable recommendation signal.
Decision support only — not financial advice.
| Layer | Choice |
|---|---|
| Frontend | React 19 + TypeScript + TailwindCSS 4 + Zustand (Vite) |
| Charts | TradingView Lightweight Charts + custom SVG gauge |
| Backend | Fastify 5 + TypeScript |
| Database | SQLite via Prisma (swap the datasource for PostgreSQL in production) |
| Market data | Yahoo Finance (yahoo-finance2 v3) — US + NSE/BSE symbols, no API key |
You'll need Node 22 or newer. The whole thing runs locally on SQLite — no database server to install, no API keys to sign up for.
1. Install and create the database.
npm install
npm run db:push # creates server/prisma/stockit.db from the Prisma schema2. Set up your login.
This part matters, so don't skip it. Every API route is locked behind authentication, and the app deliberately won't invent an account for you — a default username and password baked into an open-source repo is a gift to anyone who port-scans your machine later. So you create the first account yourself, and it takes about thirty seconds:
cp server/.env.example server/.env
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"Open server/.env and fill in three lines — paste that random string as your
JWT_SECRET, then pick an email and password for yourself:
JWT_SECRET=<the string you just generated>
ADMIN_EMAIL=you@example.com
ADMIN_PASSWORD=<a real password, not "admin">Leave everything else in that file blank for now. It's all optional.
3. Start it.
npm run dev # API on :4000, web on :5173Open http://localhost:5173 and sign in with the email and password you just
chose. The account is created automatically on first boot, as an admin, and
only when the users table is empty — so it's safe to leave those two lines in
.env afterwards.
Deep links work too: /?symbol=RELIANCE.NS&tf=delivery.
Stuck at the login screen with no account? That means the server started before
ADMIN_EMAILandADMIN_PASSWORDwere set, so it had nothing to create an account from. Fill them in and restart — it'll pick them up. (Look for the[auth]warning in the server logs; it's telling you the same thing.)
Nothing here touches real money until you explicitly go looking for it.
Out of the box the auto-trading engine is off, and when you turn it on it runs against a simulated paper-trading broker with a fake balance. Connecting a real brokerage account takes real Upstox API credentials plus a separate confirmed switch in the UI that validates the connection first. You cannot get there by accident.
Everything in server/.env besides those three lines is optional:
| Variable | What it unlocks | Without it |
|---|---|---|
UPSTOX_API_KEY / UPSTOX_API_SECRET |
Live order placement through Upstox | Paper trading only |
ANTHROPIC_API_KEY |
The LLM copilot | Copilot is unavailable, everything else works |
Market data comes from Yahoo Finance and needs no key at all, so analysis, charts, indicators, strategies, discovery, and paper trading all work the moment you log in.
web/ (React SPA, /api proxied to :4000)
src/stores/ Zustand stores (analysis, indicators, ui)
src/components/ layout / search / dashboard / admin
src/pages/ Dashboard · Indicators (admin) · Settings
server/
src/indicators/
plugins/ ← one file per indicator (the extension point)
registry.ts auto-discovers every plugin file at startup
lib/math.ts shared SMA/EMA/RSI/ATR/stddev helpers
src/services/
marketData/ MarketDataProvider interface + Yahoo implementation
scoring.ts weighted scoring, threshold bands, risk & trend derivation
analysis.ts orchestrates quote + history + indicators → response
src/routes/ stocks · analysis · indicators · settings
prisma/ IndicatorConfig · SeenPlugin · Setting · AnalysisRecord
Each plugin returns { signal, strength (-1..+1), value, explanation }.
contribution = strength × configured weight × timeframeAffinity[selected timeframe]
final score = Σ contributions of enabled indicators
The score maps to the highest threshold band it clears (Strong Buy / Buy / Watch / Neutral / Avoid by default) — bands, labels, and colors are editable in Settings and stored in the database. Risk level is derived from recent volatility plus indicator disagreement; trend direction from the trend-category indicators.
The timeframe also selects the candle window: Intraday = 15-min bars × 10 days, Swing = daily × 6 months, Delivery = daily × 1 year, Long-term = weekly × 5 years.
Default weights and timeframe affinities follow the academic evidence on each indicator class (each plugin's description cites its sources):
| Weight | Indicators | Evidence |
|---|---|---|
| 18 / 14 | MA Crossover, Price vs MA | Strongest support of any rule class — Brock, Lakonishok & LeBaron (1992, J. Finance); Park & Irwin (2007) survey |
| 14 / 12 | MACD, RSI | Beat buy-and-hold on 60 yrs of FT30 — Chong & Ng (2008) |
| 12 | Rate of Change | 3–12-month momentum — Jegadeesh & Titman (1993); Moskowitz, Ooi & Pedersen (2012). Short horizons reverse, so intraday affinity is low |
| 12 | Supertrend | No direct studies; trend-following logic backed by time-series momentum literature |
| 10 | Support/Resistance | Trading-range-break rules in Brock et al. (1992); intraday S/R predictive content in Osler (2000) |
| 8 | Volume, VWAP | Volume is informative but only as confirmation — Blume, Easley & O'Hara (1994); VWAP is an execution benchmark, intraday only |
| 6 | Bollinger Bands | Consistently underperforms MA rules and buy-and-hold after costs — Leung & Chong (2003); Lento et al. (2007) |
These are defaults, not gospel — everything is tunable per instance in the admin panel.
Create one file — nothing else changes:
// server/src/indicators/plugins/my-indicator.ts
import type { IndicatorPlugin } from "../../types.js";
const plugin: IndicatorPlugin = {
id: "my-indicator",
name: "My Indicator",
description: "What it measures.",
category: "momentum", // momentum | trend | volatility | volume | level
defaultWeight: 10,
paramSchema: {
period: { label: "Period", type: "number", default: 14, min: 2, max: 100 },
},
timeframeAffinity: { intraday: 1, swing: 1, delivery: 1, longterm: 1 },
compute(candles, params, ctx) {
return { signal: "buy", strength: 0.5, value: "…", explanation: "…" };
},
};
export default plugin;On the next server start the registry discovers it, seeds a default config row,
and it appears in the admin panel and the scoring engine automatically. The
SeenPlugin table guarantees seeding happens once — deleting the indicator in
the admin panel won't resurrect it.
The admin panel can also add multiple instances of the same plugin with different parameters (e.g. RSI-14 and RSI-7), tune weights, toggle, reorder, and delete — all persisted.
| Endpoint | Purpose |
|---|---|
GET /api/stocks/search?q= |
autocomplete |
GET /api/stocks/:symbol/quote |
live quote |
GET /api/stocks/:symbol/profile |
company fundamentals (sector, P/E, margins, analyst targets…) |
POST /api/analysis {symbol, timeframe} |
full analysis |
GET /api/analysis/recent |
history log |
GET /api/indicators · POST · PATCH/:id · DELETE/:id · POST /reorder |
admin CRUD |
GET /api/indicators/plugins |
discovered plugin types |
GET/PUT /api/settings/thresholds |
signal bands |
Pushing to main runs GitHub Actions (.github/workflows/deploy.yml):
typecheck + tests, then an SSH deploy of the backend to EC2 and a frontend
sync to S3/CloudFront, followed by a smoke test. Full production runbook
(architecture, secrets, networking, monitoring, cost) lives in
deploy/README.md.
For a lighter single-box setup — nginx serves the built SPA directly instead
of S3/CloudFront (deploy/nginx/stockit-iponly.conf) — run
bash deploy/scripts/deploy.sh on the EC2 box. It pulls main, applies the
Prisma schema, runs server tests, builds both workspaces, and reloads PM2,
rolling back automatically if the post-deploy health check fails.
- Data feed: implement
MarketDataProvider(server/src/services/marketData/provider.ts) and export it fromanalysis.ts— search, quotes, and candles all flow through it. - Database: change the
datasourceinserver/prisma/schema.prismatopostgresqland set a connection string; the Prisma models are portable.