UNPKG

@davidosborn/crypto-tax-calculator

Version:

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

192 lines (150 loc) 8.28 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = main; var _getopt = _interopRequireWildcard(require("@davidosborn/getopt")); var _mergeSortStream = _interopRequireDefault(require("@davidosborn/merge-sort-stream")); var _csvParse = _interopRequireDefault(require("csv-parse")); var _fromentries = _interopRequireDefault(require("fromentries")); var _fs = _interopRequireDefault(require("fs")); var _lineStream = _interopRequireDefault(require("line-stream")); var _multistream = _interopRequireDefault(require("multistream")); var _process = _interopRequireDefault(require("process")); var _sortStream = _interopRequireDefault(require("sort-stream2")); var _takeStream = _interopRequireDefault(require("take-stream")); var _toUtf = _interopRequireDefault(require("to-utf-8")); var _assets = _interopRequireDefault(require("./assets")); var _capitalGainsCalculateStream = _interopRequireDefault(require("./capital-gains-calculate-stream")); var _capitalGainsFormatStream = _interopRequireDefault(require("./capital-gains-format-stream")); var _loadHistory = _interopRequireDefault(require("./load-history")); var _markedStream = _interopRequireDefault(require("./marked-stream")); var _csvNormalizeStream = _interopRequireDefault(require("./csv-normalize-stream")); var _tradeFilterStream = _interopRequireDefault(require("./trade-filter-stream")); var _tradeParseStream = _interopRequireDefault(require("./trade-parse-stream")); var _tradeTransactionsStream = _interopRequireDefault(require("./trade-transactions-stream")); var _tradeValueStream = _interopRequireDefault(require("./trade-value-stream")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function main(args) { var _opts$options$output, _ref, _opts$options$history; // Parse the arguments. let opts = (0, _getopt.default)(args, { options: [{ short: 'a', long: ['assets', 'asset'], argument: 'spec', description: 'Only consider trades involving the specified assets.' }, { short: 'h', long: 'help', description: 'Display this usage information and exit.', callback: _getopt.usage }, { short: 'i', long: 'init', argument: 'spec', description: 'Define the initial balance and ACB of the assets.' }, { short: 'm', long: 'html', description: 'Format the results as HTML instead of Markdown.' }, { short: 'o', long: 'output', argument: 'file', description: 'Write the results to the specified file.' }, { short: 'q', long: 'quiet', description: 'Do not write the results.' }, { short: 's', long: 'show', argument: 'spec', description: 'Only show the specified assets.' }, { short: 't', long: 'take', argument: 'count', description: 'Do not process more than the specified number of trades.' }, { short: 'v', long: 'verbose', description: 'Write extra information to the console.' }, { short: 'w', long: 'web', description: 'Request historical asset values from the internet.' }, { short: 'y', long: 'history', argument: 'path', description: 'Read historical asset values from the specified directory.' }], usage: { footer: _fs.default.readFileSync(__dirname + '/../res/cmdline_footer.txt', 'utf8').replace(/([.,;:])\r?\n([A-Za-z])/g, '$1 $2'), header: _fs.default.readFileSync(__dirname + '/../res/cmdline_header.txt', 'utf8').replace(/([.,;:])\r?\n([A-Za-z])/g, '$1 $2'), program: 'crypto-tax-calculator', spec: '[option]... <csv-file>...' }, callback: function (opts, args, settings) { // Show the usage when there is no input. if (opts.parameters.length < 1 || !opts.parameters[0].value) (0, _getopt.usage)(settings); } }); let sources = opts.parameters.map(function (p) { return p.value; }); let destination = (_opts$options$output = opts.options.output) === null || _opts$options$output === void 0 ? void 0 : _opts$options$output.value; // Detect the output file as HTML. if (!('html' in opts.options) && ((destination === null || destination === void 0 ? void 0 : destination.endsWith('.html')) || (destination === null || destination === void 0 ? void 0 : destination.endsWith('.htm')))) opts.options.html = true; // Parse the assets to retain when filtering the trades. let assets = undefined; if (opts.options.assets) assets = new Set(opts.options.assets.value.split(',').map(_assets.default.normalizeCode)); // Parse the assets to show when filtering the results. let show = undefined; if (opts.options.show) show = new Set(opts.options.show.value.split(',').map(_assets.default.normalizeCode)); // Parse the initial balance and ACB of each asset to carry it forward from last year. let forwardByAsset = undefined; if (opts.options.init) { forwardByAsset = new Map(opts.options.init.value.split(',').map(function (spec) { let [asset, balance, acb] = spec.split(':'); return [asset, { balance: parseFloat(balance), acb: acb != null ? parseFloat(acb) : 0 }]; })); } // Load the historical data. let historyPath = (_ref = (_opts$options$history = opts.options.history) === null || _opts$options$history === void 0 ? void 0 : _opts$options$history.value) !== null && _ref !== void 0 ? _ref : __dirname + '/../history'; let history = historyPath ? (0, _loadHistory.default)(historyPath) : null; // Create a stream to calculate the capital gains. // This takes a few steps. let stream = (0, _mergeSortStream.default)(_compareTradeTime, sources.map(function (path) { return _fs.default.createReadStream(path).pipe((0, _toUtf.default)()).pipe((0, _lineStream.default)('\n')).pipe((0, _csvNormalizeStream.default)()).pipe((0, _csvParse.default)({ columns: true, skip_empty_lines: true })).pipe((0, _tradeParseStream.default)()).pipe((0, _sortStream.default)(_compareTradeTime)); })); // Limit the number of trades. if (opts.options.take) stream = stream.pipe((0, _takeStream.default)(parseInt(opts.options.take.value))); // Filter the trades by their assets. if (assets) stream = stream.pipe((0, _tradeFilterStream.default)({ assets })); // Calculate the capital gains. stream = new _multistream.default([_fs.default.createReadStream(__dirname + '/../res/output_header.md'), stream.pipe((0, _tradeValueStream.default)({ history, verbose: !!opts.options.verbose, web: !!opts.options.web })).pipe((0, _tradeTransactionsStream.default)()).pipe((0, _capitalGainsCalculateStream.default)({ assets, forwardByAsset })).pipe((0, _capitalGainsFormatStream.default)({ assets: show })), _fs.default.createReadStream(__dirname + '/../res/output_footer.md')]); // Convert the output from Markdown to HTML. if (opts.options.html) stream = stream.pipe((0, _markedStream.default)()); // Pipe the stream to the output file. if (!opts.options.quiet) stream.pipe(destination ? _fs.default.createWriteStream(destination) : _process.default.stdout); } /** * Compares trades by their time. * @param {object} a The first trade. * @param {object} b The second trade. * @returns {number} The result. */ function _compareTradeTime(a, b) { return a.time - b.time; }