Data Model — Portfolio App

All persistent entities, fields, types, relationships and storage layers  ·  Updated 2026-06-09

Storage Layers

File Server-side JSON

  • portfolio.json — main portfolio positions
  • arb_portfolios.json — arbitrage portfolios array
  • dcf_params.json — DCF parameters per ticker
  • clients.json — client records array

LocalStorage Browser per-origin

  • benchConfig_{portfolioId} — index/ETF overrides
  • pfNotes_{isin} — per-position notes

Memory Server in-process

  • _yf_cache — Yahoo Finance price cache (dict)
  • _crumb / _cookies — YF session state
  • DCF_MRP, DCF_TERM — global DCF defaults
  • DCF_RF_BY_CCY — risk-free rate by currency
  • ARBITRAGE_MODELS — 7 predefined strategies
  • SECTOR_CANONICAL — sub-sector → canonical map
  • PAYS_TO_BENCH — country ISO → index ticker

Entity Relationships

Position
isinPK string
namestring
tickerstring
quantitynumber
prunumber
currencystring
entry_datedate?
categoryenum
secteurstring
paysISO2
potentielnumber?
Portfolio
1 ──── *

ArbPortfolio
1 ──── *
Portfolio (main)
— (singleton file)
positions[ ]Position[]
file: portfolio.json
1 ──── *

Client
* ──── *
ArbPortfolio
idPK string
namestring
createddate
typeenum
descriptionstring?
positions[ ]Position[]
file: arb_portfolios.json
DCFParams
ticker → 1
DCFParams
tickerPK string
current_pricenumber
shares_outstandingnumber
total_debtnumber
total_cashnumber
waccnumber
terminal_growthnumber
fcf_projections[ ]number[]
currencystring
last_updateddate
file: dcf_params.json
* ──── *
(portfolio_ids)
Client
idPK string
namestring
emailstring
phonestring?
portfolio_ids[ ]FK ArbPortfolio
risk_profileenum
notesstring?
createddate
file: clients.json
BenchConfig (LS)
key: benchConfig_{id}PK
countryOverridesmap
sectorOverridesmap
localStorage (browser)
FromToCardinalityLink fieldNotes
PortfolioPosition 1 ──── * positions[] Embedded array; ISIN unique within the portfolio
ArbPortfolioPosition 1 ──── * positions[] Embedded array; same ISIN can appear in multiple arb portfolios
ClientArbPortfolio * ──── * portfolio_ids[] Client stores array of arb portfolio IDs; no back-reference on ArbPortfolio
Position (ticker)DCFParams * ──── 0..1 ticker Optional: DCF params exist only if manually saved by the user
ArbPortfolioBenchConfig 1 ──── 0..1 benchConfig_{id} Stored in browser localStorage; lost on cache clear

Entity Details

Position

Embedded in Portfolio / ArbPortfolio
FieldTypeNotes
PK isin string(12) ISIN code — unique within a given portfolio. Auto-generated as PRIV{ticker}{ts} if absent
namestring Display name of the asset
tickerstring Yahoo Finance ticker symbol; may be empty for funds
quantitynumber ≥ 0 Number of shares / units held
prunumber ≥ 0 Average purchase price (Prix de Revient Unitaire) in native currency
currencystring(3)
EURUSDGBP GBpCHFHKD JPY
GBp = pence (÷100 to get GBP)
entry_datestring? Purchase date YYYY-MM-DD; optional
categoryenum
StockETFFund CashOther
potentielnumber? Analyst target upside in %; may be negative; optional
secteurstring Sector name — normalized via SECTOR_CANONICAL on the server
paysstring(2) ISO 3166-1 alpha-2 country code (e.g. FR, US, DE)
fraisnumber? Transaction fees in EUR; optional

Portfolio (main)

portfolio.json
FieldTypeNotes
positionsPosition[] Ordered list of portfolio positions. Singleton file — only one main portfolio.

ArbPortfolio

arb_portfolios.json
FieldTypeNotes
PK idstring Generated: arb_{timestamp_ms}
namestring User-defined portfolio name
createdstring Creation date YYYY-MM-DD
typeenum
targetstrategy simulationwatchlist
descriptionstring? Optional free-form description
positionsPosition[] Embedded array of positions

Client

clients.json
FieldTypeNotes
PK idstring Generated: client_{timestamp_ms}
namestring Client full name; required
emailstring Email address; optional
phonestring? Phone number; optional
risk_profileenum
Conservative Moderate Aggressive
FK portfolio_idsstring[] Array of ArbPortfolio IDs associated with this client
notesstring? Free-form notes; optional
createdstring Creation date YYYY-MM-DD

DCFParams

dcf_params.json
FieldTypeNotes
PK tickerstring Yahoo Finance ticker; top-level key in the JSON object
current_pricenumber Price at time of save (native currency)
shares_outstandingnumber Total shares outstanding
total_debtnumber Total financial debt (used in WACC D/V)
total_cashnumber Cash & equivalents (added to equity value)
waccnumber Discount rate (decimal); overrides auto-computed WACC when present
terminal_growthnumber Terminal growth rate (decimal, e.g. 0.025)
fcf_projectionsnumber[] Annual FCF projections (absolute, same currency as price); triggers complete DCF mode when non-empty
currencystring Currency of the ticker (EUR, USD, GBP…)
last_updatedstring Date of last save YYYY-MM-DD

WACCDetail

Computed — not persisted
FieldTypeNotes
rfnumber Risk-free rate for the ticker's currency (from DCF_RF_BY_CCY)
betanumber Beta from Yahoo Finance quoteSummary
mrpnumber Market risk premium (global DCF_MRP, default 5.5%)
kenumber Cost of equity: rf + β × mrp
kd_grossnumber Pre-tax cost of debt from Yahoo Finance
tax_ratenumber Effective tax rate from Yahoo Finance
kd_netnumber After-tax cost of debt: kd_gross × (1 − tax)
evnumber Equity ratio E/V
dvnumber Debt ratio D/V
waccnumber Final WACC clamped to [4%, 25%]

BenchConfig

localStorage
FieldTypeNotes
PK keystring benchConfig_{portfolioId} — stored in browser localStorage
countryOverridesmap<ISO2, ticker> Country code → custom index ticker override
sectorOverridesmap<string, ticker> Sector name → custom ETF ticker override

Business Rules & Constraints

  • An ISIN must be unique within a single portfolio (main or arb). The same ISIN may appear in multiple arb portfolios.
  • If an ISIN is not provided on position creation, one is auto-generated: PRIV{BASE_TICKER}{timestamp}.
  • DCF complete mode is activated when fcf_projections is a non-empty array in dcf_params.json. Otherwise, simple mode (Yahoo Finance data only) is used.
  • WACC auto-computation: server uses Yahoo Finance beta, debt, equity, tax rate. Clamped to [4%, 25%] to avoid extreme values.
  • DCF global overrides: DCF_MRP, DCF_TERM, and DCF_RF_BY_CCY are global in-process variables. The arbitrage DCF scenario temporarily overrides them via try/finally — thread-safe only in the single-threaded server model.
  • GBp (pence): prices are automatically divided by 100 when currency is GBp to convert to GBP before FX or P&L calculation.
  • Client report FX conversion: all position values are converted to EUR using live EURUSD, EURGBP, EURCHF rates fetched at report generation time.
  • Deleting a Client does not delete the associated ArbPortfolios.
  • Deleting an ArbPortfolio does not update the portfolio_ids arrays of associated Clients — stale references are silently ignored on report generation.
  • Cache TTLs: quotes 5 min, historical data 15 min, arbitrage simulation 10 min, VaR 30 min. Cache is shared across all HTTP requests and protected by a threading.Lock.

API Endpoint → Entity Mapping

MethodPathEntity / Action
GET/api/portfolioRead Portfolio.positions[]
POST/api/portfolioAppend Position to Portfolio.positions[]
PUT/api/portfolio/{isin}Update Position in Portfolio.positions[]
DEL/api/portfolio/{isin}Remove Position from Portfolio.positions[]
POST/api/portfolio/bulkBatch-append Positions to Portfolio
GET/api/portfolio/exportSerialize Portfolio.positions[] as CSV
GET/api/arb-portfoliosRead all ArbPortfolios
POST/api/arb-portfoliosCreate ArbPortfolio
PUT/api/arb-portfolios/{id}Update ArbPortfolio
DEL/api/arb-portfolios/{id}Delete ArbPortfolio
GET/api/dcf/params?ticker=Read DCFParams[ticker]
POST/api/dcf/paramsWrite DCFParams[ticker]
GET/api/dcf/simple?ticker=Compute WACCDetail + DCF (no FCF projections)
GET/api/dcf/complete?ticker=Compute WACCDetail + DCF using DCFParams.fcf_projections
GET/api/dcf/portfolio?portfolio_id=Compute DCF score for each Position.ticker in portfolio
GET/api/arbitrage/simulateRead Portfolio + ArbPortfolios → compute ArbitrageResult (not persisted)
GET/api/clientsRead all Clients + hydrate portfolio details
POST/api/clientsCreate Client
PUT/api/clients/{id}Update Client
DEL/api/clients/{id}Delete Client
GET/api/clients/{id}/reportRead Client + ArbPortfolios + live FX → compute EUR-valued report
GET/api/esg?isin=Read ESG scores from in-memory Excel data
GET/api/esg/portfolioRead ESG scores for all Portfolio.positions[]
GET/api/quotes?tickers=Fetch live quotes from Yahoo Finance (cached)
GET/api/varMonte Carlo VaR + CVaR on Portfolio (cached)
POST/api/bhbBHB attribution — reads Portfolio + benchmark data
GET/api/whatifWhat If simulation — reads Portfolio + user params (not persisted)
GET/api/cache/clearPurge all in-memory cache entries