# FX Frankfurter Primary & Lightweight Charts Hotfix Notes

Use for FinanceManager equity/portfolio FX and charting work.

## Durable lessons

### Frankfurter.dev v2 shape

For standard FX to CHF, use Frankfurter.dev as the primary provider for EUR/CHF, USD/CHF, and GBP/CHF before falling back to TwelveData. Frankfurter does not need an API key.

Preferred latest endpoint:

```text
GET https://api.frankfurter.dev/v2/rates?base=EUR&quotes=CHF
```

Historical endpoint:

```text
GET https://api.frankfurter.dev/v2/rates?date=YYYY-MM-DD&base=EUR&quotes=CHF
```

Important: the v2 `/rates` response can be a list:

```json
[{"date":"2026-05-20","base":"EUR","quote":"CHF","rate":0.91584}]
```

Do not only parse the old `{ "rates": { "CHF": ... } }` shape. Accept both list and dict shapes defensively.

### FX priority and status semantics

For valuation FX into CHF:

1. Frankfurter.dev primary.
2. Local cache second.
3. TwelveData only as fallback.
4. Manual override only when explicitly requested.

For `CHF -> CHF`:

- `fx_status=not_needed`
- `fx_rate_to_chf=1`
- never surface as an FX error.

For valuation rechecks, request latest FX. Historical FX dates are appropriate for cost basis / transaction-date calculations, not current market valuation. A common bug is accidentally passing today's concrete date into Frankfurter for valuation, which turns a latest quote into a historical query and can produce false provider errors.

If Frankfurter succeeds, do not show raw TwelveData fallback errors in user mode. User-mode copy should be concise: `FX aktualisiert` or `FX fehlt, Frankfurter konnte keinen Kurs liefern`. Keep raw provider diagnostics for admin/debug surfaces.

Separate these statuses clearly:

- missing market price
- missing valuation FX
- missing historical/cost-basis FX

A position with a valid CHF market value must not display `FX fehlt für Bewertung`; at most show a cost-basis/data-quality warning.

### Apple / provider-symbol quality

Do not silently remap Apple between local EU listings and US NASDAQ listing.

- ISIN: `US0378331005`
- US listing: `AAPL` / NASDAQ / USD
- EU listings may use `APC.*` symbols and EUR pricing, e.g. `APC.F`

In the detail panel expose:

- instrument name
- ISIN
- local ticker
- provider symbol
- exchange
- price currency
- FX-to-CHF status
- data source/provider

If provider-symbol mapping is unclear, require an explicit user/data-quality action rather than guessing.

### Charting pattern

For equity/ETF detail charts, prefer TradingView Lightweight Charts (`lightweight-charts`, Apache-2.0) over generic ECharts candlesticks when the user expects a finance-chart UX. Do not use TradingView Advanced Charts or broker widgets.

Frontend rules:

- No provider keys in frontend.
- No provider calls on normal render.
- Chart data loads only on explicit button/range refresh.
- Reusable component pattern: `frontend/src/components/charts/MarketChart.vue`.
- Include candlestick and optional line chart, volume histogram, range selector, loading/empty/error states, crosshair, zoom/pan, responsive sizing.
- Show `Zu wenig Kursdaten verfügbar.` when candles are insufficient; do not draw dummy diagonal lines.

Backend candles response should be object-shaped, not tuple arrays, e.g.:

```json
{
  "instrument_id": "...",
  "symbol": "AAPL",
  "provider_symbol": "AAPL",
  "range": "1d",
  "interval": "5m",
  "provider": "yfinance",
  "currency": "USD",
  "exchange_timezone": "America/New_York",
  "quality_status": "fresh",
  "fetched_at": "...",
  "candles": [
    {"time":"2026-05-20T09:30:00-04:00","open":"190.10","high":"191.50","low":"189.80","close":"191.20","volume":"1234567"}
  ]
}
```

Support ranges/intervals:

- `1d` / `5m`
- `5d` / `15m`
- `1mo` / `1d`
- `6mo` / `1d`
- `ytd` / `1d`
- `1y` / `1d`

Use yfinance only in backend, behind explicit API calls, with cache/TTL and clear statuses: `missing_provider_symbol`, `provider_error`, `missing`, `fresh`.

### Verification checklist

For this class of hotfix, verify:

- Python compile
- full pytest
- full frontend tests
- frontend build
- source and build secret scans
- git safety / no runtime DB or reports in Git
- `git diff --check`
- browser sanity: `/equity`, row click, FX refresh, chart load, range switch, JS console clean
- local and Tailscale routes
- commit, push, remote hash match
