@davidosborn/crypto-tax-calculator
Version:
A tool to calculate the capital gains of cryptocurrency assets for Canadian taxes
218 lines (141 loc) • 9.07 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
var _markdownTable = _interopRequireDefault(require("markdown-table"));
var _stream = _interopRequireDefault(require("stream"));
var _assets = _interopRequireDefault(require("./assets"));
var _formatTime = _interopRequireDefault(require("./format-time"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* A stream that formats the capital gains for the user.
*/
class CapitalGainsFormatStream extends _stream.default.Transform {
/**
* Initializes a new instance.
* @param {object} [options] The options.
* @param {Set.<string>} [options.assets] The assets to show.
*/
constructor(options) {
super({
writableObjectMode: true
});
this._options = options;
this._amountFormat = new Intl.NumberFormat('en-CA', {
minimumFractionDigits: 8,
maximumFractionDigits: 8,
useGrouping: false
});
this._amountFormatNoTrailingZeros = new Intl.NumberFormat('en-CA', {
maximumFractionDigits: 8,
useGrouping: false
});
this._valueFormat = new Intl.NumberFormat('en-CA', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
this._valueFormatNoGrouping = new Intl.NumberFormat('en-CA', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false
});
}
/**
* Formats the capital gains for the user.
* @param {CapitalGains} chunk The capital gains.
* @param {string} encoding The encoding type (always 'Buffer').
* @param {function} callback A callback for when the transformation is complete.
*/
_transform(chunk, encoding, callback) {
var _this$_options, _chunk$forwardByAsset;
// Filter the assets.
if ((_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.assets) {
// Filter the assets that were carried forward from last year.
for (let asset of chunk.forwardByAsset.keys()) if (!this._options.assets.has(asset)) chunk.forwardByAsset.delete(asset); // Filter the trades.
chunk.trades = chunk.trades.filter(trade => this._options.assets.has(trade.asset)); // Filter the ledgers.
for (let asset of chunk.ledgerByAsset.keys()) if (!this._options.assets.has(asset)) chunk.ledgerByAsset.delete(asset); // Filter the negative balances.
for (let asset of chunk.negativeBalanceByAsset.keys()) if (!this._options.assets.has(asset)) chunk.negativeBalanceByAsset.delete(asset);
} // Write the balance that was carried forward from last year.
if ((_chunk$forwardByAsset = chunk.forwardByAsset) === null || _chunk$forwardByAsset === void 0 ? void 0 : _chunk$forwardByAsset.size) {
this._pushLine();
this._pushLine('## Carried forward from last year');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'Balance', 'Adjusted cost base']].concat(Array.from(chunk.forwardByAsset, ([asset, forward]) => [asset, this._formatAmount(forward.balance), this._formatValue(forward.acb)]))));
} // Write the trades.
this._pushLine();
this._pushLine('## Trades');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'Units acquired (or disposed)', 'Value', 'Balance', 'Adjusted cost base', 'Fee', 'Fee asset', 'Time', 'Exchange']].concat(Array.from(chunk.trades, trade => [trade.asset, this._formatAmount(trade.amount), this._formatValue(trade.value), this._formatAmount(trade.balance), this._formatValue(trade.acb), trade.feeAsset ? this._formatAssetAmount(trade.feeAsset, trade.feeAmount) : '', trade.feeAsset ? trade.feeAsset : '', (0, _formatTime.default)(trade.time), trade.exchange])))); // Sort the ledgers by asset.
let ledgerByAsset = Array.from(chunk.ledgerByAsset);
ledgerByAsset.sort(function (a, b) {
return a[0].localeCompare(b[0]);
}); // Write the dispositions.
this._pushLine();
this._pushLine('## Dispositions');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'Units', 'Proceeds of disposition', 'Adjusted cost base', 'Outlays and expenses', 'Gain (or loss)', 'Time', 'Exchange']].concat(...ledgerByAsset.map(([asset, ledger]) => Array.from(ledger.dispositions, disposition => [asset, this._formatAmount(disposition.amount), this._formatValue(disposition.pod), this._formatValue(disposition.acb), this._formatValue(disposition.oae), this._formatValue(disposition.gain), (0, _formatTime.default)(disposition.time), disposition.exchange]))))); // Find the assets with dispositions.
let ledgerByAssetWithDisposition = ledgerByAsset.filter(([asset, ledger]) => ledger.dispositions.length); // Write the aggregate disposition per asset.
this._pushLine();
this._pushLine('## Aggregate disposition per asset');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'Units', 'Proceeds of disposition', 'Adjusted cost base', 'Outlays and expenses', 'Gain (or loss)']].concat(ledgerByAssetWithDisposition.map(([asset, ledger]) => [asset, this._formatAmount(ledger.aggregateDisposition.amount), this._formatValue(ledger.aggregateDisposition.pod), this._formatValue(ledger.aggregateDisposition.acb), this._formatValue(ledger.aggregateDisposition.oae), this._formatValue(ledger.aggregateDisposition.gain)])))); // Write the summary per asset.
this._pushLine();
this._pushLine('## Summary per asset');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'Gain (or loss)', 'Balance', 'Adjusted cost base']].concat(ledgerByAsset.map(([asset, ledger]) => [asset, this._formatValue(ledger.aggregateDisposition.gain), this._formatAmount(ledger.balance), this._formatValue(ledger.acb)])))); // Write the summary.
this._pushLine();
this._pushLine('## Summary');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Field', 'Value'], ['Total proceeds of disposition', this._formatValue(chunk.aggregateDisposition.pod)], ['Total adjusted cost base', this._formatValue(chunk.aggregateDisposition.acb)], ['Total outlays and expenses', this._formatValue(chunk.aggregateDisposition.oae)], ['Total gain (or loss)', this._formatValue(chunk.aggregateDisposition.gain)], ['Taxable gain (or loss)', `**${this._formatValue(chunk.taxableGain)}**`]])); // Sort the negative balances by asset.
let negativeBalanceByAsset = Array.from(chunk.negativeBalanceByAsset);
negativeBalanceByAsset.sort(function (a, b) {
return a[0].localeCompare(b[0]);
}); // Write the negative balances.
if (negativeBalanceByAsset.length) {
this._pushLine();
this._pushLine('## Negative balances');
this._pushLine();
this._pushLine((0, _markdownTable.default)([['Asset', 'First negative balance', 'Time of first negative balance', 'Minimum balance', 'Time of minimum balance']].concat(negativeBalanceByAsset.map(([asset, negativeBalance]) => [asset, this._formatAmount(negativeBalance.first.balance), (0, _formatTime.default)(negativeBalance.first.time), this._formatAmount(negativeBalance.minimum.balance), (0, _formatTime.default)(negativeBalance.minimum.time)]))));
} // Find the assets that are carrying a balance.
let ledgerByAssetWithBalance = ledgerByAsset.filter(([asset, ledger]) => ledger.balance < -0.000000005 || ledger.balance >= 0.000000005); // Write the balance specification for next year.
if (ledgerByAssetWithBalance.length) {
this._pushLine();
this._pushLine('## Carry forward to the next year');
this._pushLine();
this._pushLine('The following specification can be passed to the calculator next year to carry forward this year\'s balance and adjusted cost base.');
this._pushLine();
this._pushLine('```');
this._pushLine('--init=\\');
for (let [asset, ledger] of ledgerByAssetWithBalance) {
let last = asset === ledgerByAssetWithBalance[ledgerByAssetWithBalance.length - 1][0];
let balance = this._amountFormatNoTrailingZeros.format(ledger.balance);
let acb = this._valueFormatNoGrouping.format(ledger.acb);
this._pushLine(asset + ':' + balance + ':' + acb + (last ? '' : ',\\'));
}
this._pushLine('```');
}
this._pushLine();
callback();
}
_formatAssetAmount(asset, amount) {
return _assets.default.getPriority(asset) !== 0 ? this._formatAmount(amount) : this._formatValue(amount);
}
_formatAmount(amount) {
let amountString = this._amountFormat.format(Math.abs(amount));
if (amount < -0.000000005) amountString = `(${amountString})`;
return amountString;
}
_formatValue(value) {
let valueString = this._valueFormat.format(Math.abs(value));
if (value < -0.005) valueString = `(${valueString})`;
return '$' + valueString;
}
_pushLine(line) {
this.push(line);
this.push('\n');
}
}
function _default(...args) {
return new CapitalGainsFormatStream(...args);
}