About this app
This project compares a personal BVB portfolio against the BET index distribution and suggests how to invest available cash to move the portfolio closer to the index — using buy-only allocations (no selling).
Everything runs as a static GitHub Pages app: no backend, no database. Data is loaded directly from the repository (CSV + JSON snapshots) and computed in the browser.
Pages
- Tracker (
index.html): portfolio import + comparison vs BET + buy-only suggestions. - Allocation History (
allocation.html): a time-series chart showing how BET weights shift daily. - Data Coverage (
data.html): calendar overview of available daily snapshots.
Technical overview
Stack
- Frontend: HTML + CSS + vanilla JavaScript (no framework).
- Charts: Chart.js (CDN) for Allocation History.
- Hosting: GitHub Pages (static site).
- Automation: GitHub Actions for daily scraping.
- Scraper: Python script (
scripts/scrape_bvb.py).
Data flow
- The app reads CSV/JSON directly from the repo (static snapshots).
- A GitHub Action runs daily, fetches fresh BET weights, and commits a new snapshot file.
- The UI always uses the latest committed snapshot (deterministic + reproducible).
- The Allocation History page lists daily CSVs via the GitHub Contents API and loads them from raw GitHub URLs.
It ensures the app works without a backend and can reproduce results for any past day by using the committed files. It also avoids rate limits and availability issues of querying live sources from the browser.
Index data (BET weights)
BET weights are stored as daily CSV snapshots under input/bvb_distribution/.
A separate process (GitHub Actions + Python) fetches the data once per day and commits:
input/bvb_distribution/bvb-companies-YYYY-MM-DD.csvinput/bvb_distribution/bvb-companies-latest.csv
CSV format example:
symbol,weight
TLV,19.92
SNP,18.59
SNG,11.59
H2O,11.39
...
The app normalizes weights to 100% for the selected universe (symbols present in the snapshot), then uses these normalized weights as the target.
Allocation History (time-series)
The Allocation History page visualizes how index weights change across the daily snapshots. By default it loads the last 3 months (or all available if fewer).
- Multi-select symbols, including Select All / Clear.
- Shows day-to-day delta per selected symbol (previous snapshot → latest snapshot in range).
- Uses distinct colors to keep series readable.
Data is loaded directly from the repository using GitHub’s Contents API (file list) + raw GitHub URLs (CSV content).
Portfolio input formats
1) Portfolio CSV upload
Upload a CSV with the following format:
symbol,value
SNP,126506.66
TLV,122179.18
SNG,77051.70
H2O,75628.80
BRD,45400.00
...
CASH_VALUE,12023.80
- symbol — BVB ticker (e.g. SNP, TLV).
- value — current position value (not percent).
- CASH_VALUE — optional available cash; excluded from weight calculations.
Currency is not enforced; calculations assume all values are in the same currency.
2) TradeVille copy / paste import
You can paste the TradeVille portfolio table directly. The parser is intentionally tolerant: it ignores headers and extra text, and extracts instrument rows it can safely parse.
Example (realistic TradeVille paste):
Instrumente BVB in RON
simbol Nume sold Pret piata evaluare Pondere
SNP OMV Petrom 128,368 0.9855 126,506.66 20.54%
TLV Banca Transilvania 4,027 30.34 122,179.18 19.84%
SNG S.n.g.n. Romgaz 7,783 9.90 77,051.70 12.51%
H2O Hidroelectrica 606 124.8 75,628.80 12.28%
...
RON RON 12,023.80
- Extracts symbol + evaluare for each instrument.
- Detects the RON row and maps it to CASH_VALUE.
- Ignores “Total …” summary rows and anything that cannot be parsed safely.
Tracker calculations
1) Portfolio weights
- Portfolio weight per symbol is computed from instrument values only.
CASH_VALUEis excluded from weights (cash is a budget, not an index constituent).
2) Index weights
- Index weights are loaded from the latest snapshot CSV.
- Weights are normalized to 100% for the available symbols in the snapshot.
3) Differences table
The main table shows both:
- Difference (%): portfolio weight − normalized index weight.
- Difference (value): the value delta needed to match the target weights (based on total invested value).
Buy-only suggestion algorithm (with fees)
When Enable New Investment is ON, the app proposes buy-only allocations that aim to reduce deviation from the index, while respecting real trading constraints.
Constraints
- Budget: total spend cannot exceed available cash.
- Whole shares: all orders are integer shares.
- Minimum per symbol: suggested total per symbol must be ≥ user-configured minimum (otherwise that symbol is skipped).
- Fees: each order includes an estimated fee = variable commission + fixed component (tier-based).
- No rounding in UI for prices/shares in calculations (decimals are preserved).
High-level approach
- Compute target value per symbol from normalized index weights and current portfolio total.
- Compute “underweight” symbols (those below target).
- Allocate budget iteratively:
- Start from most underweight.
- Propose a feasible order: whole shares at market price + estimated fee.
- Respect minimum-per-symbol and remaining cash.
- After initial pass, spend remaining cash to further reduce deviation (even if improvement is small), while still respecting min-per-symbol and fees.
- Show results as:
- “Investment Suggestions” cards (per symbol: shares × price + fee).
- “Portfolio after applying suggestions” table to validate the new weights/differences.
Disclaimer
This tool is for personal tracking and convenience only. It does not provide financial advice. Always verify orders, prices, and fees in your broker before trading.