Files
AtreidesAssetsFree/client/wealth/series.js
T

554 lines
25 KiB
JavaScript
Raw Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:ed8ca8faa6323fa2c4ff508c950678174612f6518b6d32ceba92945717d7bce7
import { listEvents } from '../data/events.js';
import { listValuations } from '../data/valuations.js';
import { listAssets } from '../data/assets.js';
import { listPriceHistory } from '../data/price-history.js';
import { toDailyGrid } from '../signals/data.js';
import { liveSpotPriceFor } from '../price/providers.js';
import { nowUtcIso } from '../../shared/time.js';
import { saleResult, feeResult } from '../../shared/wealth-metrics.js';
import { CONFIG } from '../../shared/config.js';
import { windowTwr } from './performance.js';
const DAG_MS = 24 * 60 * 60 * 1000;
export const dagVan = iso => String(iso).slice(0, 10);
const paar = (accountId, assetId) => `${accountId ?? 'geen'}|${assetId}`;
const accountVanSleutel = sleutel => {
const deel = String(sleutel).split('|')[0];
return deel === 'geen' ? null : Number(deel);
};
export function dagenTussen(vanDag, totDag) {
const start = Date.parse(`${vanDag}T00:00:00Z`);
const eind = Date.parse(`${totDag}T00:00:00Z`);
if (!Number.isFinite(start) || !Number.isFinite(eind) || eind < start) return [];
const uit = [];
for (let t = start; t <= eind; t += DAG_MS) uit.push(new Date(t).toISOString().slice(0, 10));
return uit;
}
export function deltasVan(e) {
const bedrag = Number(e.amount) || 0;
const fee = Number(e.unitFee) || 0;
switch (e.type) {
case 'buy':
case 'interest':
return [{ sleutel: paar(e.toAccountId, e.assetId), delta: bedrag }];
case 'sell':
return [{ sleutel: paar(e.fromAccountId, e.assetId), delta: -(bedrag + fee) }];
case 'transfer':
return [
...(e.fromAccountId != null ? [{ sleutel: paar(e.fromAccountId, e.assetId), delta: -(bedrag + fee) }] : []),
...(e.toAccountId != null ? [{ sleutel: paar(e.toAccountId, e.assetId), delta: bedrag }] : []),
];
default:
return [];
}
}
export function ownView(e, excluded) {
if (!excluded || !excluded.size) return e;
const outside = id => id != null && excluded.has(id);
if (e.type === 'transfer') {
const fromOwn = e.fromAccountId != null && !outside(e.fromAccountId);
const toOwn = e.toAccountId != null && !outside(e.toAccountId);
if (!fromOwn && !toOwn) return null;
if (fromOwn && outside(e.toAccountId)) return { ...e, toAccountId: null, crossing: -1 };
if (toOwn && outside(e.fromAccountId)) return { ...e, fromAccountId: null, crossing: 1 };
return e;
}
if (e.type === 'sell') return outside(e.fromAccountId) ? null : e;
return outside(e.toAccountId) ? null : e;
}
function accountsVan(boekingen, ankers) {
const uit = new Set();
for (const e of boekingen) for (const { sleutel } of deltasVan(e)) uit.add(accountVanSleutel(sleutel));
for (const v of ankers) uit.add(v.accountId);
return [...uit];
}
function accountFlows(e, waardeVan) {
const v = waardeVan(e.amount);
if (v == null) return [];
const van = e.fromAccountId ?? null;
const naar = e.toAccountId ?? null;
if (e.type === 'transfer') {
if (van != null && naar != null) return van === naar ? [] : [{ accountId: van, fiat: -v }, { accountId: naar, fiat: v }];
if (van == null && naar == null) return [];
return van == null ? [{ accountId: naar, fiat: v }] : [{ accountId: van, fiat: -v }];
}
const uit = [];
if (e.priceOrigin === 'none') uit.push({ accountId: e.type === 'sell' ? van : naar, fiat: e.type === 'buy' ? v : -v });
if (e.type === 'interest') uit.push({ accountId: naar, fiat: v, rente: v });
return uit;
}
const oplopend = perAsset => new Map([...perAsset].map(([id, set]) => [id, [...set].sort((a, b) => a - b)]));
function koersOp(grid, dag) {
if (!grid || !grid.length) return null;
const ts = Date.parse(`${dag}T00:00:00Z`);
if (ts < grid[0][0]) return null;
const i = Math.min(grid.length - 1, Math.floor((ts - grid[0][0]) / DAG_MS));
return grid[i][1];
}
export function dagreeks({ events = [], valuations = [], assets = [], prijzen = new Map(),
vanDag = null, totDag = null, excludedAccounts = null } = {}) {
const buiten = excludedAccounts && excludedAccounts.size ? excludedAccounts : null;
const ankers = valuations.filter(v => v.accountId != null && !(buiten && buiten.has(v.accountId)));
const boekingen = buiten ? events.map(e => ownView(e, buiten)).filter(Boolean) : events;
const eersteDag = vanDag ?? vroegsteDag(boekingen, ankers);
const laatsteDag = totDag ?? dagVan(nowUtcIso());
const dagen = eersteDag ? dagenTussen(eersteDag, laatsteDag) : [];
const leeg = { dagen: [], waarde: [], ingelegd: [], uitgehaald: [], perAsset: new Map(), inlegPerAsset: new Map(), uitPerAsset: new Map(), ankersPerAsset: new Map(), rentePerAsset: new Map(), gerealiseerdPerAsset: new Map(), onbekendPerAsset: new Map(), nietVerklaardPerAsset: new Map(),
koersPerAsset: new Map(), eenhedenPerAsset: new Map(),
aankopen: new Map(), verkopen: new Map(), eersteDagPerAsset: new Map(),
perAccount: new Map(), inlegPerAccount: new Map(), uitPerAccount: new Map(), ankersPerAccount: new Map(),
rentePerAccount: new Map(), nietVerklaardPerAccount: new Map(), bezitPerAccount: new Map(),
eersteDagPerAccount: new Map(), standNu: [] };
if (!dagen.length) return leeg;
const tijdlijn = [
...boekingen.map(e => ({ op: e.occurredAt, soort: 'boeking', e })),
...ankers.map(v => ({ op: v.rateAt, soort: 'anker', v })),
].sort((a, b) => String(a.op).localeCompare(String(b.op)));
const decimalen = new Map(assets.map(a => [a.id, Math.pow(10, a.decimals)]));
const stand = new Map();
const assetVanPaar = new Map();
let inleg = 0;
const waarde = [];
const ingelegd = [];
const perAsset = new Map(assets.map(a => [a.id, []]));
const koersPerAsset = new Map(assets.map(a => [a.id, []]));
const eenhedenPerAsset = new Map(assets.map(a => [a.id, []]));
const inlegPerAsset = new Map(assets.map(a => [a.id, []]));
const inlegNu = new Map(assets.map(a => [a.id, 0]));
let uit = 0;
const uitgehaald = [];
const uitPerAsset = new Map(assets.map(a => [a.id, []]));
const uitNu = new Map(assets.map(a => [a.id, 0]));
const ankersPerAsset = new Map(assets.map(a => [a.id, []]));
const ankerNu = new Map(assets.map(a => [a.id, 0]));
const rentePerAsset = new Map(assets.map(a => [a.id, []]));
const renteNu = new Map(assets.map(a => [a.id, 0]));
const gerealiseerdPerAsset = new Map(assets.map(a => [a.id, []]));
const gerealiseerdNu = new Map(assets.map(a => [a.id, 0]));
const onbekendPerAsset = new Map(assets.map(a => [a.id, []]));
const onbekendNu = new Map(assets.map(a => [a.id, 0]));
const kostprijsPerAsset = new Map(assets.map(a => [a.id, { buy_amount: 0, buy_spot_raw: 0, buy_fee_total: 0, gift_amount: 0 }]));
const eenhedenVan = assetId => {
let n = 0;
for (const [sleutel, eenheden] of stand) if (assetVanPaar.get(sleutel) === assetId) n += eenheden;
return n;
};
const nietVerklaardPerAsset = new Map(assets.map(a => [a.id, []]));
const nietVerklaardNu = new Map(assets.map(a => [a.id, 0]));
const geboekt = new Set();
const aankoopDagen = new Map(assets.map(a => [a.id, new Set()]));
const verkoopDagen = new Map(assets.map(a => [a.id, new Set()]));
const eersteDagPerAsset = new Map(assets.map(a => [a.id, null]));
const accounts = accountsVan(boekingen, ankers);
const perAccountReeks = () => new Map(accounts.map(id => [id, []]));
const perAccountNu = () => new Map(accounts.map(id => [id, 0]));
const acc = {
reeks: { waarde: perAccountReeks(), inleg: perAccountReeks(), uit: perAccountReeks(), ankers: perAccountReeks(),
rente: perAccountReeks(), nietVerklaard: perAccountReeks(), bezit: perAccountReeks() },
nu: { inleg: perAccountNu(), uit: perAccountNu(), ankers: perAccountNu(), rente: perAccountNu(), nietVerklaard: perAccountNu() },
};
const accBij = (soort, accountId, v) => { if (acc.nu[soort].has(accountId)) acc.nu[soort].set(accountId, acc.nu[soort].get(accountId) + v); };
const eersteDagPerAccount = new Map(accounts.map(id => [id, null]));
let i = 0;
for (const [dagNr, dag] of dagen.entries()) {
const grens = `${dag}T23:59:59.999Z`;
while (i < tijdlijn.length && String(tijdlijn[i].op) <= grens) {
const item = tijdlijn[i++];
if (item.soort === 'anker') {
const s = paar(item.v.accountId, item.v.assetId);
const nieuw = Number(item.v.balance) || 0;
const sprong = nieuw - (stand.get(s) || 0);
const koers = koersOp(prijzen.get(item.v.assetId), dag);
if (sprong !== 0 && koers != null && ankerNu.has(item.v.assetId)) {
const fiat = (sprong / (decimalen.get(item.v.assetId) || 1)) * koers;
ankerNu.set(item.v.assetId, ankerNu.get(item.v.assetId) + fiat);
const bevinding = geboekt.has(s) && !item.v.gapAcceptedAt;
if (bevinding) nietVerklaardNu.set(item.v.assetId, nietVerklaardNu.get(item.v.assetId) + fiat);
accBij('ankers', item.v.accountId, fiat);
if (bevinding) accBij('nietVerklaard', item.v.accountId, fiat);
}
stand.set(s, nieuw);
assetVanPaar.set(s, item.v.assetId);
continue;
}
const e = item.e;
const kp = kostprijsPerAsset.get(e.assetId);
if (kp && e.type === 'sell' && e.priceOrigin !== 'none') {
const decimals = assets.find(a => a.id === e.assetId)?.decimals ?? 0;
const r = saleResult({ decimals, net: eenhedenVan(e.assetId), ...kp }, e);
if (r == null) onbekendNu.set(e.assetId, onbekendNu.get(e.assetId) + 1);
else gerealiseerdNu.set(e.assetId, gerealiseerdNu.get(e.assetId) + r);
}
if (kp && e.type === 'transfer' && e.fromAccountId != null && Number(e.unitFee) > 0) {
const decimals = assets.find(a => a.id === e.assetId)?.decimals ?? 0;
const r = feeResult({ decimals, net: eenhedenVan(e.assetId), ...kp }, e.unitFee);
if (r == null) onbekendNu.set(e.assetId, onbekendNu.get(e.assetId) + 1);
else gerealiseerdNu.set(e.assetId, gerealiseerdNu.get(e.assetId) + r);
}
if (kp && e.type === 'transfer' && e.fromAccountId != null && Number(e.fiatFee) > 0) {
gerealiseerdNu.set(e.assetId, gerealiseerdNu.get(e.assetId) - Number(e.fiatFee));
}
if (kp) {
const n = Number(e.amount) || 0;
if (e.type === 'buy' && e.priceOrigin !== 'none') {
kp.buy_amount += n;
kp.buy_spot_raw += n * (Number(e.fiatPrice) || 0);
kp.buy_fee_total += Number(e.fiatFee) || 0;
}
if (e.priceOrigin === 'none') {
if (e.type === 'buy') kp.gift_amount += n;
else if (e.type === 'sell') kp.gift_amount -= n;
else if (e.type === 'transfer' && e.fromAccountId == null && e.toAccountId != null) kp.gift_amount += n;
else if (e.type === 'transfer' && e.toAccountId == null && e.fromAccountId != null) kp.gift_amount -= n;
}
}
for (const { sleutel, delta } of deltasVan(e)) {
stand.set(sleutel, (stand.get(sleutel) || 0) + delta);
assetVanPaar.set(sleutel, e.assetId);
geboekt.add(sleutel);
}
const bij = inlegVan(e, decimalen.get(e.assetId) || 1);
inleg += bij;
if (inlegNu.has(e.assetId)) inlegNu.set(e.assetId, inlegNu.get(e.assetId) + bij);
const eruit = uitVan(e, decimalen.get(e.assetId) || 1);
uit += eruit;
if (uitNu.has(e.assetId)) uitNu.set(e.assetId, uitNu.get(e.assetId) + eruit);
if (decimalen.has(e.assetId)) {
const kant = e.type === 'sell' || e.type === 'transfer' ? (e.fromAccountId ?? null) : (e.toAccountId ?? null);
accBij('inleg', kant, bij);
accBij('uit', kant, eruit);
const koers = koersOp(prijzen.get(e.assetId), dag);
const waardeVan = n => (koers == null ? null : ((Number(n) || 0) / decimalen.get(e.assetId)) * koers);
for (const f of accountFlows(e, waardeVan)) {
accBij('ankers', f.accountId, f.fiat);
if (f.rente) accBij('rente', f.accountId, f.rente);
}
}
const eenzijdig = e.type === 'transfer' && (e.fromAccountId == null) !== (e.toAccountId == null);
if (e.priceOrigin === 'none' && (e.type !== 'transfer' || eenzijdig) && ankerNu.has(e.assetId)) {
const koers = koersOp(prijzen.get(e.assetId), dag);
if (koers != null) {
const binnen = e.type === 'transfer' ? e.fromAccountId == null : e.type === 'buy';
const fiat = ((Number(e.amount) || 0) / (decimalen.get(e.assetId) || 1)) * koers;
ankerNu.set(e.assetId, ankerNu.get(e.assetId) + (binnen ? fiat : -fiat));
}
}
if (eenzijdig && e.priceOrigin !== 'none' && ankerNu.has(e.assetId)) {
const koers = koersOp(prijzen.get(e.assetId), dag);
if (koers != null) {
const fiat = ((Number(e.amount) || 0) / (decimalen.get(e.assetId) || 1)) * koers;
ankerNu.set(e.assetId, ankerNu.get(e.assetId) + (e.fromAccountId == null ? fiat : -fiat));
}
}
if (e.type === 'interest' && ankerNu.has(e.assetId)) {
const koers = koersOp(prijzen.get(e.assetId), dag);
if (koers != null) {
const fiat = ((Number(e.amount) || 0) / (decimalen.get(e.assetId) || 1)) * koers;
ankerNu.set(e.assetId, ankerNu.get(e.assetId) + fiat);
renteNu.set(e.assetId, renteNu.get(e.assetId) + fiat);
}
}
if (dagVan(e.occurredAt) === dag) {
if (e.type === 'buy') aankoopDagen.get(e.assetId)?.add(dagNr);
else if (e.type === 'sell') verkoopDagen.get(e.assetId)?.add(dagNr);
}
}
const eenhedenVandaag = new Map();
for (const [sleutel, eenheden] of stand) {
const assetId = assetVanPaar.get(sleutel);
eenhedenVandaag.set(assetId, (eenhedenVandaag.get(assetId) || 0) + eenheden);
}
let dagTotaal = 0;
for (const a of assets) {
const eenheden = eenhedenVandaag.get(a.id) || 0;
if (eenheden !== 0 && eersteDagPerAsset.get(a.id) === null) eersteDagPerAsset.set(a.id, dagNr);
const koers = koersOp(prijzen.get(a.id), dag);
const v = koers == null ? 0 : (eenheden / (decimalen.get(a.id) || 1)) * koers;
perAsset.get(a.id).push(v);
koersPerAsset.get(a.id).push(koers);
eenhedenPerAsset.get(a.id).push(eenheden);
inlegPerAsset.get(a.id).push(inlegNu.get(a.id) || 0);
uitPerAsset.get(a.id).push(uitNu.get(a.id) || 0);
ankersPerAsset.get(a.id).push(ankerNu.get(a.id) || 0);
rentePerAsset.get(a.id).push(renteNu.get(a.id) || 0);
gerealiseerdPerAsset.get(a.id).push(gerealiseerdNu.get(a.id) || 0);
onbekendPerAsset.get(a.id).push(onbekendNu.get(a.id) || 0);
nietVerklaardPerAsset.get(a.id).push(nietVerklaardNu.get(a.id) || 0);
dagTotaal += v;
}
waarde.push(dagTotaal);
ingelegd.push(inleg);
uitgehaald.push(uit);
const accWaarde = new Map(accounts.map(id => [id, 0]));
const accBezit = new Map(accounts.map(id => [id, 0]));
for (const [sleutel, eenheden] of stand) {
const assetId = assetVanPaar.get(sleutel);
const accountId = accountVanSleutel(sleutel);
if (!eenheden || !decimalen.has(assetId) || !accWaarde.has(accountId)) continue;
const koers = koersOp(prijzen.get(assetId), dag);
if (koers != null) accWaarde.set(accountId, accWaarde.get(accountId) + (eenheden / decimalen.get(assetId)) * koers);
accBezit.set(accountId, accBezit.get(accountId) + 1);
}
for (const id of accounts) {
if (accBezit.get(id) && eersteDagPerAccount.get(id) === null) eersteDagPerAccount.set(id, dagNr);
acc.reeks.waarde.get(id).push(accWaarde.get(id));
acc.reeks.bezit.get(id).push(accBezit.get(id));
for (const soort of Object.keys(acc.nu)) acc.reeks[soort].get(id).push(acc.nu[soort].get(id));
}
}
return {
dagen, waarde, ingelegd, uitgehaald, perAsset, inlegPerAsset, uitPerAsset, ankersPerAsset, rentePerAsset,
gerealiseerdPerAsset, onbekendPerAsset, nietVerklaardPerAsset,
koersPerAsset, eenhedenPerAsset, eersteDagPerAsset,
aankopen: oplopend(aankoopDagen),
verkopen: oplopend(verkoopDagen),
perAccount: acc.reeks.waarde, inlegPerAccount: acc.reeks.inleg, uitPerAccount: acc.reeks.uit,
ankersPerAccount: acc.reeks.ankers, rentePerAccount: acc.reeks.rente, nietVerklaardPerAccount: acc.reeks.nietVerklaard,
bezitPerAccount: acc.reeks.bezit, eersteDagPerAccount,
standNu: [...stand].filter(([s, n]) => n && decimalen.has(assetVanPaar.get(s)))
.map(([s, n]) => ({ accountId: accountVanSleutel(s), assetId: assetVanPaar.get(s), eenheden: n,
deler: decimalen.get(assetVanPaar.get(s)) })),
};
}
export function accountSeries(reeks = {}, accountId = null) {
return {
waarden: reeks.perAccount?.get(accountId) || [],
inleg: reeks.inlegPerAccount?.get(accountId) || [],
uit: reeks.uitPerAccount?.get(accountId) || [],
ankers: reeks.ankersPerAccount?.get(accountId) || [],
rente: reeks.rentePerAccount?.get(accountId) || [],
eenheden: reeks.bezitPerAccount?.get(accountId),
eersteDag: reeks.eersteDagPerAccount?.get(accountId) ?? 0,
};
}
export function inlegVan(e, deler) {
const kosten = Number(e.fiatFee) || 0;
if (e.type === 'buy') return ((Number(e.amount) || 0) / deler) * (Number(e.fiatPrice) || 0) + kosten;
if (e.type === 'sell') return -(((Number(e.amount) || 0) / deler) * (Number(e.fiatPrice) || 0)) + kosten;
if (e.type === 'transfer' && e.fromAccountId != null) return kosten;
return 0;
}
export function uitVan(e, deler) {
if (e.type !== 'sell') return 0;
return ((Number(e.amount) || 0) / deler) * (Number(e.fiatPrice) || 0) - (Number(e.fiatFee) || 0);
}
export function metSpotVandaag(reeks, assets = [], spots = new Map(), vandaag = dagVan(nowUtcIso())) {
const i = (reeks?.dagen?.length || 0) - 1;
if (i < 0 || reeks.dagen[i] !== vandaag) return reeks;
const decimalen = new Map(assets.map(a => [a.id, Math.pow(10, a.decimals)]));
let totaal = 0;
for (const [assetId, waarden] of reeks.perAsset) {
const spot = Number(spots.get(assetId));
const eenheden = reeks.eenhedenPerAsset?.get(assetId);
if (Number.isFinite(spot) && spot > 0 && eenheden && decimalen.has(assetId)) {
reeks.koersPerAsset.get(assetId)[i] = spot;
waarden[i] = (eenheden[i] / decimalen.get(assetId)) * spot;
}
totaal += waarden[i] || 0;
}
reeks.waarde[i] = totaal;
if (reeks.perAccount?.size) {
const perAccount = new Map([...reeks.perAccount.keys()].map(id => [id, 0]));
for (const { accountId, assetId, eenheden, deler } of (reeks.standNu || [])) {
const koers = reeks.koersPerAsset?.get(assetId)?.[i];
if (koers != null && deler && perAccount.has(accountId)) {
perAccount.set(accountId, perAccount.get(accountId) + (eenheden / deler) * koers);
}
}
for (const [id, waarde] of perAccount) reeks.perAccount.get(id)[i] = waarde;
}
return reeks;
}
export function jaarVensters(dagen = []) {
const uit = new Map();
dagen.forEach((dag, i) => {
const jaar = Number(String(dag).slice(0, 4));
if (!Number.isFinite(jaar)) return;
const v = uit.get(jaar);
if (v) v.tot = i;
else uit.set(jaar, { van: i, tot: i });
});
return uit;
}
export function meetVenster({ van, tot } = {}, lengte = Infinity) {
return { van, tot: Math.min((Number(tot) || 0) + 1, lengte - 1) };
}
export function windowDelta(r, { van = 0, tot = 0 } = {}) {
if (!Array.isArray(r) || !r.length) return 0;
const eind = Number(r[Math.min(tot, r.length - 1)]) || 0;
return eind - (van > 0 ? (Number(r[van]) || 0) : 0);
}
export function windowResult({ waarden = [], inleg = [], ankers = [], rente = [] } = {}, venster = {}) {
return windowDelta(waarden, venster) - windowDelta(inleg, venster)
- windowDelta(ankers, venster) + windowDelta(rente, venster);
}
export function dailyResults({ waarden = [], inleg = [], ankers = [], rente = [] } = {}) {
const step = (r, i) => (Number(r[i]) || 0) - (i > 0 ? (Number(r[i - 1]) || 0) : 0);
return waarden.map((_, i) => step(waarden, i) - step(inleg, i) - step(ankers, i) + step(rente, i));
}
export function todayResults(reeks = {}, vandaag = dagVan(nowUtcIso())) {
const i = (reeks.dagen?.length || 0) - 1;
if (i < 0 || reeks.dagen[i] !== vandaag) return null;
const last = input => dailyResults(input)[i];
const perAsset = new Map();
for (const id of (reeks.perAsset?.keys?.() || [])) perAsset.set(id, last(seriesFor(reeks, id)));
return { total: last(seriesFor(reeks)), perAsset };
}
export function seriesFor(reeks = {}, assetId = null) {
if (assetId == null) {
return {
waarden: reeks.waarde || [], inleg: reeks.ingelegd || [], uit: reeks.uitgehaald || [],
ankers: sumPerDay(reeks.ankersPerAsset), rente: sumPerDay(reeks.rentePerAsset), eersteDag: 0,
};
}
if (Array.isArray(assetId)) {
const only = map => new Map([...(map || new Map())].filter(([id]) => assetId.includes(id)));
const firsts = assetId.map(id => reeks.eersteDagPerAsset?.get(id)).filter(d => d != null);
return {
waarden: sumPerDay(only(reeks.perAsset)), inleg: sumPerDay(only(reeks.inlegPerAsset)),
uit: sumPerDay(only(reeks.uitPerAsset)), ankers: sumPerDay(only(reeks.ankersPerAsset)),
rente: sumPerDay(only(reeks.rentePerAsset)), eersteDag: firsts.length ? Math.min(...firsts) : 0,
};
}
return {
waarden: reeks.perAsset?.get(assetId) || [],
inleg: reeks.inlegPerAsset?.get(assetId) || [],
uit: reeks.uitPerAsset?.get(assetId) || [],
ankers: reeks.ankersPerAsset?.get(assetId) || [],
rente: reeks.rentePerAsset?.get(assetId) || [],
eenheden: reeks.eenhedenPerAsset?.get(assetId),
eersteDag: reeks.eersteDagPerAsset?.get(assetId) ?? 0,
};
}
function sumPerDay(perAsset) {
const out = [];
for (const r of (perAsset?.values?.() || [])) {
r.forEach((v, i) => { out[i] = (out[i] || 0) + v; });
}
return out;
}
export function rollingWindows(length = 0, days = CONFIG.WEALTH_RETURN.ROLLING_DAYS) {
const last = length - 1;
if (last < 0) return [];
return [
...days.map(n => ({ days: n, venster: last - n >= 1 ? { van: last - n, tot: last } : null })),
{ days: null, venster: { van: 0, tot: last } },
];
}
export function rollingReturns(input = {}, days = CONFIG.WEALTH_RETURN.ROLLING_DAYS) {
return rollingWindows((input.waarden || []).length, days).map(({ days: n, venster }) => ({
days: n,
venster,
amount: venster ? windowResult(input, venster) : null,
pct: venster ? windowTwr(input, venster) : null,
}));
}
export function vroegsteDag(events, ankers) {
const momenten = [...events.map(e => e.occurredAt), ...ankers.map(v => v.rateAt)]
.filter(Boolean).map(dagVan).sort();
return momenten.length ? momenten[0] : null;
}
export function managedCostPots({ events = [], prijzen = new Map(), managed = new Set(), totIso = null } = {}) {
const uit = new Map();
if (!managed || !managed.size) return uit;
const pot = (accountId, assetId) => {
if (accountId == null || !managed.has(accountId)) return null;
const sleutel = `${accountId}|${assetId}`;
if (!uit.has(sleutel)) uit.set(sleutel, { buy_amount: 0, buy_spot_raw: 0, buy_fee_total: 0, buy_events: 0, gift_amount: 0 });
return uit.get(sleutel);
};
const volgorde = events.filter(e => !totIso || String(e.occurredAt) <= totIso)
.sort((a, b) => String(a.occurredAt).localeCompare(String(b.occurredAt)));
for (const e of volgorde) {
const n = Number(e.amount) || 0;
const geen = e.priceOrigin === 'none';
if (e.type === 'buy') {
const p = pot(e.toAccountId, e.assetId);
if (!p) continue;
if (geen) { p.gift_amount += n; continue; }
p.buy_amount += n;
p.buy_spot_raw += n * (Number(e.fiatPrice) || 0);
p.buy_fee_total += Number(e.fiatFee) || 0;
p.buy_events += 1;
} else if (e.type === 'sell') {
const p = pot(e.fromAccountId, e.assetId);
if (p && geen) p.gift_amount -= n;
} else if (e.type === 'transfer') {
const van = e.fromAccountId ?? null;
const naar = e.toAccountId ?? null;
const p = pot(naar, e.assetId);
if (p && van != null && van !== naar) {
const koers = koersOp(prijzen.get(e.assetId), dagVan(e.occurredAt));
if (koers != null) { p.buy_amount += n; p.buy_spot_raw += n * koers; }
} else if (p && van == null && geen) {
p.gift_amount += n;
}
const q = naar == null ? pot(van, e.assetId) : null;
if (q && geen) q.gift_amount -= n;
}
}
return uit;
}
export async function dagreeksVanNu({ vanDag = null, totDag = null, excludedAccounts = null, managedAccounts = null } = {}) {
const [events, valuations, assets] = await Promise.all([listEvents(), listValuations(), listAssets()]);
const prijzen = new Map();
const rijenPerAsset = new Map();
for (const a of assets) {
let rijen = [];
try { rijen = await listPriceHistory(a.id); } catch { rijen = []; }
rijenPerAsset.set(a.id, rijen);
try {
prijzen.set(a.id, toDailyGrid(rijen));
} catch {
prijzen.set(a.id, []);
}
}
const spots = new Map();
for (const a of assets) {
try { const price = await liveSpotPriceFor(a); if (price != null) spots.set(a.id, price); }
catch { }
}
const reeks = metSpotVandaag(dagreeks({ events, valuations, assets, prijzen, vanDag, totDag, excludedAccounts }), assets, spots);
reeks.rijenPerAsset = rijenPerAsset;
if (managedAccounts && managedAccounts.size) {
reeks.beheerdeKostprijs = totIso => managedCostPots({ events, prijzen, managed: managedAccounts, totIso });
}
return reeks;
}