Skip to content
@Research-SLIIT

Research-SLIIT

InvestWise

InvestWise

Explainable Machine Intelligence for the Colombo Stock Exchange

Final Year Research Project · CSE-25-26J-445 Sri Lanka Institute of Information Technology — Faculty of Computing · 2025/26


Services Models Containers Tests NestJS Next.js Python Kafka


📦 Repositories

⚙️ Backend · dev-deploy

An Nx monorepo of 13 applications: REST API gateway, four gRPC microservices, Kafka email consumer, PayHere billing, an SSE orchestration service, four Python ML inference services, and the YAML-driven annual-report ETL. Two Docker Compose profiles — 9 containers for core dev, 14 for the full platform. CI/CD to EC2.

NestJS 11 TypeScript 5.9 Nx 21.5 Python 3.11 FastAPI Flask gRPC Kafka 7.5 MongoDB 7 Redis PostgreSQL FAISS XGBoost LightGBM CatBoost SHAP spaCy Docker GitHub Actions

A Next.js 16 App Router analytics dashboard. Live market data via server-side CORS proxies, SSE-streamed AI pipelines, SHAP visualisations, KaTeX-documented feature engineering, printed model decision trees. 58 shadcn/ui primitives, 35 domain components, 5 Zustand stores, 40 Vitest test files. Full light/dark parity, mobile-first.

Next.js 16 React 19 TypeScript 5 Tailwind CSS 4.1 shadcn/ui Radix UI Zustand 5 Recharts Framer Motion KaTeX React Hook Form Zod Vitest

13

Nx applications

7

Trained models

80

REST endpoints

62

Test files

14

Docker containers

~240

Annual reports in ETL

The problem

The Colombo Stock Exchange has ~280 listed companies and almost no analytical tooling built for it. The data exists — filings, quarterly reports, financial news — but three properties break off-the-shelf models: Sri Lankan annual reports have no consistent layout, only a minority publish machine-extractable reports across a decade, and market-moving news mixes English with Sinhala and Tamil transliterations and local company nicknames.

Our position: a prediction an investor cannot interrogate is not usable. Every model here ships with its reasoning exposed — SHAP attributions, printed decision trees with real fusion weights, KaTeX feature derivations, retrieved historical precedents. Explainability is the deliverable, not a feature.


🎬 See It Working

Full walkthrough

Sign-in → live market dashboard → all four ML components exercised end to end

Risk analysis demo
🩺 Risk Analysis
6-stage AI pipeline over SSE → Z-Score verdict with SHAP drivers
Price prediction demo
📈 Price Forecast
T+5 open price from a 3-model ensemble
Dividend demo
💰 Dividend Risk
Cut probability with 14 features and their formulas
Sentiment demo
📰 News Impact
Two-stage cascade printing its own decision tree

Market dashboard Live market dashboard — ASPI · S&P SL20 · sector performance · gainers and losers · CSE news pulled live from cse.lk

Risk analysis
Projected next-quarter Altman Z-Score — Safe / Grey / Distress
SHAP attribution
SHAP attribution — which ratios drove the score, and by how much
Price prediction
T+5 ensemble forecast with per-day deltas and confidence
Dividend prediction
Dividend cut probability against a tuned 0.822 threshold
Sentiment decision tree
The model's actual decision tree — stage scores, thresholds, fusion weights
SSE pipeline stream
The 6-stage pipeline narrating itself over SSE, live in the browser
More screenshots — SHAP ratios · dividend features · AI narrative · Swagger · light mode · mobile
Financial ratios
AI-extracted financial ratios
Feature contributions
Per-feature dividend contributions
AI narrative
LLM narrative over the prediction
Dividend comparison
Multi-company dividend comparison
Light mode
Full light/dark parity
Mobile
Mobile-first responsive

🔬 Four Research Components

Four members, four independently trained and evaluated models, one shared platform.

Component Method Key result
🩺 Financial Distress SVR projecting next-quarter Altman Z-Score from 9 ratios, explained with a SHAP KernelExplainer. Ratios extracted by sending raw PDFs to Gemini's File API rather than parsing them. Safe / Grey / Distress with per-feature signed attribution
📈 Open Price Forecast Equal-weight ensemble of LightGBM + XGBoost + CatBoost over 28 engineered technical features, 60-day lookback, log-returns clamped at ±0.15 T+5 open-price path with SHAP feature importance
💰 Dividend Cut Elastic-net Logistic Regression on 14 features engineered from 8 balance-sheet fields; temporal split at 2021, minority class weighted 3× 82.1 % accuracy · AUC 0.697 · threshold 0.822 · n=140
📰 News Market-Impact Two-stage XGBoost cascade over 3072-d embeddings, fused with a financial lexicon, grounded by FAISS retrieval; a spaCy + RapidFuzz company gate runs before any paid inference Impact flag + direction + 4-step decision tree + historical precedents
📐 Model specifications
Risk Open Price Dividend Sentiment
Algorithm SVR LightGBM + XGBoost + CatBoost Logistic Regression XGBoost ×2 + lexicon
Preprocessing StandardScaler 28-feature engineering 14-feature engineering text-embedding-3-large (3072-d)
Target Next-quarter Z-Score T+5 open price P(dividend cut) Impact + direction
Key hyperparameters equal-weight ensemble C=0.05, l1_ratio=0.2, saga, class_weight {0:1, 1:3}, max_iter=20000 Stage 1 threshold 0.30; fusion 0.6/0.4 when lex_conf ≥ 0.60, else 0.7/0.3
Validation held-out test set temporal split, train cutoff 2021 held-out + lexicon cross-check
Explainability SHAP KernelExplainer, k-means background (k=10) SHAP feature importance KaTeX feature derivations + LLM narrative printed decision tree + FAISS precedents
Serving Flask · :5001 FastAPI · :5002 FastAPI · :8001 FastAPI · :8002

Why these choices: CSE liquidity is thin and volatility regime-dependent, so three price models disagree and average. Only ~19 companies publish extractable reports across 2012–2025, so the dividend model has 140 samples — at the default 0.5 threshold it cried wolf, hence 0.822. The lexicon is precise but brittle and the embedding model general but opaque, so the lexicon leads only when confident (≥ 0.60).


🏗 How It Fits Together

Four research tracks share one ingestion layer, one auth boundary, and one API contract — the separation that let them be developed in parallel.

graph TB
    subgraph FE["Frontend · Next.js 16 + React 19"]
        direction LR
        UI1["Market<br/>Dashboard"]
        UI2["Risk<br/>Analysis"]
        UI3["Price<br/>Prediction"]
        UI4["Dividend<br/>Prediction"]
        UI5["News<br/>Sentiment"]
    end

    GW["<b>API Gateway</b> · NestJS · :3400<br/>JWT · RBAC · Subscription tiers · OpenAPI 3"]

    subgraph MESH["gRPC Service Mesh · NestJS"]
        direction LR
        M1["Auth<br/>OTP · OAuth"]
        M2["User<br/>Profiles"]
        M3["Community<br/>Social"]
        M4["Logs<br/>Audit"]
    end

    subgraph ML["ML Inference · Python"]
        direction LR
        R1["<b>Risk</b><br/>SVR + SHAP"]
        R2["<b>Open Price</b><br/>3-model ensemble"]
        R3["<b>Dividend</b><br/>Elastic-net"]
        R4["<b>Sentiment</b><br/>2-stage + FAISS"]
    end

    subgraph ORCH["Orchestration"]
        direction LR
        O1["Financial Health<br/>6-stage SSE pipeline"]
        O2["Payment<br/>PayHere"]
    end

    subgraph ASYNC["Async"]
        K["Kafka"]
        E["Email<br/>consumer"]
    end

    subgraph DATA["Data"]
        direction LR
        D1[("MongoDB")]
        D2[("Redis")]
        D3[("FAISS<br/>244 MB")]
        D4[("PostgreSQL")]
    end

    subgraph ETL["Ingestion"]
        direction LR
        I1["Dividend ETL<br/>~240 annual reports<br/>YAML-pinned extraction"]
        I2["News Scraper<br/>ft.lk RSS<br/>SHA-256 dedup"]
        I3["cse.lk API<br/>live market data"]
    end

    FE -->|"REST + SSE"| GW
    GW --> MESH
    GW --> ML
    GW --> ORCH
    MESH -.-> K --> E
    O2 -.-> K
    O1 --> R1
    MESH --> D1
    ML --> D1
    R3 --> D2
    R4 --> D3
    R2 --> D4
    I1 --> D1
    I2 --> D3
    I3 --> FE
    I3 --> O1

    classDef fe fill:#7c3aed,stroke:#4c1d95,color:#fff
    classDef gw fill:#a855f7,stroke:#6b21a8,stroke-width:3px,color:#fff
    classDef mesh fill:#10b981,stroke:#065f46,color:#fff
    classDef ml fill:#0ea5e9,stroke:#075985,color:#fff
    classDef orch fill:#14b8a6,stroke:#115e59,color:#fff
    classDef async fill:#f59e0b,stroke:#92400e,color:#fff
    classDef data fill:#475569,stroke:#0f172a,color:#fff
    classDef etl fill:#ec4899,stroke:#9d174d,color:#fff

    class UI1,UI2,UI3,UI4,UI5 fe
    class GW gw
    class M1,M2,M3,M4 mesh
    class R1,R2,R3,R4 ml
    class O1,O2 orch
    class K,E async
    class D1,D2,D3,D4 data
    class I1,I2,I3 etl
Loading

Four transports, deliberately. gRPC for the internal NestJS mesh (protobuf contracts make refactors safe) · HTTP/JSON at the Python boundary (an ML service shouldn't need a gRPC toolchain to be testable with curl) · Kafka for email (sign-up must never fail because SMTP is down) · SSE for long-running analysis (a 40-second wait becomes visible progress, with no WebSocket infrastructure).


🚀 Run It Yourself

# Backend — full stack including all ML services
git clone https://github.com/Research-SLIIT/Final-Year-Research_CSE-25-26J-445-Backend.git
cd Final-Year-Research_CSE-25-26J-445-Backend && git checkout dev-deploy
cp .env.example .env                       # fill in your values
docker compose --profile ml up -d          # 14 containers

# Frontend
git clone https://github.com/Research-SLIIT/Final-Year-Research_CSE-25-26J-445-Frontend.git
cd Final-Year-Research_CSE-25-26J-445-Frontend && git checkout dev
pnpm install && pnpm dev                   # → http://localhost:3000

Web app http://localhost:3000 · Swagger UI http://localhost:3400/api/v1

Tip

Allocate ≈ 11–12 GB of RAM to Docker before the first --profile ml run — the ML profile loads XGBoost boosters, a 3-model ensemble and a 244 MB FAISS index simultaneously; below that the Python containers are OOM-killed at startup and report unhealthy with no obvious error.

The stack starts without any LLM API keys. The dividend and sentiment services fall back to clearly labelled demo modes (every substituted stage marked demo_mode, so a demo result can never be mistaken for a real one) and every non-LLM path works fully.

Full setup, environment reference and troubleshooting live in each repository's README.


👥 The Team

CSE-25-26J-445 · SLIIT Faculty of Computing · 2025/26

Research Component Backend Frontend
🩺 Financial Risk & Explainability Risk-prediction-ml · financial-health-service /risk · Z-Score gauge · SHAP breakdown
📈 Open Price Forecasting open-price-prediction-ml /predict · forecast + delta charts
💰 Dividend Cut Prediction Dividend-prediction-ml · Dividend-ETL /dividend · feature cards · KaTeX
📰 News Sentiment & Market Impact sentiment-backend /news · AI terminal · decision tree
🏗 Platform & Infrastructure api-gateway · auth · user · community · email · payment · logs AppShell · auth store · /admin · /pricing

Supervised by the SLIIT Faculty of Computing.


Released under the MIT License. Built for academic research — nothing here constitutes financial advice; the models are research artifacts trained on limited data, and their limitations are documented in each repository.


Backend Frontend

Sri Lanka Institute of Information Technology · Faculty of Computing · Final Year Research Project 2025/26
Colombo Stock Exchange · Explainable Machine Learning · Event-Driven Microservices

Pinned Loading

  1. Financial-Risk-Analyisis-Model Financial-Risk-Analyisis-Model Public

    A sleek and intuitive weather app built with JavaScript that provides real-time weather updates. This project utilizes modern web technologies to fetch and display current weather details for any l…

    Jupyter Notebook

  2. Final-Year-Research_CSE-25-26J-445-Frontend Final-Year-Research_CSE-25-26J-445-Frontend Public

    A sleek and intuitive weather app built with JavaScript that provides real-time weather updates. This project utilizes modern web technologies to fetch and display current weather details for any l…

    TypeScript

Repositories

Showing 4 of 4 repositories

Top languages

Loading…

Most used topics

Loading…