@fuzzle/opencode-accountant 0.13.1 → 0.13.2
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 -83
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24456,6 +24456,24 @@ function createImportLogger(directory, worktreeId, provider) {
|
|
|
24456
24456
|
|
|
24457
24457
|
// src/utils/btcPurchaseGenerator.ts
|
|
24458
24458
|
import * as fs14 from "fs";
|
|
24459
|
+
|
|
24460
|
+
// src/utils/journalFormatting.ts
|
|
24461
|
+
var MIN_GAP = 5;
|
|
24462
|
+
var INDENT = " ";
|
|
24463
|
+
function formatPostings(postings) {
|
|
24464
|
+
const maxAccountLen = postings.reduce((max, p) => Math.max(max, p.account.length), 0);
|
|
24465
|
+
return postings.map((p) => {
|
|
24466
|
+
const gap = Math.max(MIN_GAP, maxAccountLen - p.account.length + MIN_GAP);
|
|
24467
|
+
const line = `${INDENT}${p.account}${" ".repeat(gap)}${p.amount}`;
|
|
24468
|
+
if (p.comment) {
|
|
24469
|
+
return `${line} ${p.comment}`;
|
|
24470
|
+
}
|
|
24471
|
+
return line;
|
|
24472
|
+
}).join(`
|
|
24473
|
+
`);
|
|
24474
|
+
}
|
|
24475
|
+
|
|
24476
|
+
// src/utils/btcPurchaseGenerator.ts
|
|
24459
24477
|
function parseRevolutFiatDatetime(dateStr) {
|
|
24460
24478
|
const [datePart, timePart] = dateStr.split(" ");
|
|
24461
24479
|
if (!datePart || !timePart) {
|
|
@@ -24669,30 +24687,33 @@ function formatJournalEntry(match2) {
|
|
|
24669
24687
|
const { fiatRow, btcRow } = match2;
|
|
24670
24688
|
const date5 = fiatRow.dateStr;
|
|
24671
24689
|
const fiatCurrency = fiatRow.currency;
|
|
24672
|
-
const fiatAmount = formatAmount(fiatRow.amount);
|
|
24673
24690
|
const btcQuantity = formatBtcQuantity(btcRow.quantity);
|
|
24674
|
-
const btcPrice = formatAmount(btcRow.price.amount);
|
|
24675
24691
|
const priceCurrency = btcRow.price.currency;
|
|
24676
24692
|
const hasFees = btcRow.fees.amount > 0;
|
|
24677
24693
|
const isBaseCurrency = fiatCurrency === "CHF";
|
|
24694
|
+
const netFiatAmount = hasFees ? fiatRow.amount - btcRow.fees.amount : fiatRow.amount;
|
|
24695
|
+
const effectivePrice = hasFees ? netFiatAmount / btcRow.quantity : btcRow.price.amount;
|
|
24696
|
+
const fiatAmount = formatAmount(fiatRow.amount);
|
|
24697
|
+
const netFiat = formatAmount(netFiatAmount);
|
|
24698
|
+
const btcPrice = formatAmount(effectivePrice);
|
|
24678
24699
|
const pair = `${fiatCurrency.toLowerCase()}-btc`;
|
|
24679
24700
|
const equityFiat = `equity:conversion:${pair}:${fiatCurrency.toLowerCase()}`;
|
|
24680
24701
|
const equityBtc = `equity:conversion:${pair}:btc`;
|
|
24681
|
-
const
|
|
24682
|
-
const
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
` assets:bank:revolut:btc ${btcQuantity} BTC${assetCostAnnotation}`
|
|
24702
|
+
const costAnnotation = isBaseCurrency ? ` @ ${btcPrice} ${priceCurrency}` : "";
|
|
24703
|
+
const postings = [
|
|
24704
|
+
{
|
|
24705
|
+
account: `assets:bank:revolut:${fiatCurrency.toLowerCase()}`,
|
|
24706
|
+
amount: `-${fiatAmount} ${fiatCurrency}`
|
|
24707
|
+
}
|
|
24688
24708
|
];
|
|
24689
24709
|
if (hasFees) {
|
|
24690
24710
|
const feeAmount = formatAmount(btcRow.fees.amount);
|
|
24691
24711
|
const feeCurrency = btcRow.fees.currency;
|
|
24692
|
-
|
|
24712
|
+
postings.push({ account: "expenses:fees:btc", amount: `${feeAmount} ${feeCurrency}` });
|
|
24693
24713
|
}
|
|
24694
|
-
|
|
24695
|
-
|
|
24714
|
+
postings.push({ account: equityFiat, amount: `${netFiat} ${fiatCurrency}` }, { account: equityBtc, amount: `-${btcQuantity} BTC` }, { account: "assets:bank:revolut:btc", amount: `${btcQuantity} BTC${costAnnotation}` });
|
|
24715
|
+
return `${date5} Bitcoin purchase
|
|
24716
|
+
${formatPostings(postings)}`;
|
|
24696
24717
|
}
|
|
24697
24718
|
function formatAmount(amount) {
|
|
24698
24719
|
return amount.toFixed(2);
|
|
@@ -25130,17 +25151,27 @@ function generateBuyEntry(trade, logger) {
|
|
|
25130
25151
|
const cashOut = totalCost + fees;
|
|
25131
25152
|
logger?.debug(`Generating Buy entry: ${qty} ${trade.symbol} @ ${price} ${trade.currency}`);
|
|
25132
25153
|
const commodity = formatCommodity(trade.symbol);
|
|
25154
|
+
const postings = [
|
|
25155
|
+
{
|
|
25156
|
+
account: `assets:investments:stocks:${trade.symbol}`,
|
|
25157
|
+
amount: `${qty} ${commodity} @ ${price} ${trade.currency}`
|
|
25158
|
+
}
|
|
25159
|
+
];
|
|
25160
|
+
if (fees > 0) {
|
|
25161
|
+
postings.push({
|
|
25162
|
+
account: "expenses:fees:trading:swissquote",
|
|
25163
|
+
amount: formatAmount2(fees, trade.currency)
|
|
25164
|
+
});
|
|
25165
|
+
}
|
|
25166
|
+
postings.push({
|
|
25167
|
+
account: `assets:broker:swissquote:${trade.currency.toLowerCase()}`,
|
|
25168
|
+
amount: formatAmount2(-cashOut, trade.currency)
|
|
25169
|
+
});
|
|
25133
25170
|
let entry = `${date5} ${description}
|
|
25134
25171
|
`;
|
|
25135
25172
|
entry += ` ; swissquote:order:${trade.orderNum} isin:${trade.isin}
|
|
25136
25173
|
`;
|
|
25137
|
-
entry +=
|
|
25138
|
-
`;
|
|
25139
|
-
if (fees > 0) {
|
|
25140
|
-
entry += ` expenses:fees:trading:swissquote ${formatAmount2(fees, trade.currency)}
|
|
25141
|
-
`;
|
|
25142
|
-
}
|
|
25143
|
-
entry += ` assets:broker:swissquote:${trade.currency.toLowerCase()} ${formatAmount2(-cashOut, trade.currency)}
|
|
25174
|
+
entry += formatPostings(postings) + `
|
|
25144
25175
|
`;
|
|
25145
25176
|
return entry;
|
|
25146
25177
|
}
|
|
@@ -25155,49 +25186,72 @@ function generateSellEntry(trade, consumed, logger) {
|
|
|
25155
25186
|
const gain = calculateCapitalGain(consumed, salePrice, trade.quantity);
|
|
25156
25187
|
logger?.debug(`Generating Sell entry: ${qty} ${trade.symbol} @ ${salePrice} ${trade.currency}, gain: ${gain.toFixed(2)}`);
|
|
25157
25188
|
const commodity = formatCommodity(trade.symbol);
|
|
25158
|
-
let entry = `${date5} ${description}
|
|
25159
|
-
`;
|
|
25160
|
-
entry += ` ; swissquote:order:${trade.orderNum} isin:${trade.isin}
|
|
25161
|
-
`;
|
|
25162
25189
|
const lotDetails = consumed.map((c) => `${c.lot.date}: ${formatQuantity(c.quantity)}@${formatPrice(c.lot.costBasis)}`).join(", ");
|
|
25163
|
-
|
|
25164
|
-
`;
|
|
25190
|
+
const postings = [];
|
|
25165
25191
|
for (const c of consumed) {
|
|
25166
25192
|
const lotQty = formatQuantity(c.quantity);
|
|
25167
25193
|
const lotPrice = formatPrice(c.lot.costBasis);
|
|
25168
|
-
|
|
25169
|
-
|
|
25194
|
+
postings.push({
|
|
25195
|
+
account: `assets:investments:stocks:${trade.symbol}`,
|
|
25196
|
+
amount: `-${lotQty} ${commodity} @ ${lotPrice} ${trade.currency}`
|
|
25197
|
+
});
|
|
25170
25198
|
}
|
|
25171
|
-
|
|
25172
|
-
|
|
25199
|
+
postings.push({
|
|
25200
|
+
account: `assets:broker:swissquote:${trade.currency.toLowerCase()}`,
|
|
25201
|
+
amount: formatAmount2(cashIn, trade.currency)
|
|
25202
|
+
});
|
|
25173
25203
|
if (fees > 0) {
|
|
25174
|
-
|
|
25175
|
-
|
|
25204
|
+
postings.push({
|
|
25205
|
+
account: "expenses:fees:trading:swissquote",
|
|
25206
|
+
amount: formatAmount2(fees, trade.currency)
|
|
25207
|
+
});
|
|
25176
25208
|
}
|
|
25177
25209
|
if (gain >= 0) {
|
|
25178
|
-
|
|
25179
|
-
|
|
25210
|
+
postings.push({
|
|
25211
|
+
account: "income:capital-gains:realized",
|
|
25212
|
+
amount: formatAmount2(-gain, trade.currency)
|
|
25213
|
+
});
|
|
25180
25214
|
} else {
|
|
25181
|
-
|
|
25182
|
-
|
|
25215
|
+
postings.push({
|
|
25216
|
+
account: "expenses:losses:capital",
|
|
25217
|
+
amount: formatAmount2(-gain, trade.currency)
|
|
25218
|
+
});
|
|
25183
25219
|
}
|
|
25220
|
+
let entry = `${date5} ${description}
|
|
25221
|
+
`;
|
|
25222
|
+
entry += ` ; swissquote:order:${trade.orderNum} isin:${trade.isin}
|
|
25223
|
+
`;
|
|
25224
|
+
entry += ` ; FIFO lots: ${lotDetails}
|
|
25225
|
+
`;
|
|
25226
|
+
entry += formatPostings(postings) + `
|
|
25227
|
+
`;
|
|
25184
25228
|
return entry;
|
|
25185
25229
|
}
|
|
25186
25230
|
function generateDividendEntry(dividend, logger) {
|
|
25187
25231
|
const date5 = formatDate(dividend.date);
|
|
25188
25232
|
const description = escapeDescription(`Dividend ${dividend.symbol} - ${dividend.name}`);
|
|
25189
25233
|
logger?.debug(`Generating Dividend entry: ${dividend.symbol}, net: ${dividend.netAmount} ${dividend.currency}`);
|
|
25234
|
+
const postings = [
|
|
25235
|
+
{
|
|
25236
|
+
account: `assets:broker:swissquote:${dividend.currency.toLowerCase()}`,
|
|
25237
|
+
amount: formatAmount2(dividend.netAmount, dividend.currency)
|
|
25238
|
+
}
|
|
25239
|
+
];
|
|
25240
|
+
if (dividend.withholdingTax > 0) {
|
|
25241
|
+
postings.push({
|
|
25242
|
+
account: "expenses:taxes:withholding",
|
|
25243
|
+
amount: formatAmount2(dividend.withholdingTax, dividend.currency)
|
|
25244
|
+
});
|
|
25245
|
+
}
|
|
25246
|
+
postings.push({
|
|
25247
|
+
account: `income:dividends:${dividend.symbol}`,
|
|
25248
|
+
amount: formatAmount2(-dividend.grossAmount, dividend.currency)
|
|
25249
|
+
});
|
|
25190
25250
|
let entry = `${date5} ${description}
|
|
25191
25251
|
`;
|
|
25192
25252
|
entry += ` ; swissquote:order:${dividend.orderNum} isin:${dividend.isin}
|
|
25193
25253
|
`;
|
|
25194
|
-
entry +=
|
|
25195
|
-
`;
|
|
25196
|
-
if (dividend.withholdingTax > 0) {
|
|
25197
|
-
entry += ` expenses:taxes:withholding ${formatAmount2(dividend.withholdingTax, dividend.currency)}
|
|
25198
|
-
`;
|
|
25199
|
-
}
|
|
25200
|
-
entry += ` income:dividends:${dividend.symbol} ${formatAmount2(-dividend.grossAmount, dividend.currency)}
|
|
25254
|
+
entry += formatPostings(postings) + `
|
|
25201
25255
|
`;
|
|
25202
25256
|
return entry;
|
|
25203
25257
|
}
|
|
@@ -25207,20 +25261,26 @@ function generateSplitEntry(action, oldQuantity, newQuantity, logger) {
|
|
|
25207
25261
|
const splitType = ratio > 1 ? "Split" : "Reverse Split";
|
|
25208
25262
|
const description = escapeDescription(`${splitType}: ${action.symbol} (${action.name})`);
|
|
25209
25263
|
logger?.debug(`Generating ${splitType} entry: ${oldQuantity} -> ${newQuantity} ${action.symbol}`);
|
|
25264
|
+
const commodity = formatCommodity(action.symbol);
|
|
25265
|
+
const postings = [
|
|
25266
|
+
{
|
|
25267
|
+
account: `assets:investments:stocks:${action.symbol}`,
|
|
25268
|
+
amount: `-${formatQuantity(oldQuantity)} ${commodity}`
|
|
25269
|
+
},
|
|
25270
|
+
{ account: "equity:conversion", amount: `${formatQuantity(oldQuantity)} ${commodity}` },
|
|
25271
|
+
{ account: "equity:conversion", amount: `-${formatQuantity(newQuantity)} ${commodity}` },
|
|
25272
|
+
{
|
|
25273
|
+
account: `assets:investments:stocks:${action.symbol}`,
|
|
25274
|
+
amount: `${formatQuantity(newQuantity)} ${commodity}`
|
|
25275
|
+
}
|
|
25276
|
+
];
|
|
25210
25277
|
let entry = `${date5} ${description}
|
|
25211
25278
|
`;
|
|
25212
25279
|
entry += ` ; swissquote:order:${action.orderNum} isin:${action.isin}
|
|
25213
25280
|
`;
|
|
25214
25281
|
entry += ` ; Ratio: ${ratio.toFixed(4)} (${oldQuantity} -> ${newQuantity})
|
|
25215
25282
|
`;
|
|
25216
|
-
|
|
25217
|
-
entry += ` assets:investments:stocks:${action.symbol} -${formatQuantity(oldQuantity)} ${commodity}
|
|
25218
|
-
`;
|
|
25219
|
-
entry += ` equity:conversion ${formatQuantity(oldQuantity)} ${commodity}
|
|
25220
|
-
`;
|
|
25221
|
-
entry += ` equity:conversion -${formatQuantity(newQuantity)} ${commodity}
|
|
25222
|
-
`;
|
|
25223
|
-
entry += ` assets:investments:stocks:${action.symbol} ${formatQuantity(newQuantity)} ${commodity}
|
|
25283
|
+
entry += formatPostings(postings) + `
|
|
25224
25284
|
`;
|
|
25225
25285
|
return entry;
|
|
25226
25286
|
}
|
|
@@ -25231,20 +25291,24 @@ function generateWorthlessEntry(action, removedLots, logger) {
|
|
|
25231
25291
|
const totalCost = removedLots.reduce((sum, lot) => sum + lot.quantity * lot.costBasis, 0);
|
|
25232
25292
|
const currency = removedLots[0]?.currency || "USD";
|
|
25233
25293
|
logger?.debug(`Generating Worthless entry: ${totalQuantity} ${action.symbol}, loss: ${totalCost} ${currency}`);
|
|
25294
|
+
const commodity = formatCommodity(action.symbol);
|
|
25295
|
+
const postings = [];
|
|
25296
|
+
for (const lot of removedLots) {
|
|
25297
|
+
const qty = formatQuantity(lot.quantity);
|
|
25298
|
+
const price = formatPrice(lot.costBasis);
|
|
25299
|
+
postings.push({
|
|
25300
|
+
account: `assets:investments:stocks:${action.symbol}`,
|
|
25301
|
+
amount: `-${qty} ${commodity} @ ${price} ${currency}`
|
|
25302
|
+
});
|
|
25303
|
+
}
|
|
25304
|
+
postings.push({ account: "expenses:losses:capital", amount: formatAmount2(totalCost, currency) });
|
|
25234
25305
|
let entry = `${date5} ${description}
|
|
25235
25306
|
`;
|
|
25236
25307
|
entry += ` ; swissquote:order:${action.orderNum} isin:${action.isin}
|
|
25237
25308
|
`;
|
|
25238
25309
|
entry += ` ; Total loss: ${totalCost.toFixed(2)} ${currency}
|
|
25239
25310
|
`;
|
|
25240
|
-
|
|
25241
|
-
for (const lot of removedLots) {
|
|
25242
|
-
const qty = formatQuantity(lot.quantity);
|
|
25243
|
-
const price = formatPrice(lot.costBasis);
|
|
25244
|
-
entry += ` assets:investments:stocks:${action.symbol} -${qty} ${commodity} @ ${price} ${currency}
|
|
25245
|
-
`;
|
|
25246
|
-
}
|
|
25247
|
-
entry += ` expenses:losses:capital ${formatAmount2(totalCost, currency)}
|
|
25311
|
+
entry += formatPostings(postings) + `
|
|
25248
25312
|
`;
|
|
25249
25313
|
return entry;
|
|
25250
25314
|
}
|
|
@@ -25255,32 +25319,37 @@ function generateMultiWayMergerEntry(group, crossCurrencyOutgoingSymbols, crossC
|
|
|
25255
25319
|
const descriptionParts = outSymbols.join(" + ");
|
|
25256
25320
|
const description = escapeDescription(inSymbols.length > 0 ? `Merger: ${descriptionParts} -> ${inSymbols.join(" + ")}` : `Merger: ${descriptionParts}`);
|
|
25257
25321
|
logger?.debug(`Generating multi-way merger entry: ${outSymbols.join(", ")} -> ${inSymbols.join(", ")}`);
|
|
25258
|
-
let entry = `${date5} ${description}
|
|
25259
|
-
`;
|
|
25260
|
-
entry += ` ; swissquote:order:${group.orderNum}
|
|
25261
|
-
`;
|
|
25262
25322
|
const oldIsins = (crossCurrencyOutgoingIsins ?? group.outgoing.map((a) => a.isin)).filter(Boolean);
|
|
25263
25323
|
const newIsins = group.incoming.map((a) => a.isin).filter(Boolean);
|
|
25264
|
-
|
|
25265
|
-
entry += ` ; Old ISIN: ${oldIsins.join(", ") || "n/a"}, New ISINs: ${newIsins.join(", ") || "n/a"}
|
|
25266
|
-
`;
|
|
25267
|
-
}
|
|
25324
|
+
const postings = [];
|
|
25268
25325
|
for (const out of group.outgoing) {
|
|
25269
25326
|
const qty = formatQuantity(Math.abs(out.quantity));
|
|
25270
25327
|
const commodity = formatCommodity(out.symbol);
|
|
25271
|
-
|
|
25272
|
-
|
|
25273
|
-
|
|
25274
|
-
|
|
25328
|
+
postings.push({
|
|
25329
|
+
account: `assets:investments:stocks:${out.symbol}`,
|
|
25330
|
+
amount: `-${qty} ${commodity}`
|
|
25331
|
+
});
|
|
25332
|
+
postings.push({ account: "equity:conversion", amount: `${qty} ${commodity}` });
|
|
25275
25333
|
}
|
|
25276
25334
|
for (const inc of group.incoming) {
|
|
25277
25335
|
const qty = formatQuantity(Math.abs(inc.quantity));
|
|
25278
25336
|
const commodity = formatCommodity(inc.symbol);
|
|
25279
|
-
|
|
25337
|
+
postings.push({ account: "equity:conversion", amount: `-${qty} ${commodity}` });
|
|
25338
|
+
postings.push({
|
|
25339
|
+
account: `assets:investments:stocks:${inc.symbol}`,
|
|
25340
|
+
amount: `${qty} ${commodity}`
|
|
25341
|
+
});
|
|
25342
|
+
}
|
|
25343
|
+
let entry = `${date5} ${description}
|
|
25280
25344
|
`;
|
|
25281
|
-
|
|
25345
|
+
entry += ` ; swissquote:order:${group.orderNum}
|
|
25346
|
+
`;
|
|
25347
|
+
if (oldIsins.length > 0 || newIsins.length > 0) {
|
|
25348
|
+
entry += ` ; Old ISIN: ${oldIsins.join(", ") || "n/a"}, New ISINs: ${newIsins.join(", ") || "n/a"}
|
|
25282
25349
|
`;
|
|
25283
25350
|
}
|
|
25351
|
+
entry += formatPostings(postings) + `
|
|
25352
|
+
`;
|
|
25284
25353
|
return entry;
|
|
25285
25354
|
}
|
|
25286
25355
|
function generateRightsDistributionEntry(action, logger) {
|
|
@@ -25289,13 +25358,18 @@ function generateRightsDistributionEntry(action, logger) {
|
|
|
25289
25358
|
const description = escapeDescription(`Rights Distribution: ${action.symbol} - ${action.name}`);
|
|
25290
25359
|
logger?.debug(`Generating Rights Distribution entry: ${qty} ${action.symbol}`);
|
|
25291
25360
|
const commodity = formatCommodity(action.symbol);
|
|
25361
|
+
const postings = [
|
|
25362
|
+
{
|
|
25363
|
+
account: `assets:investments:stocks:${action.symbol}`,
|
|
25364
|
+
amount: `${qty} ${commodity} @ 0.00 CAD`
|
|
25365
|
+
},
|
|
25366
|
+
{ account: "income:capital-gains:rights-distribution", amount: "0.00 CAD" }
|
|
25367
|
+
];
|
|
25292
25368
|
let entry = `${date5} ${description}
|
|
25293
25369
|
`;
|
|
25294
25370
|
entry += ` ; swissquote:order:${action.orderNum} isin:${action.isin}
|
|
25295
25371
|
`;
|
|
25296
|
-
entry +=
|
|
25297
|
-
`;
|
|
25298
|
-
entry += ` income:capital-gains:rights-distribution 0.00 CAD
|
|
25372
|
+
entry += formatPostings(postings) + `
|
|
25299
25373
|
`;
|
|
25300
25374
|
return entry;
|
|
25301
25375
|
}
|
|
@@ -25996,14 +26070,20 @@ function formatExchangeEntry(match2) {
|
|
|
25996
26070
|
const pair = `${sourceCurrency.toLowerCase()}-${targetCurrency.toLowerCase()}`;
|
|
25997
26071
|
const equitySource = `equity:conversion:${pair}:${sourceCurrency.toLowerCase()}`;
|
|
25998
26072
|
const equityTarget = `equity:conversion:${pair}:${targetCurrency.toLowerCase()}`;
|
|
25999
|
-
|
|
26000
|
-
|
|
26001
|
-
|
|
26002
|
-
|
|
26003
|
-
|
|
26004
|
-
|
|
26005
|
-
|
|
26006
|
-
|
|
26073
|
+
const postings = [
|
|
26074
|
+
{
|
|
26075
|
+
account: `assets:bank:revolut:${sourceCurrency.toLowerCase()}`,
|
|
26076
|
+
amount: `-${sourceAmount} ${sourceCurrency}`
|
|
26077
|
+
},
|
|
26078
|
+
{ account: equitySource, amount: `${sourceAmount} ${sourceCurrency}` },
|
|
26079
|
+
{ account: equityTarget, amount: `-${targetAmount} ${targetCurrency}` },
|
|
26080
|
+
{
|
|
26081
|
+
account: `assets:bank:revolut:${targetCurrency.toLowerCase()}`,
|
|
26082
|
+
amount: `${targetAmount} ${targetCurrency}`
|
|
26083
|
+
}
|
|
26084
|
+
];
|
|
26085
|
+
return `${date5} ${description}
|
|
26086
|
+
${formatPostings(postings)}`;
|
|
26007
26087
|
}
|
|
26008
26088
|
function formatAmount3(amount) {
|
|
26009
26089
|
return amount.toFixed(2);
|