Skip to content
pitonmert

Web Application · Data Modelling

portfolio-tracker

A personal portfolio application that keeps transactions as the source of truth and separates price data from portfolio calculations.

  • ASP.NET Core
  • React
  • TypeScript
  • EF Core
  • PostgreSQL
  • FastAPI
  • Testcontainers
Source code

Context and purpose

portfolio-tracker was built to bring assets from different sources and cash balances into one personal workspace. The application does not derive the portfolio from prices alone: it retains buy and sell activity, then calculates open and closed positions from that history.

Its purpose is not to provide financial advice. Instead, it makes the portfolio data model visible and reviewable by separating transaction history, price sourcing, calculation, and presentation.

How it works

When a user adds a buy or sell for an asset, the entry is first written to the transaction ledger. The API combines that history with the latest price to calculate quantity, cost basis, realised profit or loss, and unrealised profit or loss.

The dashboard reads open positions, closed positions, cash balance, and the portfolio summary from those results. Market prices are retrieved through a separate service when available; a manual price can be supplied when provider data is missing or unsuitable. Refreshing is supported by both a periodic background worker and a request-driven queue.

System design

The React and TypeScript client communicates with an ASP.NET Core API on the same origin. The API accesses PostgreSQL through EF Core and requests market data from a FastAPI-based market-data-service when needed. This keeps provider-specific concerns separate from the .NET API that owns financial calculations.

The core model has four parts:

  • Transactions are the immutable source record for portfolio activity.
  • Assets describe catalog-backed or user-created assets.
  • MarketPrices hold the latest provider or manual price.
  • PortfolioPositions are a rebuildable read model for fast interface queries.

Key technical decisions

Keeping the transaction ledger as the source of truth and the positions table as a read model is a key decision. If a calculation rule or market price changes, positions can be rebuilt from transactions rather than treating presentation totals as history.

Cost is calculated with the weighted average cost method. Sales update the remaining cost basis, realised results are tracked separately, and small residual quantities are handled with a closing tolerance. Calculation rules are not repeated in the UI; the API remains their single owner.

Market data lives behind a separate FastAPI service, isolating dependencies such as borsapy from the financial core. Provider symbols are normalised and availability is carried to the API as part of the price contract.

Validation and limits

The API has integration tests using real PostgreSQL Testcontainers, the React layer uses Vitest and Testing Library, and the Python service uses pytest. This validates financial calculations, HTTP contracts, and the market-data bridge independently.

The project is a personal educational tool, not a multi-user investment platform or financial advisory system. Price accuracy and availability depend on external providers. A missing price does not mean the application can make an investment decision on the user’s behalf.