A tiny, local tool for disciplined monthly investing on the Pakistan Stock Exchange. Enter how much you want to invest, and it pulls live PSX prices and tells you exactly how many whole shares of each holding to buy to hit your target weights.
Built for a Shariah-compliant / KMI30 portfolio, but the holdings and weights are fully editable, so you can point it at any set of PSX symbols.
- Live prices from the PSX data portal (intraday, with end-of-day + cached fallback).
- Buy plan: enter an amount, get shares-to-buy, cost, leftover cash, and actual vs. target weight per holding.
- Editable portfolio: change tickers and weights in the browser; saved to
data/portfolio.json. - Purchase history & positions: record what you actually bought (shares + fill price, prefilled from the plan), then see accumulated holdings, average cost, market value, and unrealized P/L at live prices.
- Leftover optimizer (optional): spends residual cash on the most-underweight names.
- Resilient: if a price can't be fetched, it falls back to the last good value and
flags it as
staleinstead of breaking. - No Node build: one self-contained HTML page served by a Python backend.
git clone https://github.com/humzakt/psx-investor.git
cd psx-investor
bash run.shThen open http://localhost:8787.
run.sh creates a virtualenv, installs dependencies, and starts the server. After the
first run you can also start it with:
source .venv/bin/activate
uvicorn app.main:app --port 8787browser -> GET /api/plan?amount=50000
|
FastAPI (app/main.py)
|-> load_portfolio() data/portfolio.json
|-> psx.get_prices() dps.psx.com.pk (async, cached, fallback)
|-> build_plan() floor(amount * weight / price)
<- JSON: rows + totals
The PSX endpoints send no CORS headers, so the Python backend acts as the proxy. See
docs/DECISIONS.md for the architecture rationale and
AGENTS.md for a contributor/agent guide.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/plan?amount=&optimize=&refresh= |
Full buy plan |
| GET | /api/prices?refresh= |
Latest prices for portfolio tickers |
| GET | /api/portfolio |
Current holdings + weights |
| PUT | /api/portfolio |
Save edited holdings + weights |
| GET | /api/transactions |
Purchase history (newest first) |
| POST | /api/transactions |
Record a batch of purchases |
| DELETE | /api/transactions/{id} |
Delete one purchase |
| GET | /api/positions?refresh= |
Accumulated positions valued at live prices |
| GET | /api/health |
Health check |
Interactive docs at /docs.
The app ships with a generic example allocation in
data/portfolio.example.json. To use your own:
- In the app: click Edit weights, adjust tickers/weights, and Save.
- Or by file: copy the example to
data/portfolio.jsonand edit it:cp data/portfolio.example.json data/portfolio.json
Config resolution order (first found wins): private/portfolio.json -> data/portfolio.json
-> data/portfolio.example.json. Your data/portfolio.json is git-ignored, so your real
allocation is never committed. (The author keeps their personal config and thesis in a
separate private repo mounted at private/.)
Your purchase history (transactions.json) is treated the same way: it resolves
private/ -> data/ (both git-ignored, no example), so nothing you record is ever
committed to the public repo.
This is not financial advice. It is a personal, educational decision-support tool. Prices come from unofficial endpoints and may be delayed or wrong. It never places trades. Do your own research and consult a licensed advisor before investing.