Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,164 @@
|
||||
// Generated, do not edit. sha256:a35de9757194e9058e50a8d62faae8adcfd1b35f42f9b9b14cece1c6cab04e85
|
||||
import { accountHoldings, positionsByAsset } from '../data/positions.js';
|
||||
import { listAccounts } from '../data/accounts.js';
|
||||
import { isOwnWealth } from '../../shared/wealth-scope.js';
|
||||
import { latestPricePoint } from '../data/price-history.js';
|
||||
import { assertScope } from '../ai/gate.js';
|
||||
import { limitedList } from '../ai/limits.js';
|
||||
import { shareBucket, shareLabel } from '../ai/buckets.js';
|
||||
import { shareDisclosure } from '../ai/precision.js';
|
||||
import { vertalerVoor } from './session.mjs';
|
||||
import { assetMetrics } from '../../shared/wealth-metrics.js';
|
||||
import { isMetal, metalFactor } from '../../shared/units.js';
|
||||
import { getBaseCurrency } from '../data/portfolio.js';
|
||||
|
||||
function verdelingsblok(rijen, totaal, t) {
|
||||
if (!(totaal > 0)) return [];
|
||||
|
||||
const vorm = shareDisclosure(t.precision);
|
||||
if (vorm === 'omit') {
|
||||
return [
|
||||
'De verdeling over de assets komt niet mee: met een exact totaal én exacte percentages ligt de '
|
||||
+ 'waarde per asset vast, en de bedragen per regel zijn juist wél in marges. Zet één van beide '
|
||||
+ 'terug op marges (of zet onbewerkt delen aan) om hem te zien.',
|
||||
'',
|
||||
];
|
||||
}
|
||||
const pctUit = pct => (vorm === 'exact' ? `${pct.toFixed(1)}%` : shareLabel(pct));
|
||||
|
||||
const posten = [];
|
||||
let overigPct = 0;
|
||||
let overigN = 0;
|
||||
let zonderKoers = 0;
|
||||
|
||||
for (const { p, m } of rijen) {
|
||||
if (m.value == null) { zonderKoers += 1; continue; }
|
||||
const pct = (m.value / totaal) * 100;
|
||||
const b = shareBucket(pct);
|
||||
if (!b || b.below) { overigPct += pct; overigN += 1; continue; }
|
||||
posten.push({ symbool: t.toAi(p, { entity: 'asset' }).symbol, pct });
|
||||
}
|
||||
if (!posten.length && !overigN) return [];
|
||||
|
||||
posten.sort((a, b) => b.pct - a.pct);
|
||||
const regels = limitedList(posten, x => `- ${x.symbool}: ${pctUit(x.pct)}`).lines;
|
||||
if (overigN) {
|
||||
regels.push(`- overig (${overigN} ${overigN === 1 ? 'asset' : 'assets'}): ${pctUit(overigPct)}`);
|
||||
}
|
||||
|
||||
return [
|
||||
vorm === 'exact'
|
||||
? 'Verdeling over de portefeuille (samen dekken ze het geheel):'
|
||||
: 'Verdeling over de portefeuille (marges; samen dekken ze het geheel):',
|
||||
...regels,
|
||||
...(zonderKoers ? [`(${zonderKoers} zonder koers, dus niet in deze verdeling.)`] : []),
|
||||
'',
|
||||
];
|
||||
}
|
||||
|
||||
function buitenTotalen(decimalen) {
|
||||
const buiten = listAccounts().filter(a => !isOwnWealth(a));
|
||||
if (!buiten.length) return [];
|
||||
const soort = new Map(buiten.map(a => [a.id, a.wealthScope]));
|
||||
const som = new Map([...new Set(buiten.map(a => a.wealthScope))].map(s => [s, { waarde: 0, zonderKoers: 0 }]));
|
||||
for (const r of accountHoldings(buiten.map(a => a.id))) {
|
||||
const punt = latestPricePoint(r.assetId);
|
||||
const post = som.get(soort.get(r.accountId));
|
||||
if (!punt || !decimalen.has(r.assetId)) { post.zonderKoers += 1; continue; }
|
||||
post.waarde += (r.saldo / Math.pow(10, decimalen.get(r.assetId))) * punt.fiatPrice;
|
||||
}
|
||||
return ['managed', 'none'].filter(s => som.has(s)).map(s => ({ soort: s, ...som.get(s) }));
|
||||
}
|
||||
const BUITEN_LABEL = { managed: 'vermogen van derden', none: 'telt niet mee' };
|
||||
|
||||
const wealthSummary = Object.freeze({
|
||||
name: 'wealth_summary',
|
||||
title: 'Vermogen in cijfers',
|
||||
description:
|
||||
'Geeft wat de portefeuille van AssetApp nu waard is: het totaal, en per asset de hoeveelheid, '
|
||||
+ 'koers, waarde, inleg en het ongerealiseerde resultaat. Staat onbewerkt delen uit, dan komen '
|
||||
+ 'het totaal en de verdeling per asset terug als marges, zonder bedrag per asset. De koers is '
|
||||
+ 'het laatst opgeslagen punt en geen verse marktkoers. Wijzigt niets.',
|
||||
inputSchema: Object.freeze({ type: 'object', properties: {}, additionalProperties: false }),
|
||||
scope: 'read',
|
||||
|
||||
async run(_args, context) {
|
||||
assertScope(wealthSummary.scope);
|
||||
const t = await vertalerVoor(context);
|
||||
t.beginCall();
|
||||
|
||||
const onbewerkt = t.precision.rawValues;
|
||||
const posities = positionsByAsset(undefined, { ownOnly: true });
|
||||
const cur = getBaseCurrency();
|
||||
const money = n => `${cur} ${n.toFixed(2)}`;
|
||||
const totaalTekst = n => {
|
||||
const uit = t.toAi({ fiatValue: n }, { entity: 'wealth', role: 'total' }).fiatValue;
|
||||
return typeof uit === 'number' ? money(uit) : `${cur} ${uit}`;
|
||||
};
|
||||
const buiten = buitenTotalen(new Map(posities.map(p => [p.id, p.decimals])));
|
||||
const buitenRegel = !buiten.length ? [] : [
|
||||
`Niet in dit totaal (Vermogen op het account): ${buiten.map(b => `${BUITEN_LABEL[b.soort]} ${totaalTekst(b.waarde)}`
|
||||
+ (b.zonderKoers ? ` (${b.zonderKoers} zonder koers)` : '')).join('; ')}.`,
|
||||
'',
|
||||
];
|
||||
const buitenVelden = buiten.length ? ['wealthScope'] : [];
|
||||
|
||||
let totaal = 0;
|
||||
let zonderKoers = 0;
|
||||
const rijen = [];
|
||||
for (const p of posities) {
|
||||
if (p.net === 0 && p.buy_events === 0) continue;
|
||||
const punt = latestPricePoint(p.id);
|
||||
const m = assetMetrics(p, punt ? punt.fiatPrice : null);
|
||||
if (m.value != null) totaal += m.value; else zonderKoers += 1;
|
||||
rijen.push({ p, m, punt });
|
||||
}
|
||||
|
||||
if (!rijen.length) {
|
||||
if (buiten.length) {
|
||||
return { text: ['Deze portefeuille heeft geen eigen bezit.', '', ...buitenRegel].join('\n').trim(),
|
||||
fieldsShared: ['baseCurrency', 'fiatValue', ...buitenVelden] };
|
||||
}
|
||||
return { text: 'Deze portefeuille heeft nog geen bezit: er is niets om een waarde van te geven.',
|
||||
fieldsShared: [] };
|
||||
}
|
||||
|
||||
const kop = `Totale waarde nu: ${totaalTekst(totaal)}`
|
||||
+ ` over ${rijen.length} ${rijen.length === 1 ? 'asset' : 'assets'}`
|
||||
+ (zonderKoers ? `, waarvan ${zonderKoers} zonder koers en dus zonder waarde` : '')
|
||||
+ '.';
|
||||
|
||||
if (!onbewerkt) {
|
||||
return {
|
||||
text: [kop, '', ...buitenRegel, ...verdelingsblok(rijen, totaal, t),
|
||||
'Per asset komen de BEDRAGEN niet mee zolang onbewerkt delen uit staat: een waarde per asset '
|
||||
+ 'met de koers erbij legt de hoeveelheid vast. Zet die schuif aan voor een portefeuille '
|
||||
+ 'zonder echte gegevens.',
|
||||
].join('\n'),
|
||||
fieldsShared: ['baseCurrency', 'fiatValue', 'symbol', ...buitenVelden],
|
||||
};
|
||||
}
|
||||
|
||||
const regels = limitedList(rijen, ({ p, m, punt }) => {
|
||||
const v = t.toAi(p, { entity: 'asset' });
|
||||
const f = isMetal(p) ? metalFactor(p.unit) : 1;
|
||||
const stuk = [`${v.symbol}: ${+(m.units / f).toFixed(8)} ${v.unit}`];
|
||||
if (m.value != null) stuk.push(`waarde ${money(m.value)} tegen ${money(punt.fiatPrice * f)} per ${v.unit}`);
|
||||
else stuk.push('geen koers, dus geen waarde');
|
||||
stuk.push(`ingelegd ${money(m.invested)}`);
|
||||
if (m.unrealized != null) stuk.push(`ongerealiseerd ${money(m.unrealized)}`);
|
||||
if (m.avgPrice != null) stuk.push(`gemiddelde aankoopkoers ${money(m.avgPrice * f)}`);
|
||||
return `- ${stuk.join(', ')}`;
|
||||
}).lines;
|
||||
|
||||
return {
|
||||
text: [kop, '', ...buitenRegel, 'Per asset:', ...regels, '',
|
||||
'De koers is het laatst opgeslagen koerspunt; dit proces haalt geen verse koersen op.',
|
||||
].join('\n'),
|
||||
fieldsShared: ['baseCurrency', 'symbol', 'unit', 'decimals', 'amount', 'balance', 'fiatPrice', 'fiatFee',
|
||||
'fiatValue', ...buitenVelden],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const WEALTH_TOOLS = Object.freeze([wealthSummary]);
|
||||
Reference in new issue
Block a user