Files
AtreidesAssetsFree/tests/wealth/return-view.test.mjs
T

147 lines
6.5 KiB
JavaScript
Raw Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:3b34cf97cc5aa20ba08ae8a6246f0235b943e5e85fc0d31e21707e06b2c2c371
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { periodChoices, startPeriod, returnLine, idsForSelection, contributorsPerDay } from '../../client/wealth/return-view.js';
import { dagreeks, seriesFor, meetVenster } from '../../client/wealth/series.js';
import { windowTwr } from '../../client/wealth/performance.js';
const rond = x => (x == null ? x : Math.round(x * 1e6) / 1e6);
const grid = (vanafDag, koersen) => {
const start = Date.parse(`${vanafDag}T00:00:00Z`);
return koersen.map((k, i) => [start + i * 86400000, k]);
};
const ev = o => ({ type: 'buy', amount: 0, fiatPrice: null, fiatFee: null, unitFee: null,
fromAccountId: null, toAccountId: 1, ...o });
const A = { id: 1, decimals: 8, kind: 'crypto' };
const B = { id: 3, decimals: 8, kind: 'metal' };
const reeks = () => dagreeks({
assets: [A, B],
prijzen: new Map([
[A.id, grid('2026-01-01', [100, 110, 99, 120, 130, 125])],
[B.id, grid('2026-01-01', [50, 50, 55, 60, 54, 57])],
]),
vanDag: '2026-01-01', totDag: '2026-01-06',
events: [
ev({ assetId: A.id, amount: 1e8, fiatPrice: 100, occurredAt: '2026-01-01T10:00:00Z' }),
ev({ assetId: B.id, amount: 2e8, fiatPrice: 55, occurredAt: '2026-01-03T10:00:00Z' }),
],
valuations: [],
});
describe('periodChoices', () => {
it('zet de periode van de pagina vooraan, gemeten zoals de jaarkeuze meet', () => {
const p = periodChoices(400, { label: '2025', venster: { van: 10, tot: 50 } });
assert.deepEqual(p[0], { key: 'page', days: null, label: '2025', venster: meetVenster({ van: 10, tot: 50 }, 400) });
assert.deepEqual(p.slice(1).map(x => x.key), ['d1', 'd7', 'd30', 'd365', 'start']);
});
it('laat de pagina weg als die de hele reeks beslaat: dat is "sinds begin"', () => {
assert.deepEqual(periodChoices(400, { label: 'x', venster: { van: 0, tot: 399 } }).map(x => x.key),
['d1', 'd7', 'd30', 'd365', 'start']);
});
it('geeft zonder reeks niets', () => {
assert.deepEqual(periodChoices(0, { label: '2025', venster: { van: 0, tot: 0 } }), []);
});
});
describe('startPeriod', () => {
it('opent op de bewaarde periode als die bestaat en te meten is', () => {
assert.equal(startPeriod(periodChoices(400), 'd30'), 'd30');
});
it('valt terug op "sinds begin" als de reeks te kort is voor de bewaarde periode', () => {
const kort = periodChoices(10);
assert.equal(kort.find(p => p.key === 'd365')?.venster ?? null, null, 'voorwaarde: een jaar is niet te meten');
assert.equal(startPeriod(kort, 'd365'), 'start');
});
it('valt terug op de periode van de pagina bij een onbekende of lege keuze', () => {
const metPagina = periodChoices(400, { label: '2025', venster: { van: 10, tot: 50 } });
assert.equal(startPeriod(metPagina, 'weg'), 'page');
assert.equal(startPeriod(metPagina, null), 'page');
assert.equal(startPeriod(periodChoices(400), null), 'start');
});
});
describe('returnLine', () => {
const invoer = () => seriesFor(reeks());
it('eindigt op het bedrag erboven, in elk venster', () => {
for (const venster of [{ van: 0, tot: 5 }, { van: 2, tot: 5 }, { van: 4, tot: 5 }]) {
const lijn = returnLine(invoer(), venster);
assert.equal(rond(lijn.points.at(-1).cumulative), rond(lijn.amount), `venster ${venster.van} t/m ${venster.tot}`);
}
});
it('begint op nul na het begin van de reeks, en op de aankoopdag vanaf dag nul', () => {
const later = returnLine(invoer(), { van: 2, tot: 5 });
assert.deepEqual(later.points[0], { i: 2, cumulative: 0, day: null, pct: null });
const vanaf = returnLine(invoer(), { van: 0, tot: 5 });
assert.equal(vanaf.points[0].i, 0);
assert.equal(rond(vanaf.points[0].day), 0, 'gekocht tegen de slotkoers van die dag');
});
it('geeft per dag het rendement van die dag', () => {
const lijn = returnLine(invoer(), { van: 3, tot: 5 });
assert.equal(rond(lijn.points[1].pct), rond(238 / 240 - 1));
assert.equal(rond(lijn.points[1].day), -2);
});
it('heeft zonder venster geen lijn en geen getallen', () => {
assert.deepEqual(returnLine(invoer(), null), { amount: null, pct: null, points: [] });
});
});
describe('seriesFor met een groep', () => {
it('is met alle assets erin hetzelfde als het totaal', () => {
const r = reeks();
const groep = seriesFor(r, [A.id, B.id]);
assert.deepEqual(groep.waarden.map(rond), r.waarde.map(rond));
assert.equal(rond(windowTwr(groep, { van: 0, tot: 5 })), rond(windowTwr(seriesFor(r), { van: 0, tot: 5 })));
});
it('telt alleen de assets van de groep, vanaf de eerste dag van een van hen', () => {
const r = reeks();
const groep = seriesFor(r, [B.id]);
assert.deepEqual(groep.waarden.map(rond), r.perAsset.get(B.id).map(rond));
assert.equal(groep.eersteDag, r.eersteDagPerAsset.get(B.id));
assert.equal(groep.eenheden, undefined, 'eenheden van verschillende assets tellen niet op');
});
});
describe('contributorsPerDay', () => {
it('telt per dag op tot het resultaat van de lijn, grootste bedrag eerst', () => {
const r = reeks();
const per = contributorsPerDay(r, [A.id, B.id], { van: 0, tot: 5 });
assert.deepEqual(per.get(4).map(c => [c.id, rond(c.day), rond(c.pct)]),
[[B.id, -12, rond(54 / 60 - 1)], [A.id, 10, rond(130 / 120 - 1)]]);
const lijn = returnLine(seriesFor(r), { van: 0, tot: 5 });
for (const p of lijn.points.filter(x => per.has(x.i))) {
assert.equal(rond(per.get(p.i).reduce((s, c) => s + c.day, 0)), rond(p.day), `dag ${p.i}`);
}
});
it('houdt het bij n per dag, en laat een asset zonder beweging weg', () => {
const per = contributorsPerDay(reeks(), [A.id, B.id], { van: 0, tot: 5 }, 1);
assert.deepEqual(per.get(4).map(c => c.id), [B.id]);
assert.deepEqual(contributorsPerDay(reeks(), [A.id, B.id], { van: 0, tot: 5 }).get(1).map(c => c.id), [A.id]);
});
it('is leeg voor één asset: dat is de lijn zelf', () => {
assert.equal(contributorsPerDay(reeks(), [A.id], { van: 0, tot: 5 }).size, 0);
});
});
describe('idsForSelection', () => {
const assets = [A, B, { id: 4, kind: 'crypto' }];
it('volgt de selectie van de pagina', () => {
assert.deepEqual(idsForSelection({ assetIds: [3] }, assets), [3]);
assert.deepEqual(idsForSelection({ kind: 'crypto' }, assets), [1, 4]);
});
it('valt terug op het hele vermogen', () => {
assert.equal(idsForSelection(null, assets), null);
assert.equal(idsForSelection({ kind: 'fiat' }, assets), null, 'een soort zonder assets');
});
});