UNPKG

@davidosborn/crypto-tax-calculator

Version:

A tool to calculate the capital gains of cryptocurrency assets for Canadian taxes

244 lines (205 loc) 7.93 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; var _stream = _interopRequireDefault(require("stream")); var _formatTime = _interopRequireDefault(require("./format-time")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * A disposition. * @typedef {object} Disposition * @property {string} [exchange] The exchange on which the disposition was executed. * @property {number} amount The amount. * @property {number} acb The adjusted cost base. * @property {number} pod The proceeds of disposition. * @property {number} oae The outlays and expenses. * @property {number} gain The capital gain (or loss). */ /** * The ledger for an asset. * @typedef {object} Ledger * @property {number} acb The adjusted cost base. * @property {number} balance The balance. * @property {array.<Disposition>} dispositions The dispositions. */ /** * The captial gains. * @typedef {object} CapitalGains * @property {Map.<string, Forward>} [forwardByAsset] The assets that were carried forward from last year. * @property {array.<Trade>} trades The trades. * @property {Map.<string, Ledger>} ledgerByAsset The ledger of each asset. * @property {Map.<string, NegativeBalance>} negativeBalanceByAsset The assets that had a negative balance. * @property {Disposition} aggregateDisposition The aggregate disposition. * @property {number} taxableGain The taxable gain (or loss). */ /** * The initial balance and ACB of an asset that was carried forward from last year. * @typedef {object} Forward * @property {number} balance The balance. * @property {number} acb The adjusted cost base. */ /** * The information about the negative balance of an asset. * @typedef {object} NegativeBalance * @property {NegativeBalanceEvent} first The first negative balance. * @property {NegativeBalanceEvent} minimum The minimum negative balance. */ /** * The information about an occurrence of a negative balance. * @typedef {object} NegativeBalanceEvent * @property {number} balance The balance at the event. * @property {number} time The time of the event, as a UNIX timestamp. */ /** * A stream that calculates the capital gains. */ class CapitalGainsCalculateStream extends _stream.default.Transform { /** * Initializes a new instance. * @param {object} [options] The options. * @param {Set.<string>} [options.assets] The assets to consider. * @param {Map.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. */ constructor(options) { var _this$_options; super({ objectMode: true }); this._options = options; /** * The assets to carry forward from last year. * @type {Map.<string, Forward>} */ this._forwardByAsset = (_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.forwardByAsset; /** * The trades. * @type {Trade} */ this._trades = []; /** * The ledger of each asset. * @type {Map.<string, Ledger>} */ this._ledgerByAsset = new Map(); /** * The assets that had a negative balance. * @type {Map.<string, number>} */ this._negativeBalanceByAsset = new Map(); // Initialize the ledger of the assets to carry forward from last year. if (this._forwardByAsset) for (let [asset, forward] of this._forwardByAsset.entries()) this._ledgerByAsset.set(asset, { acb: forward.acb, balance: forward.balance, dispositions: [] }); } /** * Accumulates the capital gains for a transaction. * @param {Transaction} chunk The transaction. * @param {string} encoding The encoding type (always 'Buffer'). * @param {function} callback A callback for when the transformation is complete. */ _transform(chunk, encoding, callback) { this._trades.push(chunk); let ledger = this._ledgerByAsset.get(chunk.asset); if (ledger === undefined) this._ledgerByAsset.set(chunk.asset, ledger = { acb: 0, balance: 0, dispositions: [] }); if (chunk.amount < 0) { if (!ledger.balance) console.log('WARNING: Disposition of ' + chunk.asset + ' from an empty balance on ' + (0, _formatTime.default)(chunk.time) + '.'); let acbPerUnit = ledger.balance ? ledger.acb / ledger.balance : 0; let disposition = { exchange: chunk.exchange, amount: -chunk.amount, pod: chunk.value, oae: chunk.feeValue, time: chunk.time }; disposition.acb = disposition.amount * acbPerUnit; disposition.gain = disposition.pod - disposition.acb - disposition.oae; ledger.dispositions.push(disposition); ledger.acb += acbPerUnit * chunk.amount; } else ledger.acb += chunk.value + chunk.feeValue; // Update the balance. ledger.balance += chunk.amount; // Remove the transaction fee from the balance, except for fiat currencies. if (chunk.feeAmount) { let feeLedger = this._ledgerByAsset.get(chunk.feeAsset); if (feeLedger !== undefined) { let feeAcbPerUnit = feeLedger.balance ? feeLedger.acb / feeLedger.balance : 0; feeLedger.acb -= feeAcbPerUnit * chunk.feeAmount; feeLedger.balance -= chunk.feeAmount; } } // Record the balance and ACB in the trade for information purposes. chunk.balance = ledger.balance; chunk.acb = ledger.acb; // Check whether the balance is negative, which would indicate an accounting error. if (ledger.balance < -0.000000005) { let negativeBalance = this._negativeBalanceByAsset.get(chunk.asset); if (negativeBalance === undefined) { negativeBalance = { first: { balance: ledger.balance, time: chunk.time }, minimum: { balance: ledger.balance, time: chunk.time } }; this._negativeBalanceByAsset.set(chunk.asset, negativeBalance); } else if (ledger.balance < negativeBalance.minimum.balance) negativeBalance.minimum = { balance: ledger.balance, time: chunk.time }; } // Clear the ACB when the balance is negative. if (ledger.balance <= 0) ledger.acb = 0; callback(); } _final(callback) { // Calculate the aggregate disposition of each asset. for (let ledger of this._ledgerByAsset.values()) ledger.aggregateDisposition = ledger.dispositions.reduce(function (a, b) { return { amount: a.amount + b.amount, acb: a.acb + b.acb, pod: a.pod + b.pod, oae: a.oae + b.oae, gain: a.gain + b.gain }; }, { amount: 0, acb: 0, pod: 0, oae: 0, gain: 0 }); // Calculate the aggregate disposition of all assets. let aggregateDisposition = Array.from(this._ledgerByAsset.values(), function (ledger) { return ledger.aggregateDisposition; }).reduce(function (a, b) { return { amount: a.amount + b.amount, acb: a.acb + b.acb, pod: a.pod + b.pod, oae: a.oae + b.oae, gain: a.gain + b.gain }; }, { amount: 0, acb: 0, pod: 0, oae: 0, gain: 0 }); this.push({ forwardByAsset: this._forwardByAsset, trades: this._trades, ledgerByAsset: this._ledgerByAsset, negativeBalanceByAsset: this._negativeBalanceByAsset, aggregateDisposition, taxableGain: aggregateDisposition.gain / 2 // Capital gains are taxable at 50%. }); callback(); } } function _default(...args) { return new CapitalGainsCalculateStream(...args); }