// 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; }