@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
JavaScript
;
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;
}