Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+202
View File
@@ -0,0 +1,202 @@
// Generated, do not edit. sha256:78fe0c105c60d329e375c6de82599c303dbce923361c391f4536e4a44fb6d65d
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { assetMetrics, saleResult, feeResult, combinePositions } from '../../shared/wealth-metrics.js';
const positie = ({ decimals = 8, net, buys = [], fee = 0, gift = 0 }) => ({
decimals,
net,
buy_events: buys.length,
buy_amount: buys.reduce((s, b) => s + b.amount, 0),
buy_spot_raw: buys.reduce((s, b) => s + b.amount * b.price, 0),
buy_fee_total: fee,
gift_amount: gift,
});
describe('assetMetrics: de waarde', () => {
it('rekent de kleinste eenheid om naar hele eenheden en dan naar euro', () => {
const m = assetMetrics(positie({ net: 10e8, buys: [{ amount: 10e8, price: 100 }] }), 110);
assert.equal(m.units, 10);
assert.equal(m.value, 1100);
});
it('geeft géén waarde zonder koers, en geen nul', () => {
const m = assetMetrics(positie({ net: 3e8, buys: [{ amount: 3e8, price: 25 }] }), null);
assert.equal(m.value, null);
assert.equal(m.unrealized, null);
assert.equal(m.invested, 75, 'de inleg komt uit de boekingen en heeft geen koers van nu nodig');
assert.equal(m.avgPrice, 25);
});
it('laat de divisors tegen elkaar wegvallen in de gemiddelde aankoopkoers', () => {
const m = assetMetrics(positie({
net: 2e8, buys: [{ amount: 1e8, price: 100 }, { amount: 1e8, price: 120 }],
}), 110);
assert.equal(m.avgPrice, 110);
assert.equal(m.invested, 220);
});
});
describe('assetMetrics: ongerealiseerd tegenover ingelegd', () => {
it('meet ongerealiseerd tegen de gemiddelde aankoopkoers en niet tegen de inleg', () => {
const m = assetMetrics(positie({ net: 60e8, buys: [{ amount: 100e8, price: 10 }] }), 10);
assert.equal(m.unrealized, 0);
assert.equal(m.invested, 1000);
assert.equal(m.value, 600);
});
it('telt de kosten bij de kostprijs en niet bij de koers', () => {
const m = assetMetrics(positie({ net: 10e8, buys: [{ amount: 10e8, price: 10 }], fee: 5 }), 10);
assert.equal(m.invested, 105);
assert.equal(m.avgPrice, 10);
assert.equal(m.avgCost, 10.5);
assert.equal(m.unrealized, -5);
assert.equal(m.avgPremium, 5, 'de premie is per gebeurtenis');
});
it('rekent de LINK-aankoop van de gebruiker zoals de afschriften', () => {
const m = assetMetrics(positie({ net: 5e8, buys: [{ amount: 5e8, price: 19.968 }], fee: 0.15 }), 12.65);
assert.equal(Math.round(m.unrealized * 100) / 100, -36.74);
assert.equal(m.avgPrice, 19.968, 'de getoonde aankoopkoers blijft zonder kosten');
});
it('verdeelt de kosten naar rato over wat er na een verkoop over is', () => {
const m = assetMetrics(positie({ net: 60e8, buys: [{ amount: 100e8, price: 10 }], fee: 20 }), 10);
assert.equal(Math.round(m.unrealized * 100) / 100, -12);
});
it('geeft geen gemiddelden zonder aankopen', () => {
const m = assetMetrics(positie({ net: 5e8 }), 20);
assert.equal(m.avgPrice, null);
assert.equal(m.avgPremium, null);
assert.equal(m.unrealized, null, 'zonder aankoopkoers is er niets om tegen af te meten');
assert.equal(m.value, 100, 'de waarde bestaat wél: die heeft alleen de stand en de koers nodig');
});
});
describe('saleResult', () => {
it('trekt de kostprijs inclusief kosten af van de netto opbrengst', () => {
const r = saleResult(positie({ net: 10e8, buys: [{ amount: 10e8, price: 10 }], fee: 5 }),
{ amount: 4e8, fiatPrice: 12, fiatFee: 1 });
assert.equal(Math.round(r * 100) / 100, 5);
});
it('sluit aan op ongerealiseerd: samen is het waarde plus opbrengst min betaald', () => {
const voor = positie({ net: 10e8, buys: [{ amount: 10e8, price: 10 }], fee: 5 });
const r = saleResult(voor, { amount: 4e8, fiatPrice: 12, fiatFee: 1 });
const na = assetMetrics({ ...voor, net: 6e8 }, 12);
assert.equal(Math.round((r + na.unrealized) * 100) / 100, 72 + 47 - 105);
});
it('geeft null als het verkochte geen bekende kostprijs heeft', () => {
assert.equal(saleResult(positie({ net: 5e8 }), { amount: 5e8, fiatPrice: 20, fiatFee: 0 }), null);
});
it('telt de fee in eenheden mee als verkocht, niet als opbrengst', () => {
const voor = positie({ net: 10e8, buys: [{ amount: 10e8, price: 10 }] });
const r = saleResult(voor, { amount: 4e8, fiatPrice: 10, fiatFee: 0, unitFee: 1e8 });
assert.equal(r, -10);
const na = assetMetrics({ ...voor, net: 5e8 }, 12);
assert.equal(r + na.unrealized, 60 + 40 - 100);
});
});
describe('feeResult', () => {
it('rekent de fee-eenheden tegen de gemiddelde kostprijs als verlies', () => {
const r = feeResult(positie({ net: 10e8, buys: [{ amount: 10e8, price: 10 }], fee: 5 }), 1e8);
assert.equal(r, -10.5);
});
it('geeft null als de fee-eenheden geen bekende kostprijs hebben', () => {
assert.equal(feeResult(positie({ net: 5e8 }), 1e8), null);
});
it('kost niets als de fee uit een gift komt', () => {
assert.equal(feeResult(positie({ net: 5e8, gift: 5e8 }), 1e8), 0);
});
});
describe('assetMetrics: metaal rekent in grammen en niet in ounces', () => {
it('houdt de eenheid van het asset aan, wat die ook is', () => {
const m = assetMetrics(positie({
decimals: 4, net: 300e4,
buys: [{ amount: 200e4, price: 50 }, { amount: 100e4, price: 50 }],
}), 63);
assert.equal(m.units, 300);
assert.equal(m.value, 18900);
assert.equal(m.invested, 15000);
assert.equal(m.unrealized, 3900);
});
it('telt de premie bij de kostprijs, in grammen', () => {
const OZT = 31.1034768;
const m = assetMetrics(positie({
decimals: 4, net: OZT * 1e4, buys: [{ amount: OZT * 1e4, price: 1000 / OZT }], fee: 25,
}), 1025 / OZT);
assert.equal(Math.round(m.unrealized * 100) / 100, 0);
});
});
describe('assetMetrics: een gift draagt kostprijs nul', () => {
it('meet alleen de betaalde eenheden tegen de gemiddelde aankoopkoers', () => {
const m = assetMetrics(positie({ net: 150e8, buys: [{ amount: 100e8, price: 10 }], gift: 50e8 }), 20);
assert.equal(m.value, 3000);
assert.equal(m.unrealized, 2000);
assert.equal(m.avgPrice, 10, 'de getoonde aankoopkoers blijft van de aankopen');
assert.equal(m.invested, 1000, 'en de inleg ook: aan een gift is niets uitgegeven');
});
it('geeft de hele waarde als er alleen een gift is', () => {
const m = assetMetrics(positie({ net: 50e8, gift: 50e8 }), 20);
assert.equal(m.avgPrice, null, 'er is nog steeds geen aankoopkoers om te tonen');
assert.equal(m.value, 1000);
assert.equal(m.unrealized, 1000);
});
it('houdt een positie zonder gift op een streepje als er niets gekocht is', () => {
const m = assetMetrics(positie({ net: 5e8 }), 20);
assert.equal(m.unrealized, null);
});
it('laat een weggegeven gift tegen de ontvangen gift wegvallen', () => {
const m = assetMetrics(positie({ net: 100e8, buys: [{ amount: 100e8, price: 10 }], gift: 0 }), 20);
assert.equal(m.unrealized, 1000);
});
it('telt bij verkoop eerst de betaalde eenheden, dan de gift tegen nul', () => {
const r = saleResult(positie({ net: 150e8, buys: [{ amount: 100e8, price: 10 }], gift: 50e8 }),
{ amount: 120e8, fiatPrice: 20, fiatFee: 0 });
assert.equal(r, 1400);
});
it('gaat niet onder nul als de positie kleiner is dan de gift', () => {
const m = assetMetrics(positie({ net: 10e8, buys: [{ amount: 100e8, price: 10 }], gift: 50e8 }), 20);
assert.equal(m.unrealized, 200, 'de hele reststand is gift, dus kostprijs nul');
});
});
describe('combinePositions', () => {
it('weegt het gemiddelde naar wat er gekocht is, niet per account', () => {
const groot = positie({ net: 3e8, buys: [{ amount: 3e8, price: 100 }], fee: 6 });
const klein = positie({ net: 1e8, buys: [{ amount: 1e8, price: 200 }], fee: 2 });
const m = assetMetrics(combinePositions([groot, klein]), 150);
assert.equal(m.units, 4);
assert.equal(m.avgPrice, 125, '(3 × 100 + 1 × 200) / 4, en niet (100 + 200) / 2');
assert.equal(m.avgPremium, 4, 'de kosten per aankoop: 8 over twee aankopen');
assert.equal(m.invested, 508);
assert.equal(m.unrealized, 600 - 508);
});
it('telt een gift mee tegen kostprijs nul, zoals bij één account', () => {
const gekocht = positie({ net: 1e8, buys: [{ amount: 1e8, price: 100 }] });
const gekregen = positie({ net: 1e8, gift: 1e8 });
assert.equal(assetMetrics(combinePositions([gekocht, gekregen]), 150).unrealized, 200);
});
it('geeft bij geen enkele positie een lege positie en geen fout', () => {
const m = assetMetrics(combinePositions([]), 150);
assert.equal(m.units, 0);
assert.equal(m.avgPrice, null);
});
});