const csvToJson = require('convert-csv-to-json') const converter = require('json-2-csv') const fs = require('fs') const BN = require('bignumber.js') const { PDFDocument } = require('pdf-lib') const year = 2023 const txs = csvToJson.fieldDelimiter(',').getJsonFromCsv(`../Ledger.csv`) let utxos = [] let dispositions = [] const handleBuy = (tx, i) => { const { Date: date, Asset, Received, Price } = tx utxos.push({ Date: date, Id: i, Asset, Amount: Received, Price, Remaining: new BN(Received), }) } const createDisposition = (tx, utxo, amount, st) => { const costBasis = amount.times(utxo.Price) const proceeds = amount.times(tx.Price) const gainLoss = proceeds.minus(costBasis) return { Date: tx.Date, Asset: tx.Asset, Utxo: utxo.Id, Amount: amount.toFixed(8), PurchasePrice: utxo.Price, DateAcquired: utxo.Date, SalePrice: tx.Price, CostBasis: costBasis.toFixed(2), Proceeds: proceeds.toFixed(2), GainLoss: gainLoss.toFixed(2), ShortTerm: st ? 'true' : '', Tax: gainLoss.gt(0) ? gainLoss.times(st ? .24 : .15).toFixed(2) : 0 } } const consumeUtxos = (tx) => { const copy = [] for (i = 0; i < utxos.length; i++) { copy[i] = utxos[i]; } const sorted = copy .filter(utxo => utxo.Asset === tx.Asset && !utxo.Remaining.isZero()) .sort((a, b) => new BN(b.Price).minus(a.Price).toNumber()) const txDate = new Date(tx.Date).valueOf() const stUtxo = sorted.find(utxo => txDate - new Date(utxo.Date).valueOf() < 31556926000) const ltUtxo = sorted.find(utxo => txDate - new Date(utxo.Date).valueOf() >= 31556926000) // find the best utxo let utxo // decide st vs lt let st if (!stUtxo && !ltUtxo) { console.error(`WARNING: No utxo available! Assuming $0 cost basis:\n-Date:${tx.Date}\n-Asset: ${tx.Asset}\n-Sent: ${tx.Sent}\n-Remaining: ${tx.Remaining.toFixed(8)}\n`) utxo = { Date: '2017-01-01T12:00:00', Id: -1, Asset: tx.Asset, Amount: tx.Remaining.toFixed(8), Price: '0', Remaining: tx.Remaining, } } else if (!stUtxo) { utxo = utxos.find(u => u.Id === ltUtxo.Id) st = false } else if (!ltUtxo) { utxo = utxos.find(u => u.Id === stUtxo.Id) st = true } else { const { Tax: stTax } = getProvisional(tx, stUtxo, true) const { Tax: ltTax } = getProvisional(tx, ltUtxo, false) if (new BN(stTax).lt(ltTax)) { utxo = utxos.find(u => u.Id === stUtxo.Id) st = true } else { utxo = utxos.find(u => u.Id === ltUtxo.Id) st = false } } if (utxo.Remaining.gte(tx.Remaining)) { const disposition = createDisposition(tx, utxo, tx.Remaining, st) dispositions.push(disposition) utxo.Remaining = utxo.Remaining.minus(tx.Remaining) } else { const disposition = createDisposition(tx, utxo, utxo.Remaining, st) dispositions.push(disposition) tx.Remaining = tx.Remaining.minus(utxo.Remaining) utxo.Remaining = new BN(0) consumeUtxos(tx) } } const getProvisional = (tx, utxo, st) => { if (utxo.Remaining.gte(tx.Remaining)) { return createDisposition(tx, utxo, tx.Remaining, st) } else { return createDisposition(tx, utxo, utxo.Remaining, st) } } txs.forEach((tx, i) => { if (tx.Received) return handleBuy(tx, i) tx.Remaining = new BN(tx.Sent) return consumeUtxos(tx) }) let balances = utxos.reduce((prev, curr) => { const asset = curr.Asset if (prev[asset]) { prev[asset] = prev[asset].plus(curr.Remaining) } else { prev[asset] = curr.Remaining } return prev }, {}) utxos = utxos.map(u => ({ ...u, Remaining: u.Remaining.toFixed(8), })) balances = Object.keys(balances) .filter(asset => !balances[asset].isZero()) .map(asset => ({ Asset: asset, balance: balances[asset].toFixed(8) })) converter.json2csv(dispositions).then(csv => fs.appendFileSync(`./dispositions.csv`, csv) ) converter.json2csv(balances).then(csv => fs.appendFileSync(`./balances.csv`, csv) ) converter.json2csv(utxos).then(csv => fs.appendFileSync(`./utxos.csv`, csv) ) // **** 8949 **** const getDate = (date) => { const dateObj = new Date(date) const month = dateObj.getMonth() + 1 const day = dateObj.getDate() const year = dateObj.getFullYear() return month + "/" + day + "/" + year; } const fillFormRows = (form, page, disposition, i) => { const dateAcquired = getDate(disposition.DateAcquired) const dateSold = getDate(disposition.Date) const name = `topmostSubform[0].Page${page}[0].Table_Line1[0].Row${i + 1}[0].f${page}_` form.getTextField(`${name}${i * 8 + 3}[0]`).setText(disposition.Asset) form.getTextField(`${name}${i * 8 + 4}[0]`).setText(dateAcquired) form.getTextField(`${name}${i * 8 + 5}[0]`).setText(dateSold) form.getTextField(`${name}${i * 8 + 6}[0]`).setText(disposition.Proceeds.toString()) form.getTextField(`${name}${i * 8 + 7}[0]`).setText(disposition.CostBasis.toString()) form.getTextField(`${name}${i * 8 + 10}[0]`).setText(disposition.GainLoss.toString()) } const fillFormTotals = (form, page, proceeds, basis) => { form.getTextField(`topmostSubform[0].Page${page}[0].f${page}_115[0]`).setText(proceeds.toFixed(2)) form.getTextField(`topmostSubform[0].Page${page}[0].f${page}_116[0]`).setText(basis.toFixed(2)) form.getTextField(`topmostSubform[0].Page${page}[0].f${page}_119[0]`).setText(proceeds.minus(basis).toFixed(2)) } const createPDF = async () => { const pdfDoc = await PDFDocument.create() const stDispositions = dispositions.filter(d => d.ShortTerm && new Date(d.Date).getFullYear() === year) const ltDispositions = dispositions.filter(d => !d.ShortTerm && new Date(d.Date).getFullYear() === year) const maxTxs = Math.max(stDispositions.length, ltDispositions.length) const numPages = Math.ceil(maxTxs / 14) for (let i = 0; i < numPages; i++) { const blank8949Bytes = fs.readFileSync('../' + year + '-f8949.pdf') const blank8949 = await PDFDocument.load(blank8949Bytes) const form = blank8949.getForm() const start = i * 14 // short term const st = stDispositions.slice(start, start + 14) const stTotals = st.reduce((totals, d, i) => { fillFormRows(form, 1, d, i) totals.proceeds = totals.proceeds.plus(d.Proceeds) totals.basis = totals.basis.plus(d.CostBasis) return totals }, { proceeds: new BN(0), basis: new BN(0) }) fillFormTotals(form, 1, stTotals.proceeds, stTotals.basis) // long term const lt = ltDispositions.slice(start, start + 14) const ltTotals = lt.reduce((totals, d, i) => { fillFormRows(form, 2, d, i) totals.proceeds = totals.proceeds.plus(d.Proceeds) totals.basis = totals.basis.plus(d.CostBasis) return totals }, { proceeds: new BN(0), basis: new BN(0) }) fillFormTotals(form, 2, ltTotals.proceeds, ltTotals.basis) await blank8949.save() const [page1, page2] = await pdfDoc.copyPages(blank8949, [0,1]) pdfDoc.addPage(page1) pdfDoc.addPage(page2) } const pdfBytes = await pdfDoc.save() fs.writeFileSync(`./8949.pdf`, pdfBytes) } createPDF()