Data Model — Investverte PMS

All persistent entities, fields, types, relationships and storage layers  ·  Updated 2026-07-24  ·  See also ARCHITECTURE.md

Storage Layers

File Server-side JSON (live, gitignored)

  • portfolio.json — main portfolio + linked benchmarks
  • arb_portfolios.json — secondary portfolios array
  • clients.json — client records
  • benchmarks.json — composite benchmark configs
  • dcf_params.json — DCF parameters per ticker
  • esg_data.json — ESG ratings by ticker
  • manual_quotes.json — manual price history by ISIN
  • ticker_proxies.json — display name → proxy ticker
  • users.json — accounts (secret)
  • config.json — data/ESG/OpenAI keys (secret)

LocalStorage Browser per-origin

  • benchConfig_{portfolioId} — index/ETF overrides
  • pfNotes_{isin} — per-position notes
  • column prefs — table visibility per portfolio

Memory Server in-process

  • _cache — quotes / history / optim TTL cache
  • _esg_cache — API ESG responses
  • sessions — authenticated users
  • DCF_MRP, DCF_TERM, DCF_RF_BY_CCY — DCF defaults
  • ARBITRAGE_MODELS — predefined strategies 1–6 + dcf
  • SECTOR_CANONICAL / PAYS_TO_BENCH — referentials

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

BenchmarkConfig

benchmarks.json
FieldTypeNotes
configsarray List of composite benchmarks
PK configs[].idnumber Benchmark id (referenced as bench:{id})
configs[].namestring Display name
configs[].components{ticker, weight}[] Weighted basket of index/ETF tickers
active_idnumber? Currently selected config id

User

users.json (secret)
FieldTypeNotes
PK usernamestring Login name
password_hashstring bcrypt hash — never store plaintext
roleenum
adminuser
FK client_idstring? For role=user: scopes access to that Client's portfolios

Config

config.json (secret)
FieldTypeNotes
data_sourceenum yahoo eodhd
eodhd_api_keystring Required when data_source=eodhd
esg_sourceenum excel api (excel also covers esg_data.json)
esg_api_url / esg_api_keystring External ESG endpoint when esg_source=api
openai_api_keystring Chat assistant; env OPENAI_API_KEY overrides

ESG Rating

esg_data.json
FieldTypeNotes
PK tickerstring Exact ticker key under ratings
esgnumber Total ESG score
e / s / gnumber? Pillar scores
nom / yearstring / number Issuer name and fiscal year

ManualQuote

manual_quotes.json
FieldTypeNotes
PK isinstring Instrument without live market feed
name / currencystring Display meta
history{date, price}[] Manual price points

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