@fuzzle/opencode-accountant 0.13.16 → 0.13.17-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +163 -65
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25306,31 +25306,41 @@ function generateSplitEntry(action, oldQuantity, newQuantity, totalCostBasis, cu
|
|
|
25306
25306
|
logger?.debug(`Generating ${splitType} entry: ${oldQuantity} -> ${newQuantity} ${action.symbol}`);
|
|
25307
25307
|
const commodity = formatCommodity(action.symbol);
|
|
25308
25308
|
const totalCost = formatAmount2(totalCostBasis, currency);
|
|
25309
|
-
const
|
|
25309
|
+
const conversionAccount = `equity:conversion:${currency.toLowerCase()}`;
|
|
25310
|
+
const outgoingPostings = [
|
|
25310
25311
|
{
|
|
25311
25312
|
account: `assets:investments:stocks:${action.symbol}`,
|
|
25312
25313
|
amount: `-${formatQuantity(oldQuantity)} ${commodity} @@ ${totalCost}`
|
|
25313
25314
|
},
|
|
25314
25315
|
{
|
|
25315
|
-
account:
|
|
25316
|
-
amount:
|
|
25317
|
-
}
|
|
25316
|
+
account: conversionAccount,
|
|
25317
|
+
amount: totalCost
|
|
25318
|
+
}
|
|
25319
|
+
];
|
|
25320
|
+
const incomingPostings = [
|
|
25318
25321
|
{
|
|
25319
|
-
account:
|
|
25320
|
-
amount:
|
|
25322
|
+
account: conversionAccount,
|
|
25323
|
+
amount: formatAmount2(-totalCostBasis, currency)
|
|
25321
25324
|
},
|
|
25322
25325
|
{
|
|
25323
25326
|
account: `assets:investments:stocks:${action.symbol}`,
|
|
25324
25327
|
amount: `${formatQuantity(newQuantity)} ${commodity} @@ ${totalCost}`
|
|
25325
25328
|
}
|
|
25326
25329
|
];
|
|
25330
|
+
const metadata = ` ; swissquote:order:${action.orderNum} isin:${action.isin}
|
|
25331
|
+
` + ` ; Ratio: ${ratio.toFixed(4)} (${oldQuantity} -> ${newQuantity})
|
|
25332
|
+
`;
|
|
25327
25333
|
let entry = `${date5} ${description}
|
|
25328
25334
|
`;
|
|
25329
|
-
entry +=
|
|
25335
|
+
entry += metadata;
|
|
25336
|
+
entry += formatPostings(outgoingPostings) + `
|
|
25330
25337
|
`;
|
|
25331
|
-
entry += `
|
|
25338
|
+
entry += `
|
|
25332
25339
|
`;
|
|
25333
|
-
entry +=
|
|
25340
|
+
entry += `${date5} ${description}
|
|
25341
|
+
`;
|
|
25342
|
+
entry += metadata;
|
|
25343
|
+
entry += formatPostings(incomingPostings) + `
|
|
25334
25344
|
`;
|
|
25335
25345
|
return entry;
|
|
25336
25346
|
}
|
|
@@ -25371,43 +25381,101 @@ function generateMultiWayMergerEntry(group, crossCurrencyOutgoingSymbols, crossC
|
|
|
25371
25381
|
logger?.debug(`Generating multi-way merger entry: ${outSymbols.join(", ")} -> ${inSymbols.join(", ")}`);
|
|
25372
25382
|
const oldIsins = (crossCurrencyOutgoingIsins ?? group.outgoing.map((a) => a.isin)).filter(Boolean);
|
|
25373
25383
|
const newIsins = group.incoming.map((a) => a.isin).filter(Boolean);
|
|
25374
|
-
|
|
25384
|
+
let metadata = ` ; swissquote:order:${group.orderNum}
|
|
25385
|
+
`;
|
|
25386
|
+
if (oldIsins.length > 0 || newIsins.length > 0) {
|
|
25387
|
+
metadata += ` ; Old ISIN: ${oldIsins.join(", ") || "n/a"}, New ISINs: ${newIsins.join(", ") || "n/a"}
|
|
25388
|
+
`;
|
|
25389
|
+
}
|
|
25390
|
+
const entries = [];
|
|
25375
25391
|
for (let i2 = 0;i2 < group.outgoing.length; i2++) {
|
|
25376
25392
|
const out = group.outgoing[i2];
|
|
25377
25393
|
const qty = formatQuantity(Math.abs(out.quantity));
|
|
25378
25394
|
const commodity = formatCommodity(out.symbol);
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25395
|
+
if (costInfo && costInfo.outgoingTotalCosts[i2] != null) {
|
|
25396
|
+
const costAmount = formatAmount2(costInfo.outgoingTotalCosts[i2], costInfo.currency);
|
|
25397
|
+
const postings = [
|
|
25398
|
+
{
|
|
25399
|
+
account: `assets:investments:stocks:${out.symbol}`,
|
|
25400
|
+
amount: `-${qty} ${commodity} @@ ${costAmount}`
|
|
25401
|
+
},
|
|
25402
|
+
{
|
|
25403
|
+
account: `equity:conversion:${costInfo.currency.toLowerCase()}`,
|
|
25404
|
+
amount: costAmount
|
|
25405
|
+
}
|
|
25406
|
+
];
|
|
25407
|
+
let sub = `${date5} ${description}
|
|
25408
|
+
`;
|
|
25409
|
+
sub += metadata;
|
|
25410
|
+
sub += formatPostings(postings) + `
|
|
25411
|
+
`;
|
|
25412
|
+
entries.push(sub);
|
|
25413
|
+
} else {
|
|
25414
|
+
const postings = [
|
|
25415
|
+
{
|
|
25416
|
+
account: `assets:investments:stocks:${out.symbol}`,
|
|
25417
|
+
amount: `-${qty} ${commodity}`
|
|
25418
|
+
}
|
|
25419
|
+
];
|
|
25420
|
+
let sub = `${date5} ${description}
|
|
25421
|
+
`;
|
|
25422
|
+
sub += metadata;
|
|
25423
|
+
sub += formatPostings(postings) + `
|
|
25424
|
+
`;
|
|
25425
|
+
entries.push(sub);
|
|
25426
|
+
}
|
|
25385
25427
|
}
|
|
25386
25428
|
for (let i2 = 0;i2 < group.incoming.length; i2++) {
|
|
25387
25429
|
const inc = group.incoming[i2];
|
|
25388
25430
|
const qty = formatQuantity(Math.abs(inc.quantity));
|
|
25389
25431
|
const commodity = formatCommodity(inc.symbol);
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
|
|
25432
|
+
if (costInfo && costInfo.incomingTotalCosts[i2] != null) {
|
|
25433
|
+
const costAmount = formatAmount2(costInfo.incomingTotalCosts[i2], costInfo.currency);
|
|
25434
|
+
const postings = [
|
|
25435
|
+
{
|
|
25436
|
+
account: `equity:conversion:${costInfo.currency.toLowerCase()}`,
|
|
25437
|
+
amount: formatAmount2(-costInfo.incomingTotalCosts[i2], costInfo.currency)
|
|
25438
|
+
},
|
|
25439
|
+
{
|
|
25440
|
+
account: `assets:investments:stocks:${inc.symbol}`,
|
|
25441
|
+
amount: `${qty} ${commodity} @@ ${costAmount}`
|
|
25442
|
+
}
|
|
25443
|
+
];
|
|
25444
|
+
let sub = `${date5} ${description}
|
|
25401
25445
|
`;
|
|
25402
|
-
|
|
25446
|
+
sub += metadata;
|
|
25447
|
+
sub += formatPostings(postings) + `
|
|
25403
25448
|
`;
|
|
25404
|
-
|
|
25405
|
-
|
|
25449
|
+
entries.push(sub);
|
|
25450
|
+
} else {
|
|
25451
|
+
const postings = [
|
|
25452
|
+
{
|
|
25453
|
+
account: `assets:investments:stocks:${inc.symbol}`,
|
|
25454
|
+
amount: `${qty} ${commodity}`
|
|
25455
|
+
}
|
|
25456
|
+
];
|
|
25457
|
+
let sub = `${date5} ${description}
|
|
25458
|
+
`;
|
|
25459
|
+
sub += metadata;
|
|
25460
|
+
sub += formatPostings(postings) + `
|
|
25406
25461
|
`;
|
|
25462
|
+
entries.push(sub);
|
|
25463
|
+
}
|
|
25407
25464
|
}
|
|
25408
|
-
|
|
25465
|
+
if (group.cashSettlement) {
|
|
25466
|
+
const cashCurrency = group.cashSettlement.currency.toLowerCase();
|
|
25467
|
+
const cashAmount = formatAmount2(group.cashSettlement.amount, group.cashSettlement.currency);
|
|
25468
|
+
let sub = `${date5} ${description}
|
|
25409
25469
|
`;
|
|
25410
|
-
|
|
25470
|
+
sub += metadata;
|
|
25471
|
+
sub += ` assets:broker:swissquote:${cashCurrency} ${cashAmount}
|
|
25472
|
+
`;
|
|
25473
|
+
sub += ` income:capital-gains:realized
|
|
25474
|
+
`;
|
|
25475
|
+
entries.push(sub);
|
|
25476
|
+
}
|
|
25477
|
+
return entries.join(`
|
|
25478
|
+
`);
|
|
25411
25479
|
}
|
|
25412
25480
|
function generateRightsDistributionEntry(action, logger) {
|
|
25413
25481
|
const date5 = formatDate(action.date);
|
|
@@ -25430,28 +25498,23 @@ function generateRightsDistributionEntry(action, logger) {
|
|
|
25430
25498
|
`;
|
|
25431
25499
|
return entry;
|
|
25432
25500
|
}
|
|
25433
|
-
function
|
|
25501
|
+
function generateDirectWorthlessEntry(date5, orderNum, outgoingSymbols, outgoingIsins, outgoingQuantities, outgoingTotalCosts, totalCostBasis, currency, logger) {
|
|
25434
25502
|
const formattedDate = formatDate(date5);
|
|
25435
25503
|
const symbols = outgoingSymbols.join(" + ");
|
|
25436
|
-
const description = escapeDescription(`Worthless: ${symbols}
|
|
25437
|
-
logger?.debug(`Generating
|
|
25504
|
+
const description = escapeDescription(`Worthless liquidation: ${symbols}`);
|
|
25505
|
+
logger?.debug(`Generating direct worthless entry: ${symbols}, loss: ${totalCostBasis.toFixed(2)} ${currency}`);
|
|
25438
25506
|
const postings = [];
|
|
25439
|
-
|
|
25440
|
-
const
|
|
25441
|
-
|
|
25442
|
-
|
|
25443
|
-
|
|
25444
|
-
|
|
25445
|
-
|
|
25446
|
-
|
|
25447
|
-
|
|
25448
|
-
|
|
25449
|
-
|
|
25450
|
-
postings.push({
|
|
25451
|
-
account: "equity:conversion",
|
|
25452
|
-
amount: `-${formatQuantity(quantity)} ${commodity} @ ${formatPrice(costBasisPerUnit)} ${currency}`
|
|
25453
|
-
});
|
|
25454
|
-
}
|
|
25507
|
+
for (let i2 = 0;i2 < outgoingSymbols.length; i2++) {
|
|
25508
|
+
const symbol2 = outgoingSymbols[i2];
|
|
25509
|
+
const quantity = outgoingQuantities[i2] || 0;
|
|
25510
|
+
const cost = outgoingTotalCosts[i2] || 0;
|
|
25511
|
+
if (quantity <= 0)
|
|
25512
|
+
continue;
|
|
25513
|
+
const commodity = formatCommodity(symbol2);
|
|
25514
|
+
postings.push({
|
|
25515
|
+
account: `assets:investments:stocks:${symbol2}`,
|
|
25516
|
+
amount: `-${formatQuantity(quantity)} ${commodity} @@ ${formatAmount2(cost, currency)}`
|
|
25517
|
+
});
|
|
25455
25518
|
}
|
|
25456
25519
|
postings.push({
|
|
25457
25520
|
account: "expenses:losses:capital",
|
|
@@ -25461,7 +25524,7 @@ function generatePendingMergerWorthlessEntry(date5, orderNum, outgoingSymbols, o
|
|
|
25461
25524
|
`;
|
|
25462
25525
|
entry += ` ; swissquote:order:${orderNum}
|
|
25463
25526
|
`;
|
|
25464
|
-
entry += ` ;
|
|
25527
|
+
entry += ` ; total loss: ${totalCostBasis.toFixed(2)} ${currency}
|
|
25465
25528
|
`;
|
|
25466
25529
|
if (outgoingIsins.length > 0) {
|
|
25467
25530
|
entry += ` ; Original ISINs: ${outgoingIsins.join(", ")}
|
|
@@ -25699,6 +25762,17 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
|
|
|
25699
25762
|
if (group.outgoing.length === 0 && group.incoming.length > 0) {
|
|
25700
25763
|
const pendingState = loadPendingMerger(projectDir, lotInventoryPath, group.key, logger);
|
|
25701
25764
|
if (pendingState) {
|
|
25765
|
+
for (let i2 = 0;i2 < pendingState.outgoingSymbols.length; i2++) {
|
|
25766
|
+
group.outgoing.push({
|
|
25767
|
+
date: pendingState.date,
|
|
25768
|
+
orderNum: pendingState.orderNum,
|
|
25769
|
+
type: "Internal exchange of securities",
|
|
25770
|
+
symbol: pendingState.outgoingSymbols[i2],
|
|
25771
|
+
name: "",
|
|
25772
|
+
isin: pendingState.outgoingIsins[i2] || "",
|
|
25773
|
+
quantity: -(pendingState.outgoingQuantities[i2] || 0)
|
|
25774
|
+
});
|
|
25775
|
+
}
|
|
25702
25776
|
const totalIncomingQty2 = group.incoming.reduce((sum, a) => sum + Math.abs(a.quantity), 0);
|
|
25703
25777
|
const incomingTotalCosts2 = [];
|
|
25704
25778
|
for (const inc of group.incoming) {
|
|
@@ -25722,11 +25796,11 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
|
|
|
25722
25796
|
logger?.info(`Cross-currency merger incoming: ${absQty} ${inc.symbol} @ ${costBasisPerUnit.toFixed(2)} ${pendingState.currency}`);
|
|
25723
25797
|
}
|
|
25724
25798
|
const costInfo2 = {
|
|
25725
|
-
outgoingTotalCosts: [],
|
|
25799
|
+
outgoingTotalCosts: pendingState.outgoingTotalCosts || [],
|
|
25726
25800
|
incomingTotalCosts: incomingTotalCosts2,
|
|
25727
25801
|
currency: pendingState.currency
|
|
25728
25802
|
};
|
|
25729
|
-
const entry2 = generateMultiWayMergerEntry(group,
|
|
25803
|
+
const entry2 = generateMultiWayMergerEntry(group, undefined, undefined, costInfo2, logger);
|
|
25730
25804
|
entries.push(entry2);
|
|
25731
25805
|
removePendingMerger(projectDir, lotInventoryPath, group.key, logger);
|
|
25732
25806
|
} else {
|
|
@@ -25764,16 +25838,10 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
|
|
|
25764
25838
|
outgoingSymbols,
|
|
25765
25839
|
outgoingIsins: group.outgoing.map((a) => a.isin),
|
|
25766
25840
|
outgoingQuantities: group.outgoing.map((a) => Math.abs(a.quantity)),
|
|
25841
|
+
outgoingTotalCosts,
|
|
25767
25842
|
totalCostBasis,
|
|
25768
25843
|
currency: costCurrency
|
|
25769
25844
|
}, logger);
|
|
25770
|
-
const costInfo2 = {
|
|
25771
|
-
outgoingTotalCosts,
|
|
25772
|
-
incomingTotalCosts: [],
|
|
25773
|
-
currency: costCurrency
|
|
25774
|
-
};
|
|
25775
|
-
const entry2 = generateMultiWayMergerEntry(group, undefined, undefined, costInfo2, logger);
|
|
25776
|
-
entries.push(entry2);
|
|
25777
25845
|
logger?.info(`Cross-currency merger outgoing: ${outgoingSymbols.join(", ")} -> pending (cost basis: ${totalCostBasis.toFixed(2)})`);
|
|
25778
25846
|
return entries;
|
|
25779
25847
|
}
|
|
@@ -25871,7 +25939,7 @@ function resolveRemainingPendingMergers(projectDir, lotInventoryPath, logger) {
|
|
|
25871
25939
|
continue;
|
|
25872
25940
|
}
|
|
25873
25941
|
const year = parseInt(dateMatch[1], 10);
|
|
25874
|
-
const entry =
|
|
25942
|
+
const entry = generateDirectWorthlessEntry(state.date, state.orderNum, state.outgoingSymbols, state.outgoingIsins, state.outgoingQuantities || [], state.outgoingTotalCosts || [], state.totalCostBasis, state.currency, logger);
|
|
25875
25943
|
const journalFile = path13.join(projectDir, "ledger", "investments", `${year}-${state.currency.toLowerCase()}.journal`);
|
|
25876
25944
|
if (!entriesByJournal.has(journalFile)) {
|
|
25877
25945
|
entriesByJournal.set(journalFile, []);
|
|
@@ -25978,6 +26046,7 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
|
|
|
25978
26046
|
const dividends = [];
|
|
25979
26047
|
const corporateActions = [];
|
|
25980
26048
|
const forexTransactions = [];
|
|
26049
|
+
const mergerCashSettlements = [];
|
|
25981
26050
|
for (const txn of transactions) {
|
|
25982
26051
|
const category = categorizeTransaction(txn);
|
|
25983
26052
|
switch (category) {
|
|
@@ -25996,8 +26065,12 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
|
|
|
25996
26065
|
case "corporate":
|
|
25997
26066
|
if (!txn.symbol || txn.symbol.trim() === "") {
|
|
25998
26067
|
if (txn.netAmount && txn.netAmount !== "-") {
|
|
25999
|
-
|
|
26000
|
-
|
|
26068
|
+
mergerCashSettlements.push({
|
|
26069
|
+
dateOrderKey: `${txn.date}-${txn.orderNum}`,
|
|
26070
|
+
amount: parseNumber(txn.netAmount),
|
|
26071
|
+
currency: txn.currency
|
|
26072
|
+
});
|
|
26073
|
+
stats.corporateActions++;
|
|
26001
26074
|
} else {
|
|
26002
26075
|
stats.skipped++;
|
|
26003
26076
|
}
|
|
@@ -26041,6 +26114,31 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
|
|
|
26041
26114
|
}
|
|
26042
26115
|
}
|
|
26043
26116
|
const mergerGroups = groupMergerActions(mergerActions);
|
|
26117
|
+
for (const cash of mergerCashSettlements) {
|
|
26118
|
+
const matchingGroup = mergerGroups.find((g) => g.key === cash.dateOrderKey);
|
|
26119
|
+
if (matchingGroup) {
|
|
26120
|
+
matchingGroup.cashSettlement = { amount: cash.amount, currency: cash.currency };
|
|
26121
|
+
} else {
|
|
26122
|
+
simpleTransactions.push({
|
|
26123
|
+
date: cash.dateOrderKey.split("-").slice(0, 3).join("-"),
|
|
26124
|
+
orderNum: cash.dateOrderKey.split("-").slice(3).join("-"),
|
|
26125
|
+
transaction: "Merger",
|
|
26126
|
+
symbol: "",
|
|
26127
|
+
name: "",
|
|
26128
|
+
isin: "",
|
|
26129
|
+
quantity: "",
|
|
26130
|
+
unitPrice: "",
|
|
26131
|
+
costs: "",
|
|
26132
|
+
accruedInterest: "",
|
|
26133
|
+
netAmount: String(cash.amount),
|
|
26134
|
+
balance: "",
|
|
26135
|
+
currency: cash.currency
|
|
26136
|
+
});
|
|
26137
|
+
stats.simpleTransactions++;
|
|
26138
|
+
stats.corporateActions--;
|
|
26139
|
+
logger?.warn(`Cash settlement ${cash.dateOrderKey} has no matching merger group \u2014 routed to simple CSV`);
|
|
26140
|
+
}
|
|
26141
|
+
}
|
|
26044
26142
|
for (const group of mergerGroups) {
|
|
26045
26143
|
timeline.push({ kind: "mergerGroup", sortDate: formatDate(group.date), group });
|
|
26046
26144
|
}
|
|
@@ -26178,7 +26276,7 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
|
|
|
26178
26276
|
investmentAccounts.add("income:capital-gains:realized");
|
|
26179
26277
|
investmentAccounts.add("income:capital-gains:rights-distribution");
|
|
26180
26278
|
investmentAccounts.add("expenses:losses:capital");
|
|
26181
|
-
investmentAccounts.add(
|
|
26279
|
+
investmentAccounts.add(`equity:conversion:${currency.toLowerCase()}`);
|
|
26182
26280
|
investmentAccounts.add("equity:rounding");
|
|
26183
26281
|
ensureInvestmentAccountDeclarations(accountJournalPath, investmentAccounts, logger);
|
|
26184
26282
|
}
|
package/package.json
CHANGED