@fuzzle/opencode-accountant 0.13.13-next.1 → 0.13.13
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 +40 -159
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25416,47 +25416,6 @@ function generateRightsDistributionEntry(action, logger) {
|
|
|
25416
25416
|
`;
|
|
25417
25417
|
return entry;
|
|
25418
25418
|
}
|
|
25419
|
-
function generatePendingMergerWorthlessEntry(date5, orderNum, outgoingSymbols, outgoingIsins, outgoingQuantities, totalCostBasis, currency, logger) {
|
|
25420
|
-
const formattedDate = formatDate(date5);
|
|
25421
|
-
const symbols = outgoingSymbols.join(" + ");
|
|
25422
|
-
const description = escapeDescription(`Worthless: ${symbols} (unresolved merger)`);
|
|
25423
|
-
logger?.debug(`Generating pending merger worthless entry: ${symbols}, loss: ${totalCostBasis.toFixed(2)} ${currency}`);
|
|
25424
|
-
const postings = [];
|
|
25425
|
-
if (outgoingQuantities.length > 0) {
|
|
25426
|
-
const totalQty = outgoingQuantities.reduce((a, b) => a + b, 0);
|
|
25427
|
-
for (let i2 = 0;i2 < outgoingSymbols.length; i2++) {
|
|
25428
|
-
const symbol2 = outgoingSymbols[i2];
|
|
25429
|
-
const quantity = outgoingQuantities[i2] || 0;
|
|
25430
|
-
if (quantity <= 0)
|
|
25431
|
-
continue;
|
|
25432
|
-
const proportion = totalQty > 0 ? quantity / totalQty : 0;
|
|
25433
|
-
const costForSymbol = totalCostBasis * proportion;
|
|
25434
|
-
const costBasisPerUnit = costForSymbol / quantity;
|
|
25435
|
-
const commodity = formatCommodity(symbol2);
|
|
25436
|
-
postings.push({
|
|
25437
|
-
account: "equity:conversion",
|
|
25438
|
-
amount: `-${formatQuantity(quantity)} ${commodity} @ ${formatPrice(costBasisPerUnit)} ${currency}`
|
|
25439
|
-
});
|
|
25440
|
-
}
|
|
25441
|
-
}
|
|
25442
|
-
postings.push({
|
|
25443
|
-
account: "expenses:losses:capital",
|
|
25444
|
-
amount: formatAmount2(totalCostBasis, currency)
|
|
25445
|
-
});
|
|
25446
|
-
let entry = `${formattedDate} ${description}
|
|
25447
|
-
`;
|
|
25448
|
-
entry += ` ; swissquote:order:${orderNum}
|
|
25449
|
-
`;
|
|
25450
|
-
entry += ` ; Pending merger resolved as worthless - total loss: ${totalCostBasis.toFixed(2)} ${currency}
|
|
25451
|
-
`;
|
|
25452
|
-
if (outgoingIsins.length > 0) {
|
|
25453
|
-
entry += ` ; Original ISINs: ${outgoingIsins.join(", ")}
|
|
25454
|
-
`;
|
|
25455
|
-
}
|
|
25456
|
-
entry += formatPostings(postings) + `
|
|
25457
|
-
`;
|
|
25458
|
-
return entry;
|
|
25459
|
-
}
|
|
25460
25419
|
function formatJournalFile(entries, year, currency) {
|
|
25461
25420
|
const header = `; Swissquote ${currency.toUpperCase()} investment transactions for ${year}
|
|
25462
25421
|
; Generated by opencode-accountant
|
|
@@ -25614,44 +25573,6 @@ var MERGER_LIKE_TYPES = new Set([
|
|
|
25614
25573
|
"Exchange of securities",
|
|
25615
25574
|
"Corporate Action"
|
|
25616
25575
|
]);
|
|
25617
|
-
function processNonMergerAction(action, inventory, entries, logger) {
|
|
25618
|
-
switch (action.type) {
|
|
25619
|
-
case "Reverse Split": {
|
|
25620
|
-
const qty = action.quantity;
|
|
25621
|
-
if (qty < 0) {
|
|
25622
|
-
return;
|
|
25623
|
-
}
|
|
25624
|
-
const oldQty = getHeldQuantity(inventory, action.symbol);
|
|
25625
|
-
const newQty = Math.abs(qty);
|
|
25626
|
-
if (oldQty > 0) {
|
|
25627
|
-
const ratio = newQty / oldQty;
|
|
25628
|
-
adjustLotsForSplit(inventory, action.symbol, ratio, logger);
|
|
25629
|
-
const entry = generateSplitEntry(action, oldQty, newQty, logger);
|
|
25630
|
-
entries.push(entry);
|
|
25631
|
-
} else {
|
|
25632
|
-
logger?.warn(`Reverse split for ${action.symbol} but no lots found`);
|
|
25633
|
-
}
|
|
25634
|
-
break;
|
|
25635
|
-
}
|
|
25636
|
-
case "Worthless Liquidation": {
|
|
25637
|
-
const removedLots = removeLots(inventory, action.symbol, logger);
|
|
25638
|
-
if (removedLots.length > 0) {
|
|
25639
|
-
const entry = generateWorthlessEntry(action, removedLots, logger);
|
|
25640
|
-
entries.push(entry);
|
|
25641
|
-
} else {
|
|
25642
|
-
logger?.warn(`Worthless liquidation for ${action.symbol} but no lots found`);
|
|
25643
|
-
}
|
|
25644
|
-
break;
|
|
25645
|
-
}
|
|
25646
|
-
case "Rights Distribution": {
|
|
25647
|
-
if (action.quantity > 0) {
|
|
25648
|
-
const entry = generateRightsDistributionEntry(action, logger);
|
|
25649
|
-
entries.push(entry);
|
|
25650
|
-
}
|
|
25651
|
-
break;
|
|
25652
|
-
}
|
|
25653
|
-
}
|
|
25654
|
-
}
|
|
25655
25576
|
function processCorporateActions(actions, inventory, lotInventoryPath, projectDir, logger) {
|
|
25656
25577
|
const entries = [];
|
|
25657
25578
|
actions.sort((a, b) => formatDate(a.date).localeCompare(formatDate(b.date)));
|
|
@@ -25665,19 +25586,46 @@ function processCorporateActions(actions, inventory, lotInventoryPath, projectDi
|
|
|
25665
25586
|
}
|
|
25666
25587
|
}
|
|
25667
25588
|
const mergerGroups = groupMergerActions(mergerActions);
|
|
25668
|
-
|
|
25669
|
-
|
|
25670
|
-
|
|
25671
|
-
|
|
25672
|
-
|
|
25673
|
-
|
|
25674
|
-
|
|
25675
|
-
|
|
25676
|
-
|
|
25677
|
-
|
|
25678
|
-
|
|
25679
|
-
|
|
25680
|
-
|
|
25589
|
+
for (const group of mergerGroups) {
|
|
25590
|
+
const groupEntries = processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, logger);
|
|
25591
|
+
entries.push(...groupEntries);
|
|
25592
|
+
}
|
|
25593
|
+
for (const action of otherActions) {
|
|
25594
|
+
switch (action.type) {
|
|
25595
|
+
case "Reverse Split": {
|
|
25596
|
+
const qty = action.quantity;
|
|
25597
|
+
if (qty < 0) {
|
|
25598
|
+
continue;
|
|
25599
|
+
}
|
|
25600
|
+
const oldQty = getHeldQuantity(inventory, action.symbol);
|
|
25601
|
+
const newQty = Math.abs(qty);
|
|
25602
|
+
if (oldQty > 0) {
|
|
25603
|
+
const ratio = newQty / oldQty;
|
|
25604
|
+
adjustLotsForSplit(inventory, action.symbol, ratio, logger);
|
|
25605
|
+
const entry = generateSplitEntry(action, oldQty, newQty, logger);
|
|
25606
|
+
entries.push(entry);
|
|
25607
|
+
} else {
|
|
25608
|
+
logger?.warn(`Reverse split for ${action.symbol} but no lots found`);
|
|
25609
|
+
}
|
|
25610
|
+
break;
|
|
25611
|
+
}
|
|
25612
|
+
case "Worthless Liquidation": {
|
|
25613
|
+
const removedLots = removeLots(inventory, action.symbol, logger);
|
|
25614
|
+
if (removedLots.length > 0) {
|
|
25615
|
+
const entry = generateWorthlessEntry(action, removedLots, logger);
|
|
25616
|
+
entries.push(entry);
|
|
25617
|
+
} else {
|
|
25618
|
+
logger?.warn(`Worthless liquidation for ${action.symbol} but no lots found`);
|
|
25619
|
+
}
|
|
25620
|
+
break;
|
|
25621
|
+
}
|
|
25622
|
+
case "Rights Distribution": {
|
|
25623
|
+
if (action.quantity > 0) {
|
|
25624
|
+
const entry = generateRightsDistributionEntry(action, logger);
|
|
25625
|
+
entries.push(entry);
|
|
25626
|
+
}
|
|
25627
|
+
break;
|
|
25628
|
+
}
|
|
25681
25629
|
}
|
|
25682
25630
|
}
|
|
25683
25631
|
return entries;
|
|
@@ -25764,7 +25712,6 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
|
|
|
25764
25712
|
orderNum: group.orderNum,
|
|
25765
25713
|
outgoingSymbols,
|
|
25766
25714
|
outgoingIsins: group.outgoing.map((a) => a.isin),
|
|
25767
|
-
outgoingQuantities: group.outgoing.map((a) => Math.abs(a.quantity)),
|
|
25768
25715
|
totalCostBasis,
|
|
25769
25716
|
currency: outgoingCurrency || "CAD"
|
|
25770
25717
|
}, logger);
|
|
@@ -25838,68 +25785,6 @@ function removePendingMerger(projectDir, lotInventoryPath, key, logger) {
|
|
|
25838
25785
|
logger?.debug(`Removed pending merger state: ${key}`);
|
|
25839
25786
|
}
|
|
25840
25787
|
}
|
|
25841
|
-
function resolveRemainingPendingMergers(projectDir, lotInventoryPath, logger) {
|
|
25842
|
-
const pendingDir = getPendingMergerDir(projectDir, lotInventoryPath);
|
|
25843
|
-
if (!fs18.existsSync(pendingDir)) {
|
|
25844
|
-
return { resolved: 0, journalFilesUpdated: [] };
|
|
25845
|
-
}
|
|
25846
|
-
const files = fs18.readdirSync(pendingDir).filter((f) => f.endsWith(".json"));
|
|
25847
|
-
if (files.length === 0) {
|
|
25848
|
-
return { resolved: 0, journalFilesUpdated: [] };
|
|
25849
|
-
}
|
|
25850
|
-
const entriesByJournal = new Map;
|
|
25851
|
-
const resolvedFiles = [];
|
|
25852
|
-
for (const file2 of files) {
|
|
25853
|
-
const filePath = path13.join(pendingDir, file2);
|
|
25854
|
-
try {
|
|
25855
|
-
const content = fs18.readFileSync(filePath, "utf-8");
|
|
25856
|
-
const state = JSON.parse(content);
|
|
25857
|
-
const dateMatch = state.date.match(/\d{2}-\d{2}-(\d{4})/);
|
|
25858
|
-
if (!dateMatch) {
|
|
25859
|
-
logger?.warn(`Invalid date in pending merger ${file2}: ${state.date}`);
|
|
25860
|
-
continue;
|
|
25861
|
-
}
|
|
25862
|
-
const year = parseInt(dateMatch[1], 10);
|
|
25863
|
-
const entry = generatePendingMergerWorthlessEntry(state.date, state.orderNum, state.outgoingSymbols, state.outgoingIsins, state.outgoingQuantities || [], state.totalCostBasis, state.currency, logger);
|
|
25864
|
-
const journalFile = path13.join(projectDir, "ledger", "investments", `${year}-${state.currency.toLowerCase()}.journal`);
|
|
25865
|
-
if (!entriesByJournal.has(journalFile)) {
|
|
25866
|
-
entriesByJournal.set(journalFile, []);
|
|
25867
|
-
}
|
|
25868
|
-
entriesByJournal.get(journalFile).push(entry);
|
|
25869
|
-
fs18.unlinkSync(filePath);
|
|
25870
|
-
resolvedFiles.push(file2);
|
|
25871
|
-
logger?.info(`Resolved pending merger ${file2} as worthless: ${state.outgoingSymbols.join(", ")} (${state.totalCostBasis.toFixed(2)} ${state.currency})`);
|
|
25872
|
-
} catch (error45) {
|
|
25873
|
-
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
25874
|
-
logger?.warn(`Failed to resolve pending merger ${file2}: ${message}`);
|
|
25875
|
-
}
|
|
25876
|
-
}
|
|
25877
|
-
const journalFilesUpdated = [];
|
|
25878
|
-
for (const [journalFile, journalEntries] of entriesByJournal) {
|
|
25879
|
-
if (fs18.existsSync(journalFile)) {
|
|
25880
|
-
fs18.appendFileSync(journalFile, `
|
|
25881
|
-
` + journalEntries.join(`
|
|
25882
|
-
`));
|
|
25883
|
-
} else {
|
|
25884
|
-
const basename5 = path13.basename(journalFile, ".journal");
|
|
25885
|
-
const parts = basename5.split("-");
|
|
25886
|
-
const yearStr = parts[0];
|
|
25887
|
-
const currency = parts.slice(1).join("-");
|
|
25888
|
-
const header = formatJournalFile(journalEntries, parseInt(yearStr, 10), currency);
|
|
25889
|
-
fs18.writeFileSync(journalFile, header);
|
|
25890
|
-
}
|
|
25891
|
-
journalFilesUpdated.push(journalFile);
|
|
25892
|
-
logger?.info(`Updated ${path13.basename(journalFile)} with worthless resolution entries`);
|
|
25893
|
-
}
|
|
25894
|
-
const remaining = fs18.readdirSync(pendingDir).filter((f) => f.endsWith(".json"));
|
|
25895
|
-
if (remaining.length === 0) {
|
|
25896
|
-
try {
|
|
25897
|
-
fs18.rmdirSync(pendingDir);
|
|
25898
|
-
logger?.debug("Removed empty pending-mergers directory");
|
|
25899
|
-
} catch {}
|
|
25900
|
-
}
|
|
25901
|
-
return { resolved: resolvedFiles.length, journalFilesUpdated };
|
|
25902
|
-
}
|
|
25903
25788
|
async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInventoryPath = DEFAULT_LOT_INVENTORY_PATH, symbolMap = {}, logger) {
|
|
25904
25789
|
logger?.startSection(`Swissquote Preprocessing: ${path13.basename(csvPath)}`);
|
|
25905
25790
|
const stats = {
|
|
@@ -26845,10 +26730,6 @@ async function executeSwissquotePreprocessStep(context, contextIds, logger) {
|
|
|
26845
26730
|
}
|
|
26846
26731
|
logger?.logStep("Swissquote Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.corporateActions} corporate actions, ${result.stats.forexTransactions} forex`);
|
|
26847
26732
|
}
|
|
26848
|
-
const pendingResolution = resolveRemainingPendingMergers(context.directory, lotInventoryPath, logger);
|
|
26849
|
-
if (pendingResolution.resolved > 0) {
|
|
26850
|
-
logger?.logStep("Swissquote Pending Mergers", "success", `Resolved ${pendingResolution.resolved} pending merger(s) as worthless`);
|
|
26851
|
-
}
|
|
26852
26733
|
if (allForexRows.length > 0) {
|
|
26853
26734
|
const firstCtx = swissquoteContexts[0];
|
|
26854
26735
|
const yearJournalPath = path16.join(context.directory, "ledger", `${firstCtx.year}.journal`);
|
package/package.json
CHANGED