Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,538 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user