555 lines
25 KiB
JavaScript
555 lines
25 KiB
JavaScript
// 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;
|
|
}
|