@davidosborn/crypto-tax-calculator
Advanced tools
Comparing version 0.0.13 to 0.0.14
@@ -36,11 +36,11 @@ 'use strict'; | ||
* @typedef {object} CapitalGains | ||
* @property {array.<Trade>} trades The trades. | ||
* @property {object.<string, Ledger>} ledgerByAsset The ledger of each asset. | ||
* @property {Disposition} aggregateDisposition The aggregate disposition. | ||
* @property {number} taxableGain The taxable gain (or loss). | ||
* @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 {Disposition} aggregateDisposition The aggregate disposition. | ||
* @property {number} taxableGain The taxable gain (or loss). | ||
*/ | ||
/** | ||
* The initial balance and ACB of an asset. | ||
* This is typically carried forward from the previous year. | ||
* The initial balance and ACB of an asset that was carried forward from last year. | ||
* @typedef {object} Forward | ||
@@ -57,10 +57,19 @@ * @property {number} balance The balance. | ||
* Initializes a new instance. | ||
* @param {object} [options] The options. | ||
* @param {object.<string, Forward>} [options.forward] The initial balance and ACB of each asset. | ||
* @param {object} [options] The options. | ||
* @param {object.<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, Ledger>} | ||
*/ | ||
this._forwardByAsset = new Map(((_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.forwardByAsset) ? Object.entries(this._options.forwardByAsset) : []); | ||
/** | ||
* The trades. | ||
@@ -82,6 +91,6 @@ * @type {Trade} | ||
this._assetsWithNegativeBalance = new Set(); // Create the ledger for assets that have been carried forward from the previous year. | ||
this._assetsWithNegativeBalance = new Set(); // Initialize the ledger of the assets to carry forward from last year. | ||
if (options === null || options === void 0 ? void 0 : options.forward) { | ||
for (let [asset, forward] of Object.entries(options.forward)) { | ||
if (this._forwardByAsset) { | ||
for (let [asset, forward] of this._forwardByAsset) { | ||
this._ledgerByAsset.set(asset, { | ||
@@ -124,3 +133,3 @@ acb: forward.acb, | ||
pod: chunk.value, | ||
oae: chunk.fee, | ||
oae: chunk.feeValue, | ||
time: chunk.time | ||
@@ -132,6 +141,20 @@ }; | ||
ledger.acb += acbPerUnit * chunk.amount; | ||
} else ledger.acb += chunk.value + chunk.fee; | ||
} else ledger.acb += chunk.value + chunk.feeValue; // Update the balance. | ||
ledger.balance += chunk.amount; // Check whether the balance is negative, which would indicate an accounting error. | ||
ledger.balance += chunk.amount; // Remove the transaction fee from the balance. | ||
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 in the trade. | ||
chunk.balance = ledger.balance; // Check whether the balance is negative, which would indicate an accounting error. | ||
if (ledger.balance < -0.000000005 && !this._assetsWithNegativeBalance.has(chunk.asset)) { | ||
@@ -183,2 +206,3 @@ this._assetsWithNegativeBalance.add(chunk.asset); | ||
this.push({ | ||
forwardByAsset: this._forwardByAsset, | ||
trades: this._trades, | ||
@@ -185,0 +209,0 @@ ledgerByAsset: this._ledgerByAsset, |
@@ -52,3 +52,14 @@ 'use strict'; | ||
_transform(chunk, encoding, callback) { | ||
// Write the trades. | ||
// Write the balance that was carried forward from last year. | ||
if (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(); | ||
@@ -60,3 +71,3 @@ | ||
this._pushLine((0, _markdownTable.default)([['Asset', 'Units acquired (or disposed)', 'Value', 'Fee', 'Date', 'Exchange']].concat(Array.from(chunk.trades, trade => [trade.asset, this._formatAmount(trade.amount), this._formatValue(trade.value), this._formatValue(trade.fee), this._formatDate(trade.time), trade.exchange])))); // Sort the assets. | ||
this._pushLine((0, _markdownTable.default)([['Asset', 'Units acquired', 'Value', 'Balance', 'Fee', 'Fee asset', 'Date', 'Exchange']].concat(Array.from(chunk.trades, trade => [trade.asset, this._formatAmount(trade.amount), this._formatValue(trade.value), this._formatAmount(trade.balance), this._formatAmount(trade.feeAmount), trade.feeAsset, this._formatDate(trade.time), trade.exchange])))); // Sort the assets. | ||
@@ -75,5 +86,7 @@ | ||
this._pushLine((0, _markdownTable.default)([['Asset', 'Units', 'Proceeds of disposition', 'Adjusted cost base', 'Outlays and expenses', 'Gain (or loss)', 'Date', '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), this._formatDate(disposition.time), disposition.exchange]))))); // Write the aggregate disposition per asset. | ||
this._pushLine((0, _markdownTable.default)([['Asset', 'Units', 'Proceeds of disposition', 'Adjusted cost base', 'Outlays and expenses', 'Gain (or loss)', 'Date', '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), this._formatDate(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(); | ||
@@ -85,3 +98,3 @@ | ||
this._pushLine((0, _markdownTable.default)([['Asset', 'Units', 'Proceeds of disposition', 'Adjusted cost base', 'Outlays and expenses', 'Gain (or loss)']].concat(ledgerByAsset.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((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. | ||
@@ -112,7 +125,7 @@ | ||
this._pushLine('## Carry forward'); | ||
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 the previous year\'s balance and adjusted cost base.'); | ||
this._pushLine('The following specification can be passed to the calculator next year to carry forward this year\'s balance and adjusted cost base.'); | ||
@@ -123,3 +136,3 @@ this._pushLine(); | ||
this._pushLine('--forward=\\'); | ||
this._pushLine('--init=\\'); | ||
@@ -161,3 +174,6 @@ for (let [asset, ledger] of ledgerByAssetWithBalance) { | ||
day: 'numeric', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
month: 'short', | ||
second: '2-digit', | ||
year: 'numeric' | ||
@@ -164,0 +180,0 @@ }); |
@@ -28,2 +28,4 @@ 'use strict'; | ||
var _assets = _interopRequireDefault(require("./assets")); | ||
var _capitalGainsCalculateStream = _interopRequireDefault(require("./capital-gains-calculate-stream")); | ||
@@ -39,6 +41,8 @@ | ||
var _tradeSeparateStream = _interopRequireDefault(require("./trade-separate-stream")); | ||
var _tradeTransactionsStream = _interopRequireDefault(require("./trade-transactions-stream")); | ||
var _tradeValueStream = _interopRequireDefault(require("./trade-value-stream")); | ||
var _transactionFilterStream = _interopRequireDefault(require("./transaction-filter-stream")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -54,6 +58,6 @@ | ||
options: [{ | ||
short: 'f', | ||
long: 'forward', | ||
short: 'a', | ||
long: ['assets', 'asset'], | ||
argument: 'spec', | ||
description: 'Define the initial balance and ACB of the assets.' | ||
description: 'Only consider the specified assets.' | ||
}, { | ||
@@ -65,2 +69,7 @@ short: 'h', | ||
}, { | ||
short: 'i', | ||
long: 'init', | ||
argument: 'spec', | ||
description: 'Define the initial balance and ACB of the assets.' | ||
}, { | ||
short: 'm', | ||
@@ -98,4 +107,4 @@ long: 'html', | ||
usage: { | ||
footer: _fs.default.readFileSync(__dirname + '/../res/cmdline_footer.txt', 'utf8').replace(/([.,;:])\r?\n/g, '$1 '), | ||
header: _fs.default.readFileSync(__dirname + '/../res/cmdline_header.txt', 'utf8').replace(/([.,;:])\r?\n/g, '$1 '), | ||
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', | ||
@@ -114,6 +123,6 @@ spec: '[option]... <csv-file>...' | ||
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 initial balance and ACB of each asset. | ||
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 initial balance and ACB of each asset to carry it forward from last year. | ||
let forward = null; | ||
if (opts.options.forward) forward = (0, _fromentries.default)(opts.options.forward.value.split(',').map(function (spec) { | ||
let forwardByAsset = null; | ||
if (opts.options.init) forwardByAsset = (0, _fromentries.default)(opts.options.init.value.split(',').map(function (spec) { | ||
let [asset, balance, acb] = spec.split(':'); | ||
@@ -137,10 +146,15 @@ return [asset, { | ||
if (opts.options.take) stream = stream.pipe((0, _takeStream.default)(parseInt(opts.options.take.value))); // Continue creating the stream. | ||
stream = (0, _multistream.default)([_fs.default.createReadStream(__dirname + '/../res/output_header.md'), stream.pipe((0, _tradeValueStream.default)({ | ||
if (opts.options.take) stream = stream.pipe((0, _takeStream.default)(parseInt(opts.options.take.value))); | ||
stream = stream.pipe((0, _tradeValueStream.default)({ | ||
history, | ||
verbose: !!opts.options.verbose, | ||
web: !!opts.options.web | ||
})).pipe((0, _tradeSeparateStream.default)()).pipe((0, _capitalGainsCalculateStream.default)({ | ||
forward | ||
})).pipe((0, _tradeTransactionsStream.default)()); // Limit the assets. | ||
if (opts.options.assets) stream = stream.pipe((0, _transactionFilterStream.default)({ | ||
assets: new Set(opts.options.assets.value.split(',').map(_assets.default.normalizeCode)) | ||
})); // Calculate the capital gains. | ||
stream = (0, _multistream.default)([_fs.default.createReadStream(__dirname + '/../res/output_header.md'), stream.pipe((0, _capitalGainsCalculateStream.default)({ | ||
forwardByAsset | ||
})).pipe((0, _capitalGainsFormatStream.default)()), _fs.default.createReadStream(__dirname + '/../res/output_footer.md')]); // Convert the output from Markdown to HTML. | ||
@@ -147,0 +161,0 @@ |
@@ -28,8 +28,8 @@ 'use strict'; | ||
* @property {number} quoteAmount The amount of the quote currency. | ||
* @property {number} [value] The value of the assets, in Canadian dollars. | ||
* @property {boolean} sell True if the trade represents a sale. | ||
* @property {number} time The time at which the trade occurred, as a UNIX timestamp. | ||
* @property {string} feeAsset The currency of the trading fee. | ||
* @property {number} feeAmount The amount of the trading fee. | ||
* @property {number} [value] The value of the assets, in Canadian dollars. | ||
* @property {number} [fee] The value of the trading fee, in Canadian dollars. | ||
* @property {string} feeAsset The currency of the transaction fee. | ||
* @property {number} feeAmount The amount of the transaction fee. | ||
* @property {number} [feeValue] The value of the transaction fee, in Canadian dollars. | ||
*/ | ||
@@ -137,2 +137,7 @@ | ||
quoteAsset = _assets.default.normalizeCode(quoteAsset); | ||
let quantity = TradeParseStream._parseNumber(chunk.Quantity); | ||
let quantityRemaining = TradeParseStream._parseNumber(chunk.QuantityRemaining); | ||
this.push({ | ||
@@ -143,3 +148,3 @@ exchange: 'Bittrex', | ||
baseAmount: TradeParseStream._parseNumber(chunk.Price), | ||
quoteAmount: TradeParseStream._parseNumber(chunk.Quantity), | ||
quoteAmount: quantity - quantityRemaining, | ||
sell: chunk.OrderType.includes('SELL'), | ||
@@ -158,38 +163,42 @@ time: TradeParseStream._parseTime(chunk.TimeStamp), | ||
async _transformKraken(chunk) { | ||
let chunks = this._tradeChunks; | ||
let chunks = this._tradeChunks; // We only care about trades. | ||
if (chunk.type === 'trade') { | ||
// Normalize the properties of the chunk. | ||
chunk = { | ||
asset: _assets.default.normalizeCode(chunk.asset), | ||
amount: TradeParseStream._parseNumber(chunk.amount), | ||
time: TradeParseStream._parseTime(chunk.time), | ||
fee: TradeParseStream._parseNumber(chunk.fee) // Process two consecutive trade chunks as a single trade. | ||
if (chunk.type !== 'trade') { | ||
if (chunks.length > 0) { | ||
console.log('WARNING: Found unpaired trade chunk.'); | ||
chunks.length = 0; | ||
} | ||
}; | ||
chunks.push(chunk); | ||
return; | ||
} // Normalize the properties of the chunk. | ||
if (chunks.length === 2) { | ||
// Ensure the chunks have the same timestamp. | ||
if (chunks[0].time !== chunks[1].time) console.log('WARNING: Found paired trade chunks with different timestamps.'); // Determine which chunks represent the base and quote of the currency pair. | ||
let priorities = chunks.map(c => _assets.default.getPriority(c.asset)); | ||
let isCurrencyPairReversed = priorities[0] < priorities[1]; | ||
let baseChunk = chunks[+!isCurrencyPairReversed]; | ||
let quoteChunk = chunks[+isCurrencyPairReversed]; | ||
this.push({ | ||
exchange: 'Kraken', | ||
baseAsset: baseChunk.asset, | ||
quoteAsset: quoteChunk.asset, | ||
baseAmount: Math.abs(baseChunk.amount), | ||
quoteAmount: Math.abs(quoteChunk.amount), | ||
sell: baseChunk.amount > 0, | ||
time: baseChunk.time, | ||
feeAsset: baseChunk.asset, | ||
feeAmount: baseChunk.fee | ||
}); | ||
chunks.length = 0; | ||
} | ||
} else if (chunks.length > 0) { | ||
console.log('WARNING: Found unpaired trade chunk.'); | ||
chunk = { | ||
asset: _assets.default.normalizeCode(chunk.asset), | ||
amount: TradeParseStream._parseNumber(chunk.amount), | ||
time: TradeParseStream._parseTime(chunk.time), | ||
fee: TradeParseStream._parseNumber(chunk.fee) // Process two consecutive trade chunks as a single trade. | ||
}; | ||
chunks.push(chunk); | ||
if (chunks.length === 2) { | ||
// Ensure the chunks have the same timestamp. | ||
if (chunks[0].time !== chunks[1].time) console.log('WARNING: Found paired trade chunks with different timestamps.'); // Determine which chunks represent the base and quote of the currency pair. | ||
let priorities = chunks.map(c => _assets.default.getPriority(c.asset)); | ||
let isCurrencyPairReversed = priorities[0] < priorities[1]; | ||
let baseChunk = chunks[+!isCurrencyPairReversed]; | ||
let quoteChunk = chunks[+isCurrencyPairReversed]; | ||
this.push({ | ||
exchange: 'Kraken', | ||
baseAsset: baseChunk.asset, | ||
quoteAsset: quoteChunk.asset, | ||
baseAmount: Math.abs(baseChunk.amount), | ||
quoteAmount: Math.abs(quoteChunk.amount), | ||
sell: baseChunk.amount > 0, | ||
time: baseChunk.time, | ||
feeAsset: baseChunk.asset, | ||
feeAmount: baseChunk.fee | ||
}); | ||
chunks.length = 0; | ||
@@ -196,0 +205,0 @@ } |
@@ -58,3 +58,3 @@ 'use strict'; | ||
chunk.fee = chunk.feeAsset === chunk.baseAsset ? chunk.value * chunk.feeAmount / chunk.baseAmount : chunk.feeAsset === chunk.quoteAsset ? chunk.value * chunk.feeAmount / chunk.quoteAmount : await this._getValue(chunk.feeAsset, chunk.feeAmount, chunk.time); | ||
chunk.feeValue = chunk.feeAsset === chunk.baseAsset ? chunk.value * chunk.feeAmount / chunk.baseAmount : chunk.feeAsset === chunk.quoteAsset ? chunk.value * chunk.feeAmount / chunk.quoteAmount : await this._getValue(chunk.feeAsset, chunk.feeAmount, chunk.time); | ||
this.push(chunk); | ||
@@ -61,0 +61,0 @@ callback(); |
{ | ||
"name": "@davidosborn/crypto-tax-calculator", | ||
"version": "0.0.13", | ||
"version": "0.0.14", | ||
"description": "A tool to calculate the capital gains of cryptocurrency assets for Canadian taxes", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
The initial balance of the assets can be defined by the '--balance' option. | ||
The argument is a comma-separated sequence of assets and their balances, | ||
in the form of '<asset>:<balance>,...', such as '--balance=BTC:1,ETH:5,LTC:10'. | ||
The calculation can be limited to a subset of the available assets by the '--assets' option. | ||
The argument is a comma-separated list of assets. | ||
The balances can be carried forward from last year by the '--init' option. | ||
The argument is a comma-separated list of assets, each with its balance and adjusted cost base, | ||
in the form of '<asset>:<balance>:<acb>,...', such as '--init=BTC:1:5000,ETH:5:1000,LTC:10:80'. |
@@ -26,10 +26,10 @@ 'use strict' | ||
* @typedef {object} CapitalGains | ||
* @property {array.<Trade>} trades The trades. | ||
* @property {object.<string, Ledger>} ledgerByAsset The ledger of each asset. | ||
* @property {Disposition} aggregateDisposition The aggregate disposition. | ||
* @property {number} taxableGain The taxable gain (or loss). | ||
* @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 {Disposition} aggregateDisposition The aggregate disposition. | ||
* @property {number} taxableGain The taxable gain (or loss). | ||
*/ | ||
/** | ||
* The initial balance and ACB of an asset. | ||
* This is typically carried forward from the previous year. | ||
* The initial balance and ACB of an asset that was carried forward from last year. | ||
* @typedef {object} Forward | ||
@@ -46,4 +46,4 @@ * @property {number} balance The balance. | ||
* Initializes a new instance. | ||
* @param {object} [options] The options. | ||
* @param {object.<string, Forward>} [options.forward] The initial balance and ACB of each asset. | ||
* @param {object} [options] The options. | ||
* @param {object.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. | ||
*/ | ||
@@ -55,3 +55,13 @@ constructor(options) { | ||
this._options = options | ||
/** | ||
* The assets to carry forward from last year. | ||
* @type {Map.<string, Ledger>} | ||
*/ | ||
this._forwardByAsset = new Map(this._options?.forwardByAsset | ||
? Object.entries(this._options.forwardByAsset) | ||
: []) | ||
/** | ||
* The trades. | ||
@@ -74,5 +84,5 @@ * @type {Trade} | ||
// Create the ledger for assets that have been carried forward from the previous year. | ||
if (options?.forward) { | ||
for (let [asset, forward] of Object.entries(options.forward)) { | ||
// Initialize the ledger of the assets to carry forward from last year. | ||
if (this._forwardByAsset) { | ||
for (let [asset, forward] of this._forwardByAsset) { | ||
this._ledgerByAsset.set(asset, { | ||
@@ -115,3 +125,3 @@ acb: forward.acb, | ||
pod: chunk.value, | ||
oae: chunk.fee, | ||
oae: chunk.feeValue, | ||
time: chunk.time | ||
@@ -126,6 +136,20 @@ } | ||
else | ||
ledger.acb += chunk.value + chunk.fee | ||
ledger.acb += chunk.value + chunk.feeValue | ||
// Update the balance. | ||
ledger.balance += chunk.amount | ||
// Remove the transaction fee from the balance. | ||
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 in the trade. | ||
chunk.balance = ledger.balance | ||
// Check whether the balance is negative, which would indicate an accounting error. | ||
@@ -183,2 +207,3 @@ if (ledger.balance < -0.000000005 && !this._assetsWithNegativeBalance.has(chunk.asset)) { | ||
this.push({ | ||
forwardByAsset: this._forwardByAsset, | ||
trades: this._trades, | ||
@@ -185,0 +210,0 @@ ledgerByAsset: this._ledgerByAsset, |
@@ -46,2 +46,20 @@ 'use strict' | ||
_transform(chunk, encoding, callback) { | ||
// Write the balance that was carried forward from last year. | ||
if (chunk.forwardByAsset.size) { | ||
this._pushLine() | ||
this._pushLine('## Carried forward from last year') | ||
this._pushLine() | ||
this._pushLine(markdownTable( | ||
[[ | ||
'Asset', | ||
'Balance', | ||
'Adjusted cost base' | ||
]] | ||
.concat(Array.from(chunk.forwardByAsset, ([asset, forward]) => [ | ||
asset, | ||
this._formatAmount(forward.balance), | ||
this._formatValue(forward.acb) | ||
])))) | ||
} | ||
// Write the trades. | ||
@@ -54,5 +72,7 @@ this._pushLine() | ||
'Asset', | ||
'Units acquired (or disposed)', | ||
'Units acquired', | ||
'Value', | ||
'Balance', | ||
'Fee', | ||
'Fee asset', | ||
'Date', | ||
@@ -65,3 +85,5 @@ 'Exchange' | ||
this._formatValue(trade.value), | ||
this._formatValue(trade.fee), | ||
this._formatAmount(trade.balance), | ||
this._formatAmount(trade.feeAmount), | ||
trade.feeAsset, | ||
this._formatDate(trade.time), | ||
@@ -105,2 +127,6 @@ trade.exchange | ||
// Find the assets with dispositions. | ||
let ledgerByAssetWithDisposition = ledgerByAsset | ||
.filter(([asset, ledger]) => ledger.dispositions.length) | ||
// Write the aggregate disposition per asset. | ||
@@ -119,3 +145,3 @@ this._pushLine() | ||
]] | ||
.concat(ledgerByAsset.map(([asset, ledger]) => [ | ||
.concat(ledgerByAssetWithDisposition.map(([asset, ledger]) => [ | ||
asset, | ||
@@ -169,8 +195,8 @@ this._formatAmount(ledger.aggregateDisposition.amount), | ||
this._pushLine() | ||
this._pushLine('## Carry forward') | ||
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 the previous year\'s balance and adjusted cost base.') | ||
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('--forward=\\') | ||
this._pushLine('--init=\\') | ||
for (let [asset, ledger] of ledgerByAssetWithBalance) { | ||
@@ -208,3 +234,6 @@ let last = asset === ledgerByAssetWithBalance[ledgerByAssetWithBalance.length - 1][0] | ||
day: 'numeric', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
month: 'short', | ||
second: '2-digit', | ||
year: 'numeric' | ||
@@ -211,0 +240,0 @@ }) |
@@ -13,2 +13,3 @@ 'use strict' | ||
import utf8 from 'to-utf-8' | ||
import Assets from './assets' | ||
import capitalGainsCalculateStream from './capital-gains-calculate-stream' | ||
@@ -19,4 +20,5 @@ import capitalGainsFormatStream from './capital-gains-format-stream' | ||
import tradeParseStream from './trade-parse-stream' | ||
import tradeSeparateStream from './trade-separate-stream' | ||
import tradeTransactionsStream from './trade-transactions-stream' | ||
import tradeValueStream from './trade-value-stream' | ||
import transactionFilterStream from './transaction-filter-stream' | ||
@@ -28,6 +30,6 @@ export default function main(args) { | ||
{ | ||
short: 'f', | ||
long: 'forward', | ||
short: 'a', | ||
long: ['assets', 'asset'], | ||
argument: 'spec', | ||
description: 'Define the initial balance and ACB of the assets.' | ||
description: 'Only consider the specified assets.' | ||
}, | ||
@@ -41,2 +43,8 @@ { | ||
{ | ||
short: 'i', | ||
long: 'init', | ||
argument: 'spec', | ||
description: 'Define the initial balance and ACB of the assets.' | ||
}, | ||
{ | ||
short: 'm', | ||
@@ -81,4 +89,4 @@ long: 'html', | ||
usage: { | ||
footer: fs.readFileSync(__dirname + '/../res/cmdline_footer.txt', 'utf8').replace(/([.,;:])\r?\n/g, '$1 '), | ||
header: fs.readFileSync(__dirname + '/../res/cmdline_header.txt', 'utf8').replace(/([.,;:])\r?\n/g, '$1 '), | ||
footer: fs.readFileSync(__dirname + '/../res/cmdline_footer.txt', 'utf8').replace(/([.,;:])\r?\n([A-Za-z])/g, '$1 $2'), | ||
header: fs.readFileSync(__dirname + '/../res/cmdline_header.txt', 'utf8').replace(/([.,;:])\r?\n([A-Za-z])/g, '$1 $2'), | ||
program: 'crypto-tax-calculator', | ||
@@ -101,6 +109,6 @@ spec: '[option]... <csv-file>...' | ||
// Parse the initial balance and ACB of each asset. | ||
let forward = null | ||
if (opts.options.forward) | ||
forward = fromEntries(opts.options.forward.value.split(',') | ||
// Parse the initial balance and ACB of each asset to carry it forward from last year. | ||
let forwardByAsset = null | ||
if (opts.options.init) | ||
forwardByAsset = fromEntries(opts.options.init.value.split(',') | ||
.map(function(spec) { | ||
@@ -136,14 +144,22 @@ let [asset, balance, acb] = spec.split(':') | ||
// Continue creating the stream. | ||
stream = stream | ||
.pipe(tradeValueStream({ | ||
history, | ||
verbose: !!opts.options.verbose, | ||
web: !!opts.options.web | ||
})) | ||
.pipe(tradeTransactionsStream()) | ||
// Limit the assets. | ||
if (opts.options.assets) | ||
stream = stream.pipe(transactionFilterStream({ | ||
assets: new Set(opts.options.assets.value.split(',').map(Assets.normalizeCode)) | ||
})) | ||
// Calculate the capital gains. | ||
stream = multiStream([ | ||
fs.createReadStream(__dirname + '/../res/output_header.md'), | ||
stream | ||
.pipe(tradeValueStream({ | ||
history, | ||
verbose: !!opts.options.verbose, | ||
web: !!opts.options.web | ||
})) | ||
.pipe(tradeSeparateStream()) | ||
.pipe(capitalGainsCalculateStream({ | ||
forward | ||
forwardByAsset | ||
})) | ||
@@ -150,0 +166,0 @@ .pipe(capitalGainsFormatStream()), |
@@ -16,8 +16,8 @@ 'use strict' | ||
* @property {number} quoteAmount The amount of the quote currency. | ||
* @property {number} [value] The value of the assets, in Canadian dollars. | ||
* @property {boolean} sell True if the trade represents a sale. | ||
* @property {number} time The time at which the trade occurred, as a UNIX timestamp. | ||
* @property {string} feeAsset The currency of the trading fee. | ||
* @property {number} feeAmount The amount of the trading fee. | ||
* @property {number} [value] The value of the assets, in Canadian dollars. | ||
* @property {number} [fee] The value of the trading fee, in Canadian dollars. | ||
* @property {string} feeAsset The currency of the transaction fee. | ||
* @property {number} feeAmount The amount of the transaction fee. | ||
* @property {number} [feeValue] The value of the transaction fee, in Canadian dollars. | ||
*/ | ||
@@ -135,2 +135,5 @@ | ||
let quantity = TradeParseStream._parseNumber(chunk.Quantity) | ||
let quantityRemaining = TradeParseStream._parseNumber(chunk.QuantityRemaining) | ||
this.push({ | ||
@@ -141,3 +144,3 @@ exchange: 'Bittrex', | ||
baseAmount: TradeParseStream._parseNumber(chunk.Price), | ||
quoteAmount: TradeParseStream._parseNumber(chunk.Quantity), | ||
quoteAmount: quantity - quantityRemaining, | ||
sell: chunk.OrderType.includes('SELL'), | ||
@@ -157,41 +160,44 @@ time: TradeParseStream._parseTime(chunk.TimeStamp), | ||
if (chunk.type === 'trade') { | ||
// Normalize the properties of the chunk. | ||
chunk = { | ||
asset: Assets.normalizeCode(chunk.asset), | ||
amount: TradeParseStream._parseNumber(chunk.amount), | ||
time: TradeParseStream._parseTime(chunk.time), | ||
fee: TradeParseStream._parseNumber(chunk.fee) | ||
// We only care about trades. | ||
if (chunk.type !== 'trade') { | ||
if (chunks.length > 0) { | ||
console.log('WARNING: Found unpaired trade chunk.') | ||
chunks.length = 0 | ||
} | ||
return | ||
} | ||
// Process two consecutive trade chunks as a single trade. | ||
chunks.push(chunk) | ||
if (chunks.length === 2) { | ||
// Ensure the chunks have the same timestamp. | ||
if (chunks[0].time !== chunks[1].time) | ||
console.log('WARNING: Found paired trade chunks with different timestamps.') | ||
// Normalize the properties of the chunk. | ||
chunk = { | ||
asset: Assets.normalizeCode(chunk.asset), | ||
amount: TradeParseStream._parseNumber(chunk.amount), | ||
time: TradeParseStream._parseTime(chunk.time), | ||
fee: TradeParseStream._parseNumber(chunk.fee) | ||
} | ||
// Determine which chunks represent the base and quote of the currency pair. | ||
let priorities = chunks.map(c => Assets.getPriority(c.asset)) | ||
let isCurrencyPairReversed = priorities[0] < priorities[1] | ||
let baseChunk = chunks[+!isCurrencyPairReversed] | ||
let quoteChunk = chunks[+isCurrencyPairReversed] | ||
// Process two consecutive trade chunks as a single trade. | ||
chunks.push(chunk) | ||
if (chunks.length === 2) { | ||
// Ensure the chunks have the same timestamp. | ||
if (chunks[0].time !== chunks[1].time) | ||
console.log('WARNING: Found paired trade chunks with different timestamps.') | ||
this.push({ | ||
exchange: 'Kraken', | ||
baseAsset: baseChunk.asset, | ||
quoteAsset: quoteChunk.asset, | ||
baseAmount: Math.abs(baseChunk.amount), | ||
quoteAmount: Math.abs(quoteChunk.amount), | ||
sell: baseChunk.amount > 0, | ||
time: baseChunk.time, | ||
feeAsset: baseChunk.asset, | ||
feeAmount: baseChunk.fee | ||
}) | ||
// Determine which chunks represent the base and quote of the currency pair. | ||
let priorities = chunks.map(c => Assets.getPriority(c.asset)) | ||
let isCurrencyPairReversed = priorities[0] < priorities[1] | ||
let baseChunk = chunks[+!isCurrencyPairReversed] | ||
let quoteChunk = chunks[+isCurrencyPairReversed] | ||
chunks.length = 0 | ||
} | ||
} | ||
else if (chunks.length > 0) { | ||
console.log('WARNING: Found unpaired trade chunk.') | ||
this.push({ | ||
exchange: 'Kraken', | ||
baseAsset: baseChunk.asset, | ||
quoteAsset: quoteChunk.asset, | ||
baseAmount: Math.abs(baseChunk.amount), | ||
quoteAmount: Math.abs(quoteChunk.amount), | ||
sell: baseChunk.amount > 0, | ||
time: baseChunk.time, | ||
feeAsset: baseChunk.asset, | ||
feeAmount: baseChunk.fee | ||
}) | ||
chunks.length = 0 | ||
@@ -198,0 +204,0 @@ } |
@@ -45,3 +45,3 @@ 'use strict' | ||
// Calculate the value of the fee. | ||
chunk.fee = ( | ||
chunk.feeValue = ( | ||
chunk.feeAsset === chunk.baseAsset ? chunk.value * chunk.feeAmount / chunk.baseAmount : | ||
@@ -48,0 +48,0 @@ chunk.feeAsset === chunk.quoteAsset ? chunk.value * chunk.feeAmount / chunk.quoteAmount : |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
89495
35
2342