From Bhavcopy to Buy Signal: How We Built Equities4Profit — Stock Analysis SaaS for Indian Retail Investors
HOME - OUR BLOGS
India has tens of millions of retail equity investors who want to trade intelligently but lack the time or knowledge for traditional technical analysis. Here's how TTPL built Equities4Profit — a Laravel + Vue.js SaaS that converts NSE Bhavcopy data into color-coded analysis engine — and the architectural decisions behind every layer of the stack.
Bhavesh Thummar
Posted on 8th April 2026

The Problem: India's Retail Investor Is Underserved
India has one of the fastest-growing retail equity investor bases in the world. The number of demat accounts crossed 100 million in 2023, with tens of millions of new investors entering the market each year. Yet the tools available to these investors fall into two deeply unsatisfactory categories: complex charting platforms that require years of technical analysis knowledge to use effectively, and advisory services that demand blind trust in a third-party recommendation without any transparency into the underlying logic.
The retail investor caught between these two extremes — typically an employed professional with 20–30 minutes of time per day for investment decisions, moderate capital at stake, and a strong aversion to losses — has no purpose-built tool. They need something that is simultaneously fast (5–10 minutes for a full watchlist), honest about risk (not just optimistic about gains), and simple enough to use without a finance degree.
That gap is what Equities4Profit was built to close. And building it taught us a great deal about what makes financial software genuinely useful — as opposed to technically impressive but practically overwhelming.
The Core Insight: A Stock Is Its Own Benchmark
Most technical analysis tools compare stocks to external benchmarks: the Nifty 50, sector peers, moving averages, or arbitrary support/resistance levels drawn by analysts on charts. The problem with this approach for the retail investor is that it requires context the investor doesn't have — an intuitive understanding of what it means for a stock to be "overbought" relative to its RSI, or why a price has historically bounced off a particular support level.
Equities4Profit's insight is different: use the stock's own recent history as its benchmark. If a stock's current low is sitting at or near its N-day lowest low, that tells you something concrete and actionable — without requiring any comparison to indices, peers, or analyst targets. The same logic applies to highs, closing prices, and trading volume. Each metric, compared to its own recent range, produces a meaningful signal.
The key product decision was to expose these signals not as raw percentages but as color-coded bands with plain-language risk and opportunity descriptions — so that the investor's daily task becomes reading a color and a label, not calculating a number.
The Architecture: Two SPAs, One Laravel Backend
Equities4Profit has two distinct audiences: prospective users who need to understand the product and register, and active subscribers who need to use it daily. These two experiences have nothing in common from a UI perspective — and forcing them into a single Vue.js app would have created an authentication-state mess and degraded both experiences.
The solution was two independent Vue.js 2 + TypeScript applications compiled into separate bundles by Webpack Mix:
- public.ts — the marketing SPA with Home, Why Use, How It Works, Plans, FAQ, Contact, Login, Register, and Password Reset views. No authentication dependency. Fast, content-focused, and crawlable for SEO.
- private.ts — the subscriber app with Dashboard (Single Stock View), Portfolio Tracker, Legends, Plans, Profile, and Admin views. Fully authenticated, plan-gated, and optimized for daily analytical use.
Laravel serves both SPAs from a single catch-all route, with separate Blade layout files (public.blade.php and private.blade.php) loading the appropriate bundle based on authentication state. Vue Router handles all in-app navigation from that point. This architecture means the public site is always accessible — no authentication redirects, no session state leaks between the two portals.
The API layer uses role-based middleware (check.role:admin|super_admin) for admin routes and a custom check.plan middleware for tracker routes that validates the user's active subscription before allowing access. Plan gating is enforced at both the API layer and the Vue Router level — subscribers without an active plan see the upgrade view immediately rather than hitting an API error.
Building the NSE Bhavcopy Pipeline
NSE publishes daily Bhavcopy CSV files containing end-of-day OHLCV data for every listed equity. These files are the data foundation for every tracker calculation in Equities4Profit — without a reliable, up-to-date OHLCV database, the entire analysis layer is meaningless.
The pipeline has three stages:
Stage 1: Download & Extract
The FileController exposes dedicated routes for downloading and extracting Bhavcopy files, both individually (by date) and in bulk. NSE distributes Bhavcopy files as ZIP archives, so the extract step runs before parsing. The File model tracks each downloaded file's state, enabling the system to retry failed downloads and skip files that have already been processed successfully.
Stage 2: Parse & Normalize
BhavcopyService parses the CSV rows and normalizes the columns to a standard schema (symbol, series, open, high, low, close, last, prev_close, total_trade_quantity, total_trade_value, total_trades, isin, timestamp, date). NSE has changed its CSV column layout at least twice since the platform launched, so the parser has to map both legacy column names (TOTTRDQTY, TOTTRDVAL) and newer equivalents to the same internal schema. The TIMESTAMP field is converted to a Unix timestamp for efficient range-based indexing.
Stage 3: Upsert
Each parsed record is upserted using Eloquent's updateOrCreate keyed on symbol + series + timestamp. This ensures that re-processing any Bhavcopy file — whether due to a retry after a failure or a deliberate re-import — produces no duplicate rows. The SymbolService runs a parallel upsert for the master symbol list, keeping the autocomplete search index current with every import.
The bulk processing routes are designed to handle multi-year historical backfills: they iterate through all available Bhavcopy files in date order, processing each one sequentially through Laravel Jobs. This keeps the web server responsive while the backfill runs in the background.
The Five Tracker Algorithms
TrackerService is the analytical core of Equities4Profit. It exposes a single public method — getTrackerInfoForSymbol($trackerName, $symbol, $interval, $info) — that dispatches to one of five private calculation methods based on the tracker type. Every calculation uses the same Bhavcopy table, querying it with offset and limit logic to select the correct time window.
The offset/limit approach is important: rather than filtering by a date range (which would require knowing the exact trading calendar), the system uses orderBy('timestamp', 'desc')->limit($from)->offset($to) to select the most recent N trading days. This makes the calculation robust to market holidays and weekends — it always operates on actual trading days, not calendar days.
Each tracker returns an object with: name, prev_value (the historical reference), current_value (today's reading), difference, percentage_change (rounded to 0 decimal places for display), and a state boolean (false if data is unavailable). The consistent response shape means the Portfolio view and Single Stock View can render any tracker type with the same component logic.
Two configurable date ranges — short and long — are available for every tracker. The RangeService serves these as labeled dropdown options (e.g., "5 Days", "20 Days", "52 Weeks") from the database, allowing the operator to add or modify range options without a code change.
The Legend System: Making Math Human-Readable
The tracker algorithms produce percentages. The Legend system is what transforms those percentages into investment decisions an ordinary investor can make.
For each tracker type, the Legend table defines a set of bands. Each band specifies: a percentage range (band_start, band_end), whether the reading is above or below zero (is_above_zero), a color, a direction icon, and tracker-type-specific fields. For Low and Close trackers, the bands include risk_type, returns, term, possible_upswing_chance, and possible_getting_stuck. For the Volume tracker, bands include volume_type, risk_type, and remarks. For the High tracker, bands indicate opportunity level.
In practice, an investor using the Low tracker might see a green indicator labeled "Conservative Risk / Good Returns / Short Term / High Upswing Chance / Low Getting Stuck Possibility" — without knowing that this corresponds to the stock's low being within a specific percentage of its 20-day floor. The interpretation does all the work.
This design philosophy — encode expert judgment once into the system, then surface it as a color and a label — is what makes Equities4Profit genuinely usable for the audience it serves. The investor does not need to understand the threshold; they need to understand what the threshold means for their money.
The Subscription Engine: PayPal + Fault-Tolerant Plan Assignment
Building a reliable subscription layer on top of PayPal's Checkout SDK is straightforward in the happy path: create order → user approves → server captures → plan assigned. The engineering challenge is every deviation from the happy path.
The most common failure mode: the user completes payment in the PayPal popup, then closes the browser before the popup redirects back to the application's capture endpoint. PayPal has captured the payment; the application has no idea. The user refreshes, sees no active plan, and emails support.
Our solution was a full Order state machine with a background reconciliation cron:
- CREATED: Order created on PayPal, reference_id stored locally.
- APPROVED: User approved payment in PayPal popup.
- COMPLETED: Server successfully captured payment.
- PLAN_ASSIGN_COMPLETED: Plan assigned and active in the database.
CronService::orderSync queries all orders not yet in PLAN_ASSIGN_COMPLETED state, re-queries PayPal's OrdersGetRequest for each, and assigns the plan if the payment status is COMPLETED. This cron runs independently of any user action — it catches missed callbacks automatically, typically within minutes of the payment.
PlanService::mapPlan handles the plan stacking case: if a user has an existing active paid plan, the new plan's start date is set to the expiry of the last active plan rather than today. This means a subscriber who renews six weeks before their plan expires does not lose those six weeks — they stack cleanly.
What We Would Build Differently Today
Equities4Profit was built in 2020–2021. If we were building it today, we would make a few different architectural choices:
Pre-computed Tracker Values
Today, tracker values are computed on demand from the Bhavcopy table. For a 20-symbol watchlist with two date ranges, that's 40 aggregation queries per request. With a growing historical dataset, this becomes a bottleneck. A better architecture would pre-compute tracker values for all symbols after each daily Bhavcopy import, store the results in a denormalized read table, and serve the watchlist from there — reducing the per-request query count from 40 to 20 simple lookups.
Vue 3 + Composition API
Vue 2's Options API works fine but becomes verbose in complex components. Vue 3's Composition API would make the tracker display logic and data-fetching state more readable and testable. Migrating would also unlock Pinia as a cleaner alternative to Vuex for the store layer.
Push-Based Data Refresh
The current portfolio view requires a manual refresh to pick up new Bhavcopy data. A Laravel Broadcasting + Echo setup would allow the system to push a "data updated" event to all connected subscribers after each daily Bhavcopy import completes, triggering an automatic portfolio refresh without user action.
Conclusion: Software for the Investor, Not the Analyst
The most important lesson from building Equities4Profit is that financial software for retail investors needs to be designed from the investor's psychology outward, not from the data model inward. The Bhavcopy pipeline, the tracker algorithms, and the database schema are all invisible to the user. What they see is a color and a two-word risk label. Everything else — the ingestion pipeline, the aggregation queries, the PayPal state machine — exists only to make that color accurate, fast, and reliably available every morning.
When technical decisions serve that clarity directly — offset-based queries that respect trading calendars, pre-computed ranges that eliminate on-demand database load, a fault-tolerant subscription layer that never loses a paying user's access — they are the right decisions. When they add complexity that the user cannot feel, they are worth reconsidering.
Equities4Profit is a reminder that the best financial software is not the most feature-rich — it is the most trustworthy. And trustworthiness in this domain means accuracy, speed, and risk-first honesty. Those are the three things we built toward, and the three things we would build toward again.






