539 lines
22 KiB
JavaScript
539 lines
22 KiB
JavaScript
// Generated, do not edit. sha256:8f2431fd9a93cf7a3b491d7a9c3586996866c1c56d872590bc1b901665a63a6e
|
|
import { listAccounts } from '../data/accounts.js';
|
|
import { listAssets } from '../data/assets.js';
|
|
import { listEvents, listFlowLegs, listTradeEvents } from '../data/events.js';
|
|
import { firstPositionDate, positionAt, positionsByAsset, unexplainedByPair } from '../data/positions.js';
|
|
import { listPriceHistory } from '../data/price-history.js';
|
|
import { getPref } from '../data/preferences.js';
|
|
import { toDailyGrid } from '../signals/data.js';
|
|
import { listProviders } from '../data/providers.js';
|
|
import { provenanceOn, provenanceOfRow, originFrom, liveOrigin } from '../../shared/price-provenance.js';
|
|
import { ensurePriceHistoryFor, ensurePointsAtFor, liveSpotPriceFor, lastSpotSource } from '../price/providers.js';
|
|
import { REIKWIJDTE, categorieVoor, reikwijdteVoor } from '../tax/constants.js';
|
|
import { isOwnWealth } from '../../shared/wealth-scope.js';
|
|
import { CONFIG } from '../../shared/config.js';
|
|
import { modifiedDietz, irr, averageCapital } from './performance.js';
|
|
import { dagreeks, metSpotVandaag, todayResults } from './series.js';
|
|
import { listValuations } from '../data/valuations.js';
|
|
import { toUtcIso, nowUtcIso } from '../../shared/time.js';
|
|
import { t } from '../i18n/index.js';
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
function priceAt(raster, isoDate) {
|
|
if (!raster || !raster.grid || raster.grid.length === 0) return null;
|
|
const { grid, dagen, kind } = raster;
|
|
const ts = Date.parse(`${isoDate}T00:00:00Z`);
|
|
if (ts < grid[0][0]) return null;
|
|
const grens = CONFIG.PRICE_HISTORY.CARRY_FORWARD_DAYS[kind];
|
|
if (grens != null && !versGenoeg(dagen, ts, grens)) return null;
|
|
const idx = Math.min(grid.length - 1, Math.floor((ts - grid[0][0]) / DAY_MS));
|
|
return grid[idx][1];
|
|
}
|
|
|
|
function versGenoeg(dagen, ts, grens) {
|
|
if (!dagen || dagen.length === 0) return false;
|
|
for (let i = dagen.length - 1; i >= 0; i--) {
|
|
if (dagen[i] <= ts) return ts - dagen[i] <= grens * DAY_MS;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const peilIso = year => toUtcIso(`${year}-01-01T00:00:00`);
|
|
|
|
const localNowIso = () => nowUtcIso();
|
|
|
|
async function liveSpots(assets) {
|
|
const m = new Map();
|
|
for (const a of assets) {
|
|
try { const price = await liveSpotPriceFor(a); if (price != null) m.set(a.id, price); }
|
|
catch { }
|
|
}
|
|
return m;
|
|
}
|
|
|
|
function waardeVan(sats, div, price) {
|
|
if (sats === 0) return 0;
|
|
return price != null ? sats / div * price : null;
|
|
}
|
|
|
|
function currentPriceFor(asset, raster, live) {
|
|
const spot = live.get(asset.id);
|
|
if (spot != null) return spot;
|
|
const grid = raster && raster.grid;
|
|
return grid && grid.length ? grid[grid.length - 1][1] : null;
|
|
}
|
|
|
|
function raster(rows, asset) {
|
|
const dagen = [...new Set(rows.filter(r => r.fiatPrice != null && r.fiatPrice > 0).map(r => r.observedAt.slice(0, 10)))]
|
|
.sort()
|
|
.map(d => Date.parse(`${d}T00:00:00Z`));
|
|
return { grid: toDailyGrid(rows), dagen, kind: asset.kind, rows };
|
|
}
|
|
|
|
function herkomstVan(eigenRaster, isoDate, price, ctx) {
|
|
if (price == null || !eigenRaster || !eigenRaster.rows) return null;
|
|
return originFrom(provenanceOn(eigenRaster.rows, isoDate,
|
|
{ assetKind: eigenRaster.kind, manualProviderId: ctx.manualId, usedPrice: price }), ctx.names);
|
|
}
|
|
|
|
function huidigeHerkomst(asset, eigenRaster, live, price, ctx) {
|
|
if (price == null) return null;
|
|
if (live.get(asset.id) != null) return liveOrigin(price, lastSpotSource(asset));
|
|
const grid = eigenRaster && eigenRaster.grid;
|
|
if (!grid || !grid.length || !eigenRaster.rows) return null;
|
|
const d = provenanceOn(eigenRaster.rows, new Date(grid[grid.length - 1][0]).toISOString().slice(0, 10),
|
|
{ assetKind: eigenRaster.kind, manualProviderId: ctx.manualId, usedPrice: price });
|
|
if (!d || !d.row) return null;
|
|
return originFrom(provenanceOfRow(d.row, { manualProviderId: ctx.manualId }), ctx.names);
|
|
}
|
|
|
|
export async function herkomstContext() {
|
|
try {
|
|
const providers = await listProviders();
|
|
const manual = providers.find(p => p.key === 'manual');
|
|
return { names: new Map(providers.map(p => [p.id, p.name])), manualId: manual ? manual.id : null };
|
|
} catch {
|
|
return { names: new Map(), manualId: null };
|
|
}
|
|
}
|
|
|
|
async function buildGrids(assets, peilDates = []) {
|
|
for (const a of assets) {
|
|
try { await ensurePriceHistoryFor(a); }
|
|
catch { }
|
|
}
|
|
for (const a of assets) {
|
|
if (peilDates.length) { try { await ensurePointsAtFor(a, peilDates); } catch { } }
|
|
}
|
|
const grids = new Map();
|
|
for (const a of assets) {
|
|
try { grids.set(a.id, raster(await listPriceHistory(a.id), a)); }
|
|
catch { grids.set(a.id, null); }
|
|
}
|
|
return grids;
|
|
}
|
|
|
|
async function flows(fromIso, untilIso, grids, telt = () => true, grens = []) {
|
|
let deposits = 0, withdrawals = 0, giften = 0;
|
|
const legs = [];
|
|
for (const e of await listTradeEvents(fromIso, untilIso)) {
|
|
const kant = e.type === 'buy' ? e.toAccountId
|
|
: e.type === 'sell' ? e.fromAccountId
|
|
: (e.fromAccountId ?? e.toAccountId);
|
|
if (!telt(kant, e.assetId)) continue;
|
|
const eenheden = e.amount / Math.pow(10, e.decimals);
|
|
const fee = e.fiatFee != null ? e.fiatFee : 0;
|
|
if (e.type === 'transfer' && e.fromAccountId != null && fee > 0) {
|
|
deposits += fee;
|
|
legs.push({ fiat: fee, atIso: e.occurredAt });
|
|
}
|
|
if (e.type === 'transfer' && e.priceOrigin !== 'none') {
|
|
if ((e.fromAccountId == null) === (e.toAccountId == null)) continue;
|
|
const eigenRaster = grids ? grids.get(e.assetId) : null;
|
|
const prijs = eigenRaster ? priceAt(eigenRaster, e.occurredAt.slice(0, 10)) : null;
|
|
if (prijs == null) continue;
|
|
const fiat = eenheden * prijs;
|
|
if (e.fromAccountId == null) { deposits += fiat; legs.push({ fiat, atIso: e.occurredAt }); }
|
|
else { withdrawals += fiat; legs.push({ fiat: -fiat, atIso: e.occurredAt }); }
|
|
continue;
|
|
}
|
|
if (e.priceOrigin === 'none') {
|
|
if (e.type === 'transfer' && (e.fromAccountId == null) === (e.toAccountId == null)) continue;
|
|
const eigenRaster = grids ? grids.get(e.assetId) : null;
|
|
const prijs = eigenRaster ? priceAt(eigenRaster, e.occurredAt.slice(0, 10)) : null;
|
|
const binnen = e.type === 'transfer' ? e.fromAccountId == null : e.type === 'buy';
|
|
if (prijs != null) {
|
|
const fiat = (binnen ? 1 : -1) * eenheden * prijs;
|
|
giften += fiat;
|
|
legs.push({ fiat, atIso: e.occurredAt });
|
|
}
|
|
continue;
|
|
}
|
|
const fiat = eenheden * e.fiatPrice;
|
|
if (e.type === 'buy') {
|
|
deposits += fiat + fee;
|
|
legs.push({ fiat: fiat + fee, atIso: e.occurredAt });
|
|
} else {
|
|
withdrawals += fiat - fee;
|
|
legs.push({ fiat: -(fiat - fee), atIso: e.occurredAt });
|
|
}
|
|
}
|
|
for (const g of grens) {
|
|
if (!(g.occurredAt > fromIso && g.occurredAt <= untilIso)) continue;
|
|
const eigenRaster = grids ? grids.get(g.assetId) : null;
|
|
const prijs = eigenRaster ? priceAt(eigenRaster, g.occurredAt.slice(0, 10)) : null;
|
|
if (prijs == null) continue;
|
|
const fiat = g.eenheden * prijs;
|
|
if (g.naarBuiten) { withdrawals += fiat; legs.push({ fiat: -fiat, atIso: g.occurredAt }); }
|
|
else { deposits += fiat; legs.push({ fiat, atIso: g.occurredAt }); }
|
|
}
|
|
return { deposits, withdrawals, giften, legs };
|
|
}
|
|
|
|
function legValue(leg, eigenRaster) {
|
|
const units = leg.units / Math.pow(10, leg.decimals);
|
|
if (leg.priceOrigin !== 'none') {
|
|
if (leg.type === 'buy') return units * leg.fiatPrice + (leg.fiatFee || 0);
|
|
if (leg.type === 'sell') return units * leg.fiatPrice - (leg.fiatFee || 0);
|
|
}
|
|
const price = priceAt(eigenRaster, leg.occurredAt.slice(0, 10));
|
|
return price == null ? null : units * price;
|
|
}
|
|
|
|
async function pairFlows(fromIso, untilIso, grids) {
|
|
const uit = new Map();
|
|
for (const leg of await listFlowLegs(fromIso, untilIso)) {
|
|
const key = `${leg.accountId}|${leg.assetId}`;
|
|
const rec = uit.get(key) || { in: 0, uit: 0, onvolledig: false, legs: [] };
|
|
const fiat = legValue(leg, grids.get(leg.assetId));
|
|
if (fiat == null) rec.onvolledig = true;
|
|
else {
|
|
if (leg.direction === 'in') rec.in += fiat;
|
|
else rec.uit += fiat;
|
|
rec.legs.push({ fiat: leg.direction === 'in' ? fiat : -fiat, atIso: leg.occurredAt });
|
|
}
|
|
const fee = Number(leg.fiatFee) || 0;
|
|
if (leg.type === 'transfer' && leg.direction === 'out' && fee > 0) {
|
|
rec.in += fee;
|
|
rec.legs.push({ fiat: fee, atIso: leg.occurredAt });
|
|
}
|
|
uit.set(key, rec);
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
async function setRowPct(rows, grids, { y, running, vanIso, totIso, openingPerPaar, methode }) {
|
|
const stromen = await pairFlows(vanIso, totIso, grids);
|
|
for (const r of rows) {
|
|
const eindCel = running ? r.current : r.perYear[y + 1];
|
|
const beginCel = r.perYear[y];
|
|
if (!eindCel || !beginCel) continue;
|
|
const key = `${r.account.id}|${r.asset.id}`;
|
|
const stroom = stromen.get(key) || { in: 0, uit: 0, onvolledig: false, legs: [] };
|
|
const begin = beginCel.value;
|
|
const bruikbaar = begin != null && eindCel.value != null && !stroom.onvolledig;
|
|
eindCel.gain = bruikbaar
|
|
? eindCel.value - begin - stroom.in + stroom.uit - (openingPerPaar.get(key) || 0)
|
|
: null;
|
|
eindCel.pct = bruikbaar && begin > 0
|
|
? rendementVan(methode, {
|
|
gain: eindCel.gain, begin, eind: eindCel.value - (openingPerPaar.get(key) || 0), flows: stroom.legs,
|
|
fromIso: vanIso, untilIso: totIso,
|
|
})
|
|
: null;
|
|
eindCel.basis = bruikbaar && begin > 0
|
|
? averageCapital(begin, stroom.legs, { fromIso: vanIso, untilIso: totIso })
|
|
: null;
|
|
eindCel.weging = bruikbaar
|
|
? averageCapital(begin, stroom.legs, { fromIso: vanIso, untilIso: totIso })
|
|
: null;
|
|
}
|
|
}
|
|
|
|
export function sumReturn(cellen = []) {
|
|
let gain = 0, weging = 0, begin = 0;
|
|
for (const { begin: b, eind: e } of cellen) {
|
|
if (!e || !b || e.gain == null || e.weging == null) return null;
|
|
gain += e.gain;
|
|
weging += e.weging;
|
|
begin += b.value || 0;
|
|
}
|
|
if (!cellen.length) return null;
|
|
return { gain, pct: begin > 0 && weging > 0 ? gain / weging : null };
|
|
}
|
|
|
|
function rendementVan(methode, { gain, begin, eind, flows, fromIso, untilIso }) {
|
|
if (methode === 'irr') {
|
|
const r = irr([
|
|
{ fiat: -begin, atIso: fromIso },
|
|
...flows.map(f => ({ fiat: -f.fiat, atIso: f.atIso })),
|
|
{ fiat: eind, atIso: untilIso },
|
|
], { fromIso });
|
|
if (r != null) return r;
|
|
}
|
|
return modifiedDietz({ gain, begin, flows, fromIso, untilIso });
|
|
}
|
|
|
|
const geenRekening = () => ({ id: null, name: t('wealth.noAccount'), taxCategory: null, taxScope: null, isUnassigned: true });
|
|
|
|
async function restPerAsset(peil, perAssetSom) {
|
|
const uit = new Map();
|
|
for (const r of await positionsByAsset(peil)) {
|
|
const rest = r.net - (perAssetSom.get(r.id) || 0);
|
|
if (rest !== 0) uit.set(r.id, rest);
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
const withoutTaxSettings = x => ({ ...x, taxCategory: null, taxScope: null });
|
|
|
|
export async function buildWealthModel({ ignoreTaxSettings = false } = {}) {
|
|
let assets = await listAssets();
|
|
let accounts = await listAccounts();
|
|
const hasTaxSettings = [...assets, ...accounts].some(x => x.taxCategory != null || x.taxScope != null);
|
|
if (ignoreTaxSettings) {
|
|
assets = assets.map(withoutTaxSettings);
|
|
accounts = accounts.map(withoutTaxSettings);
|
|
}
|
|
const first = await firstPositionDate();
|
|
const now = new Date();
|
|
const nowIso = localNowIso();
|
|
if (!first || assets.length === 0) return { years: [], rows: [], totals: null };
|
|
|
|
const firstYear = Number(first.slice(0, 4));
|
|
const huidigJaar = now.getFullYear();
|
|
const startPref = Number(await getPref('app', 'start_year')) || CONFIG.START_YEAR.AUTO;
|
|
const methode = String(await getPref('app', 'mwr_method') || 'dietz');
|
|
const startYear = Math.min(Math.max(firstYear + 1, startPref), huidigJaar);
|
|
const ingekort = startYear > firstYear + 1;
|
|
const years = [];
|
|
for (let y = startYear; y <= huidigJaar; y++) years.push(y);
|
|
|
|
const grids = await buildGrids(assets, years.map(y => `${y}-01-01`));
|
|
const live = await liveSpots(assets);
|
|
const multiAsset = assets.length > 1;
|
|
const hctx = await herkomstContext();
|
|
|
|
let rows = [];
|
|
const somPerAsset = new Map();
|
|
for (const moment of [...years.map(peilIso), nowIso]) somPerAsset.set(moment, new Map());
|
|
const telMee = (moment, assetId, sats) => {
|
|
const m = somPerAsset.get(moment);
|
|
m.set(assetId, (m.get(assetId) || 0) + sats);
|
|
};
|
|
for (const ac of accounts) {
|
|
for (const a of assets) {
|
|
const eigenRaster = grids.get(a.id);
|
|
const div = Math.pow(10, a.decimals);
|
|
const perYear = {};
|
|
let any = false;
|
|
for (const y of years) {
|
|
const sats = await positionAt(ac.id, a.id, peilIso(y));
|
|
telMee(peilIso(y), a.id, sats);
|
|
const price = priceAt(eigenRaster, `${y}-01-01`);
|
|
perYear[y] = { sats, price, value: waardeVan(sats, div, price), gain: null, pct: null,
|
|
origin: sats === 0 ? null : herkomstVan(eigenRaster, `${y}-01-01`, price, hctx) };
|
|
if (sats !== 0) any = true;
|
|
}
|
|
const curSats = await positionAt(ac.id, a.id, nowIso);
|
|
telMee(nowIso, a.id, curSats);
|
|
const curPrice = currentPriceFor(a, eigenRaster, live);
|
|
const current = { sats: curSats, price: curPrice, value: waardeVan(curSats, div, curPrice), gain: null, pct: null,
|
|
origin: curSats === 0 ? null : huidigeHerkomst(a, eigenRaster, live, curPrice, hctx) };
|
|
if (curSats !== 0) any = true;
|
|
if (!any) continue;
|
|
rows.push({
|
|
account: ac, asset: a,
|
|
categorie: categorieVoor(ac, a),
|
|
reikwijdte: reikwijdteVoor(ac, a),
|
|
name: multiAsset ? `${ac.name} · ${a.symbol}` : ac.name,
|
|
perYear, current,
|
|
});
|
|
}
|
|
}
|
|
|
|
const restPerMoment = new Map();
|
|
for (const [moment, som] of somPerAsset) restPerMoment.set(moment, await restPerAsset(moment, som));
|
|
for (const a of assets) {
|
|
const div = Math.pow(10, a.decimals);
|
|
const eigenRaster = grids.get(a.id);
|
|
const perYear = {};
|
|
let any = false;
|
|
for (const y of years) {
|
|
const sats = restPerMoment.get(peilIso(y)).get(a.id) || 0;
|
|
const price = priceAt(eigenRaster, `${y}-01-01`);
|
|
perYear[y] = { sats, price, value: waardeVan(sats, div, price), gain: null, pct: null,
|
|
origin: sats === 0 ? null : herkomstVan(eigenRaster, `${y}-01-01`, price, hctx) };
|
|
if (sats !== 0) any = true;
|
|
}
|
|
const curSats = restPerMoment.get(nowIso).get(a.id) || 0;
|
|
const curPrice = currentPriceFor(a, eigenRaster, live);
|
|
const current = { sats: curSats, price: curPrice, value: waardeVan(curSats, div, curPrice), gain: null, pct: null,
|
|
origin: curSats === 0 ? null : huidigeHerkomst(a, eigenRaster, live, curPrice, hctx) };
|
|
if (curSats !== 0) any = true;
|
|
if (!any) continue;
|
|
const ac = geenRekening();
|
|
rows.push({
|
|
account: ac, asset: a,
|
|
categorie: categorieVoor(ac, a),
|
|
reikwijdte: reikwijdteVoor(ac, a),
|
|
name: multiAsset ? `${ac.name} · ${a.symbol}` : ac.name,
|
|
perYear, current,
|
|
});
|
|
}
|
|
|
|
const buitenBox3 = rows
|
|
.filter(r => r.reikwijdte === REIKWIJDTE.NONE)
|
|
.map(r => ({ name: r.name, value: r.current.value }));
|
|
rows = rows.filter(r => r.reikwijdte !== REIKWIJDTE.NONE);
|
|
const categoryRows = rows;
|
|
rows = rows.filter(r => r.reikwijdte !== REIKWIJDTE.CATEGORY);
|
|
|
|
const emptyYears = years.filter(y => rows.every(r => r.perYear[y].sats === 0));
|
|
|
|
const totals = { perYear: {}, current: 0 };
|
|
for (const y of years) totals.perYear[y] = rows.reduce((s, r) => s + (r.perYear[y].value || 0), 0);
|
|
totals.current = rows.reduce((s, r) => s + (r.current.value || 0), 0);
|
|
|
|
const ONBEKEND = 'onbekend';
|
|
const catVanRij = (r) => r.categorie || ONBEKEND;
|
|
const perCategorie = (waardeVanRij, lijst = rows) => {
|
|
const uit = {};
|
|
for (const r of lijst) {
|
|
const cat = catVanRij(r);
|
|
uit[cat] = (uit[cat] || 0) + (waardeVanRij(r) || 0);
|
|
}
|
|
return uit;
|
|
};
|
|
totals.perYearPerCategorie = {};
|
|
for (const y of years) totals.perYearPerCategorie[y] = perCategorie(r => r.perYear[y].value);
|
|
totals.currentPerCategorie = perCategorie(r => r.current.value);
|
|
const categorySplit = { perYear: {}, current: perCategorie(r => r.current.value, categoryRows) };
|
|
for (const y of years) categorySplit.perYear[y] = perCategorie(r => r.perYear[y].value, categoryRows);
|
|
|
|
const jaarTotaal = (perCat) => Object.values(perCat).reduce((s, v) => s + v, 0);
|
|
|
|
const rijPerPaar = new Map(rows.map(r => [`${r.account.id}|${r.asset.id}`, r]));
|
|
|
|
const rekeningVan = new Map(accounts.map(a => [a.id, a]));
|
|
const assetVan = new Map(assets.map(a => [a.id, a]));
|
|
const telt = (accountId, assetId) =>
|
|
reikwijdteVoor(accountId == null ? geenRekening() : rekeningVan.get(accountId), assetVan.get(assetId))
|
|
=== REIKWIJDTE.ALL;
|
|
const grens = (await listEvents())
|
|
.filter(e => e.type === 'transfer' && e.fromAccountId != null && e.toAccountId != null
|
|
&& telt(e.fromAccountId, e.assetId) !== telt(e.toAccountId, e.assetId))
|
|
.map(e => ({
|
|
assetId: e.assetId, occurredAt: e.occurredAt, naarBuiten: telt(e.fromAccountId, e.assetId),
|
|
eenheden: (Number(e.amount) || 0) / Math.pow(10, assetVan.get(e.assetId)?.decimals ?? 0),
|
|
}));
|
|
|
|
const aanwas = [];
|
|
for (const y of years) {
|
|
const running = y === now.getFullYear();
|
|
const perCatBegin = totals.perYearPerCategorie[y];
|
|
const perCatEind = running ? totals.currentPerCategorie : totals.perYearPerCategorie[y + 1];
|
|
const begin = jaarTotaal(perCatBegin);
|
|
const eindIso = running ? nowIso : peilIso(y + 1);
|
|
const eind = perCatEind ? jaarTotaal(perCatEind) : null;
|
|
if (eind == null) continue;
|
|
let beginWeg = 0, eindWeg = 0;
|
|
const zonderKoers = [];
|
|
for (const r of rows) {
|
|
const b = r.perYear[y], e = running ? r.current : r.perYear[y + 1];
|
|
if (!b || !e) continue;
|
|
const bOnbekend = b.sats !== 0 && b.value == null;
|
|
const eOnbekend = e.sats !== 0 && e.value == null;
|
|
if (bOnbekend === eOnbekend) continue;
|
|
if (bOnbekend) eindWeg += e.value || 0;
|
|
else beginWeg += b.value || 0;
|
|
zonderKoers.push(r.name);
|
|
}
|
|
const f = await flows(peilIso(y), eindIso, grids, telt, grens);
|
|
let unexplained = 0;
|
|
let uitBalansen = 0;
|
|
const unexplainedRows = [];
|
|
const openingPerPaar = new Map();
|
|
for (const brok of await unexplainedByPair(peilIso(y), eindIso)) {
|
|
const paar = `${brok.accountId}|${brok.assetId}`;
|
|
const r = rijPerPaar.get(paar);
|
|
if (!r) continue;
|
|
const prijs = running ? r.current.price : (r.perYear[y + 1] ? r.perYear[y + 1].price : null);
|
|
if (prijs == null) continue;
|
|
const valueOf = eenheden => eenheden / Math.pow(10, r.asset.decimals) * prijs;
|
|
const opening = valueOf(brok.openingUnits);
|
|
const value = valueOf(brok.units);
|
|
uitBalansen += opening;
|
|
openingPerPaar.set(paar, opening);
|
|
if (Math.abs(value - opening) < 0.005) continue;
|
|
unexplained += value - opening;
|
|
unexplainedRows.push({ name: r.name, units: brok.units, value: value - opening });
|
|
}
|
|
unexplainedRows.sort((a, b) => Math.abs(b.value) - Math.abs(a.value));
|
|
await setRowPct(rows, grids, { y, running, vanIso: peilIso(y), totIso: eindIso, openingPerPaar, methode });
|
|
const result = (eind - eindWeg) - (begin - beginWeg) - f.deposits + f.withdrawals - uitBalansen - f.giften;
|
|
const beginMeet = begin - beginWeg;
|
|
const pct = beginMeet > 0
|
|
? rendementVan(methode, {
|
|
gain: result, begin: beginMeet, eind: eind - eindWeg - uitBalansen, flows: f.legs,
|
|
fromIso: peilIso(y), untilIso: eindIso,
|
|
})
|
|
: null;
|
|
aanwas.push({
|
|
year: y, running, begin, eind, beginWeg,
|
|
deposits: f.deposits, withdrawals: f.withdrawals, uitBalansen, giften: f.giften,
|
|
result, pct, unexplained, unexplainedRows, zonderKoers,
|
|
});
|
|
}
|
|
|
|
const pct = { perYear: {}, current: null };
|
|
for (const y of years) pct.perYear[y] = null;
|
|
for (const a of aanwas) {
|
|
if (a.running) pct.current = a.pct; else pct.perYear[a.year + 1] = a.pct;
|
|
}
|
|
|
|
const perJaar = years.map(y => {
|
|
const perCategorie = totals.perYearPerCategorie[y] || {};
|
|
const rendement = aanwas.find(x => x.year === y) || null;
|
|
return {
|
|
year: y,
|
|
value: totals.perYear[y],
|
|
perCategorie,
|
|
categorieCompleet: !(perCategorie[ONBEKEND] > 0),
|
|
rendement: rendement ? rendement.result : null,
|
|
running: rendement ? rendement.running : false,
|
|
};
|
|
});
|
|
|
|
return { years, nowIso, rows, totals, pct, aanwas, perJaar, multiAsset, buitenBox3, firstYear,
|
|
ingekort, emptyYears, mwrMethod: methode, categoryRows, categorySplit, hasTaxSettings };
|
|
}
|
|
|
|
export async function currentStanding() {
|
|
const assets = await listAssets();
|
|
if (assets.length === 0) return { rows: [], total: 0, totalSats: 0, today: null };
|
|
const grids = await buildGrids(assets);
|
|
const live = await liveSpots(assets);
|
|
const hctx = await herkomstContext();
|
|
const nowIso = localNowIso();
|
|
const rows = [];
|
|
let total = 0, totalSats = 0;
|
|
const som = new Map();
|
|
const voegToe = (ac, a, sats) => {
|
|
if (sats === 0) return;
|
|
const eigenRaster = grids.get(a.id);
|
|
const price = currentPriceFor(a, eigenRaster, live);
|
|
const value = price != null ? sats / Math.pow(10, a.decimals) * price : null;
|
|
const origin = value != null ? huidigeHerkomst(a, eigenRaster, live, price, hctx) : null;
|
|
rows.push({ account: ac, asset: a, sats, value, origin });
|
|
if (value != null) total += value;
|
|
totalSats += sats;
|
|
};
|
|
const accounts = await listAccounts();
|
|
for (const ac of accounts) {
|
|
const eigen = isOwnWealth(ac);
|
|
for (const a of assets) {
|
|
const sats = await positionAt(ac.id, a.id, nowIso);
|
|
som.set(a.id, (som.get(a.id) || 0) + sats);
|
|
if (eigen) voegToe(ac, a, sats);
|
|
}
|
|
}
|
|
const rest = await restPerAsset(nowIso, som);
|
|
for (const a of assets) voegToe(geenRekening(), a, rest.get(a.id) || 0);
|
|
rows.sort((x, y) => (y.value || 0) - (x.value || 0));
|
|
return { rows, total, totalSats, today: await resultToday(assets, grids, live, accounts) };
|
|
}
|
|
|
|
async function resultToday(assets, grids, live, accounts) {
|
|
try {
|
|
const [events, valuations] = await Promise.all([listEvents(), listValuations()]);
|
|
const prijzen = new Map(assets.map(a => [a.id, grids.get(a.id)?.grid || []]));
|
|
const excludedAccounts = new Set(accounts.filter(ac => !isOwnWealth(ac)).map(ac => ac.id));
|
|
return todayResults(metSpotVandaag(dagreeks({ events, valuations, assets, prijzen, excludedAccounts }), assets, live));
|
|
} catch (err) {
|
|
console.warn('[standing] today\'s result not computed:', err);
|
|
return null;
|
|
}
|
|
}
|