Stock Portfolio Analyzer

A Streamlit app that turns your buy and sell history into current holdings, allocation, profit, XIRR and a value-over-time chart. Market prices come from Yahoo Finance via yfinance.

1. Quick start

You need uv. It installs Python 3.12 and the app's dependencies for you.

git clone https://github.com/programmer11/stock-portfolio-analyzer.git
cd stock-portfolio-analyzer
uv sync                          # create .venv and install dependencies
uv run streamlit run app.py      # open http://localhost:8501

In the app, open Input Transactions and click Load sample data to try it with the bundled example.

2. CSV format

One row per trade. Column names are not case-sensitive.

ColumnMeaningAlso accepted as
tickerStock symbol, e.g. AAPL. Add the exchange suffix for non-US stocks, e.g. RELIANCE.NSsymbol, stock, scrip
dateTrade date, e.g. 2024-01-03trade date, transaction date
transaction_typeBUY or SELL (B/S also work)type, side, action
quantityNumber of shares, greater than 0qty, shares, units
pricePrice per sharerate, unit price
ticker,date,transaction_type,quantity,price
AAPL,2024-01-03,BUY,10,185.07
MSFT,2024-04-03,BUY,10,420.3
AAPL,2024-07-03,SELL,7,215.8

If any row is invalid, nothing is imported and the app lists the CSV line numbers to fix.

3. Using the app

Tab 1: Input Transactions

Transactions are saved to data/transactions.csv on your computer, so they're still there after a restart. That file is never committed to Git.

Tab 2: Current Portfolio

Tab 3: Historical Performance

4. How the numbers are calculated

Trades are replayed in date order using the average-cost method.

MetricFormula
Average cost basisTotal cost of shares held ÷ shares held. A sale removes cost at the average price.
Profit & Gains (unrealized)Shares held × current price − cost of shares held
Realized profit(Sale price − average cost) × shares sold, added up over every sale
Total Lifetime InvestmentSum of quantity × price over all BUYs
Total SalesSum of quantity × price over all SELLs
Lifetime ProceedsTotal Sales + Current Portfolio Value
Total ReturnLifetime Proceeds − Total Lifetime Investment (equal to realized + unrealized profit)
XIRRAnnualised rate that makes the net present value of all dated cash flows zero. BUYs are outflows; SELLs and today's portfolio value are inflows.
Net invested (chart)Running total of BUY cash − SELL cash

A sale larger than the shares you hold is reduced to the shares held.

5. Prices

6. Project structure

app.py               the three tabs (screen layout only)
portfolio/
  loader.py          read and check the CSV, save/load transactions
  holdings.py        replay trades -> positions, average cost, realized profit
  prices.py          yfinance calls, cached
  performance.py     lifetime metrics, XIRR, daily value series
tests/               tests for the math, no internet needed
docs/                this documentation (GitHub Pages)

See the architecture diagram for how data flows between these modules.

7. Testing

uv run pytest

The tests check the calculations against figures worked out by hand from sample_data.csv. They need no internet. For example, the sample's holdings come out as 10 AAPL at 182.39, 9 MSFT at 415.495 and 4 TSLA at 196.865.

8. Troubleshooting