#7880·ghostfolio

[BUG] Holdings valued at cost basis instead of market value: marketPrice: 1, investment: 0, hasErrors: true for every symbol despite correct MarketData

Author: jkaware55Created Sep 15, 2026Updated Sep 15, 2026

Bug description

Every holding is valued at its transaction unitPrice (cost basis) rather than its current market price. The portfolio total is therefore wrong for any position whose price has moved.

The market data itself is fine — MarketData contains correct, current prices for every symbol, gathered without errors. The portfolio calculator does not use them. It flags every symbol as errored and falls back to cost basis.

Minimal reproduction below: a fresh official Docker image, one user, three BUY activities.

Steps to reproduce Start a fresh instance from ghostfolio/ghostfolio:latest with an empty PostgreSQL database and a new Redis (no other configuration; default DATA_SOURCES, base currency USD). POST /api/v1/user to create a user, then authenticate via POST /api/v1/auth/anonymous. Import three activities via POST /api/v1/import: json { "activities": [ {"symbol":"AAPL","type":"BUY","quantity":100,"unitPrice":100.00,"fee":0, "currency":"USD","dataSource":"YAHOO","date":"2026-09-04T00:00:00.000Z"}, {"symbol":"VOO","type":"BUY","quantity":100,"unitPrice":100.00,"fee":0, "currency":"USD","dataSource":"YAHOO","date":"2026-09-04T00:00:00.000Z"}, {"symbol":"FXAIX","type":"BUY","quantity":100,"unitPrice":100.00,"fee":0, "currency":"USD","dataSource":"YAHOO","date":"2026-09-04T00:00:00.000Z"} ] } Wait for data gathering to complete (or POST /api/v1/admin/gather). GET /api/v1/portfolio/details?range=max. Expected behaviour

Each holding valued at quantity × market price:

Symbol Qty Market price Expected value AAPL 100 333.08 33,308.00 VOO 100 708.01 70,801.00 FXAIX 100 268.67 26,867.00 Total 130,976.00 Actual behaviour AAPL value=10,000.00 marketPrice=1 quantity=100 investment=0 VOO value=10,000.00 marketPrice=1 quantity=100 investment=0 FXAIX value=10,000.00 marketPrice=1 quantity=100 investment=0 TOTAL: $30,000.00 summary.currentValueInBaseCurrency: 0 summary.totalInvestment: 0

$30,000 is exactly 300 shares × $100.00 — the cost basis.

The cached portfolio snapshot contains:

json { "hasErrors": true, "errors": [ {"dataSource":"YAHOO","symbol":"AAPL"}, {"dataSource":"YAHOO","symbol":"VOO"}, {"dataSource":"YAHOO","symbol":"FXAIX"} ], "totalInvestment": 0 } Why this is not a data-provider problem

The prices are in the database and are correct. Immediately after the run above:

sql SELECT symbol, count(*), max(date)::date, round(max("marketPrice")::numeric,2) FROM "MarketData" GROUP BY symbol ORDER BY symbol;

symbol | count | max | px --------+-------+------------+-------- AAPL | 11 | 2026-09-14 | 333.08 FXAIX | 11 | 2026-09-14 | 268.67 VOO | 11 | 2026-09-14 | 708.01

Data gathering completes and logs no errors. MarketData.symbol/dataSource joins cleanly to SymbolProfile.symbol/dataSource for all three.

yahoo-finance2 works from inside the same container, including the batch call the snapshot path uses:

js const yf = new YahooFinance(); await yf.quote(['AAPL','VOO','FXAIX']); // all three return live prices, 77-572 ms

quote, quoteSummary and chart all succeed.

GET /api/v1/symbol/YAHOO/AAPL returns a correct live price through Ghostfolio's own data-provider layer.

investment: 0 requires no market data at all. It is quantity × unitPrice taken straight from the activity, and it returns 0. That suggests the calculation fails before market prices are involved, and the errored symbols are a symptom rather than the cause.

Not asset-class specific

Reproduced identically with a US equity (AAPL), an ETF (VOO) and a US mutual fund (FXAIX). All three fail the same way.

Relationship to #5675 / #7813

This is a different code path, and the #7813 fix is present in the affected builds.

Greping the built dist/apps/api/main.js on 3.70.1 (built 2026-09-14) finds zero occurrences of getLatestOrder and zero of latestActivity — the identifiers #7813 removed. There are no DIVIDEND activities involved. All activities are BUY, so the trigger described in #5675 cannot be firing. Despite the invented-price path being gone, holdings still report cost basis as the market value.

#5675 concerned improving the fallback. This issue is about why the fallback is reached at all when valid market data is present. May be relevant to the rework discussed in #5678.

Environment Version: ghostfolio/ghostfolio:latest (also reproduced on 3.67.0, 3.69.0, 3.70.1) Deployment: self-hosted Database: PostgreSQL 16 (fresh, empty) Redis: 7 Base currency: USD Data source: YAHOO (default) yahoo-finance2: 4.0.2 Reproduced on both the official Docker image and a Node/systemd install on Debian 13 Already ruled out REQUEST_TIMEOUT — raised from the 2000 ms default to 30000, no change Stale cache — FLUSHALL on Redis plus restart; reproduces on a freshly computed snapshot Version — 3.67.0, 3.69.0, 3.70.1 and latest all identical Install method — official Docker image with a brand-new database behaves identically Timezone — MarketData.date and Order.date both store clean 00:00:00 Base currency — user settings report baseCurrency: USD Activity date vs market data range — activities dated 2026-09-04, market data spans 2026-09-04 → 2026-09-14 Asset class — equity, ETF and mutual fund all affected Additional notes No output appears in the application log during the calculation, even with LOG_LEVELS=["log","error","warn","debug","verbose"]. The snapshot path is silent. GET /api/v1/admin returns "exchangeRates": null and "settings": {"CURRENCIES": []}. I don't know whether that is expected for a single-currency USD portfolio, but since marketPrice: 1 is also what a USD cash position legitimately reports, a currency-conversion path returning undefined (producing NaN, collapsing to 0) would be consistent with the symptoms. This is speculation — I could not confirm it. Happy to run diagnostic queries or test a patched build; the reproduction instance is disposable.