tejas.k

Case study

₹ Rupee Engine

A modular equity-analysis and portfolio engine for Indian markets: CLI, API and dashboard.

Status
Active, since Feb 2026
Coverage
2,600+ NSE stocks per full run
Algorithms
13 pluggable, registered
Interfaces
CLI · REST API · React dashboard
  • -91%Memory for big backtestsFrom about 50 GB to about 4 GB, with the same results.
  • 50-70%Faster on the slow partsA run that used to hang now finishes in about 11 minutes.
  • 2,600+Stocks scored in one runThe whole market, end to end, with no failed lookups.

The problem

Judging a stock means combining fundamentals, price behaviour and news sentiment, and doing it consistently across a thousand names. Done by hand it is slow and impossible to audit.

I wanted a system where every recommendation is the output of explicit, testable scoring logic, and where the logic itself can be measured against history instead of trusted on instinct.

Architecture

Rupee Engine data flowData flows from public sources through a rate-limited async ingestion layer and a TTL cache into PostgreSQL. Scoring and the optimizer read from it, and backtesting replays history from the same store. snapshots Public data sources exchange + fundamentals + news Async ingestion httpx · asyncio · rate limits TTL cache incremental refresh PostgreSQL schema owned by migrations Scoring engine registry of 13 algorithms Portfolio optimizer costs · risk/return CLI · REST API · dashboard token-protected API Backtest + calibration replays history
Data flows from public sources through a rate-limited async ingestion layer and a TTL cache into PostgreSQL. Scoring and the optimizer read from it, and backtesting replays history from the same store.
  1. Public data sources: exchange + fundamentals + news
  2. Async ingestion: httpx · asyncio · rate limits
  3. TTL cache: incremental refresh
  4. PostgreSQL: schema owned by migrations
  5. Scoring engine: registry of 13 algorithms
  6. Portfolio optimizer: costs · risk/return
  7. CLI · REST API · dashboard: token-protected API
  8. Backtest + calibration: replays history

Explore the stages

Decisions that mattered

Faster and lighter, same answers

Situation
Big runs were slow, one hung, and a large backtest ran out of memory on a 64 GB machine.
What I decided
Find the real cause with profilers instead of guessing. Accept a change only if the results stay identical. Make a missed shortcut fail loudly, so a speed-up cannot quietly rot.
Result
Up to 91% less memory, 50-70% faster, and the hang is gone. Nothing about the output changed.

Fail loudly, never guess

Situation
When data is missing, the easy path is to score what is left and carry on.
What I decided
Stop with a clear error instead. If most data is missing, or the database is not ready, the run or the API refuses to start.
Result
No quietly wrong portfolio. A failure is visible the moment it happens.

Be kind to the data sources

Situation
The sources limit how fast I can ask, and a naive run repeated almost all of its work.
What I decided
Remember what is already fetched, skip what is stored, and refresh only what is due.
Result
A repeat pass over the news sources dropped from about a minute to a few seconds.

What I optimise for

  • Measure before and after, every time.
  • Fail loudly instead of guessing.
  • Same inputs give the same answers: changes to the scoring are fingerprinted and frozen outputs are reviewed.
  • Docs are checked against the code, so they cannot drift.

Incidents get a written postmortem and a runbook, not just a fix.

Known gaps and what is next

  • It runs from one machine, started by hand.

    Next Scheduled runs with an alert when one fails.

  • A data source can change its page and go quiet.

    Next A health check per source that says so.

  • It ranks and sizes a portfolio. It does not trade, and it is not advice.

    Next Stay a research tool, on purpose.

← All work Let's talk →

↑↓ navigate · ↵ run · esc close

tejas@portfolio ~

The 30-second version

Tejas Kothari

Message on LinkedInSee the workLinkedIn