@davidosborn/crypto-tax-calculator
Advanced tools
Comparing version 0.0.14 to 0.0.15
@@ -36,3 +36,3 @@ 'use strict'; | ||
* @typedef {object} CapitalGains | ||
* @property {Map.<string, Forward>} forwardByAsset The assets that were carried forward from last year. | ||
* @property {Map.<string, Forward>} [forwardByAsset] The assets that were carried forward from last year. | ||
* @property {array.<Trade>} trades The trades. | ||
@@ -57,4 +57,5 @@ * @property {Map.<string, Ledger>} ledgerByAsset The ledger of each asset. | ||
* Initializes a new instance. | ||
* @param {object} [options] The options. | ||
* @param {object.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. | ||
* @param {object} [options] The options. | ||
* @param {Set.<string>} [options.assets] The assets to retain. | ||
* @param {Map.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. | ||
*/ | ||
@@ -70,6 +71,6 @@ constructor(options) { | ||
* The assets to carry forward from last year. | ||
* @type {Map.<string, Ledger>} | ||
* @type {Map.<string, Forward>} | ||
*/ | ||
this._forwardByAsset = new Map(((_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.forwardByAsset) ? Object.entries(this._options.forwardByAsset) : []); | ||
this._forwardByAsset = (_this$_options = this._options) === null || _this$_options === void 0 ? void 0 : _this$_options.forwardByAsset; | ||
/** | ||
@@ -94,11 +95,7 @@ * The trades. | ||
if (this._forwardByAsset) { | ||
for (let [asset, forward] of this._forwardByAsset) { | ||
this._ledgerByAsset.set(asset, { | ||
acb: forward.acb, | ||
balance: forward.balance, | ||
dispositions: [] | ||
}); | ||
} | ||
} | ||
if (this._forwardByAsset) for (let [asset, forward] of this._forwardByAsset.entries()) this._ledgerByAsset.set(asset, { | ||
acb: forward.acb, | ||
balance: forward.balance, | ||
dispositions: [] | ||
}); | ||
} | ||
@@ -125,6 +122,3 @@ /** | ||
if (chunk.amount < 0) { | ||
if (!ledger.balance) { | ||
console.log('WARNING: Disposition of ' + chunk.asset + ' from an empty balance on ' + (0, _formatTime.default)(chunk.time) + '.'); | ||
} | ||
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; | ||
@@ -145,3 +139,3 @@ let disposition = { | ||
ledger.balance += chunk.amount; // Remove the transaction fee from the balance. | ||
ledger.balance += chunk.amount; // Remove the transaction fee from the balance, except for fiat currencies. | ||
@@ -156,6 +150,7 @@ if (chunk.feeAmount) { | ||
} | ||
} // Record the balance in the trade. | ||
} // Record the balance and ACB in the trade for information purposes. | ||
chunk.balance = ledger.balance; // Check whether the balance is negative, which would indicate an accounting error. | ||
chunk.balance = ledger.balance; | ||
chunk.acb = ledger.acb; // Check whether the balance is negative, which would indicate an accounting error. | ||
@@ -165,5 +160,7 @@ if (ledger.balance < -0.000000005 && !this._assetsWithNegativeBalance.has(chunk.asset)) { | ||
console.log('WARNING: Encountered a negative balance for ' + chunk.asset + '.'); | ||
} | ||
console.log('WARNING: Encountered a negative balance for ' + chunk.asset + ' on ' + (0, _formatTime.default)(chunk.time) + '.'); | ||
} // Clear the ACB when the balance is negative. | ||
if (ledger.balance <= 0) ledger.acb = 0; | ||
callback(); | ||
@@ -212,3 +209,3 @@ } | ||
ledgerByAsset: this._ledgerByAsset, | ||
aggregateDisposition: aggregateDisposition, | ||
aggregateDisposition, | ||
taxableGain: aggregateDisposition.gain / 2 // Capital gains are taxable at 50%. | ||
@@ -215,0 +212,0 @@ |
@@ -10,6 +10,8 @@ 'use strict'; | ||
var _marked = _interopRequireDefault(require("marked")); | ||
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 }; } | ||
@@ -53,4 +55,6 @@ | ||
_transform(chunk, encoding, callback) { | ||
var _chunk$forwardByAsset; | ||
// Write the balance that was carried forward from last year. | ||
if (chunk.forwardByAsset.size) { | ||
if ((_chunk$forwardByAsset = chunk.forwardByAsset) === null || _chunk$forwardByAsset === void 0 ? void 0 : _chunk$forwardByAsset.size) { | ||
this._pushLine(); | ||
@@ -72,3 +76,3 @@ | ||
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. | ||
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 assets. | ||
@@ -87,3 +91,3 @@ | ||
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. | ||
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. | ||
@@ -155,2 +159,6 @@ | ||
_formatAssetAmount(asset, amount) { | ||
return _assets.default.getPriority(asset) !== 0 ? this._formatAmount(amount) : this._formatValue(amount); | ||
} | ||
_formatAmount(amount) { | ||
@@ -170,13 +178,2 @@ let amountString = this._amountFormat.format(Math.abs(amount)); | ||
_formatDate(time) { | ||
return new Date(time).toLocaleDateString('en-CA', { | ||
day: 'numeric', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
month: 'short', | ||
second: '2-digit', | ||
year: 'numeric' | ||
}); | ||
} | ||
_pushLine(line) { | ||
@@ -183,0 +180,0 @@ this.push(line); |
@@ -19,3 +19,3 @@ 'use strict'; | ||
minute: '2-digit', | ||
month: '2-digit', | ||
month: 'short', | ||
year: 'numeric' | ||
@@ -22,0 +22,0 @@ }); |
@@ -18,2 +18,4 @@ 'use strict'; | ||
var _lineStream = _interopRequireDefault(require("line-stream")); | ||
var _multistream = _interopRequireDefault(require("multistream")); | ||
@@ -39,2 +41,6 @@ | ||
var _csvNormalizeStream = _interopRequireDefault(require("./csv-normalize-stream")); | ||
var _tradeFilterStream = _interopRequireDefault(require("./trade-filter-stream")); | ||
var _tradeParseStream = _interopRequireDefault(require("./trade-parse-stream")); | ||
@@ -46,4 +52,2 @@ | ||
var _transactionFilterStream = _interopRequireDefault(require("./transaction-filter-stream")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -62,3 +66,3 @@ | ||
argument: 'spec', | ||
description: 'Only consider the specified assets.' | ||
description: 'Only consider trades involving the specified assets.' | ||
}, { | ||
@@ -77,3 +81,3 @@ short: 'h', | ||
long: 'html', | ||
description: 'Format the output as HTML instead of Markdown.' | ||
description: 'Format the results as HTML instead of Markdown.' | ||
}, { | ||
@@ -83,7 +87,7 @@ short: 'o', | ||
argument: 'file', | ||
description: 'Write the output to the specified file.' | ||
description: 'Write the results to the specified file.' | ||
}, { | ||
short: 'q', | ||
long: 'quiet', | ||
description: 'Do not produce any output.' | ||
description: 'Do not write the results.' | ||
}, { | ||
@@ -124,13 +128,20 @@ short: 't', | ||
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. | ||
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 forwardByAsset = null; | ||
if (opts.options.init) forwardByAsset = (0, _fromentries.default)(opts.options.init.value.split(',').map(function (spec) { | ||
let [asset, balance, acb] = spec.split(':'); | ||
return [asset, { | ||
balance: parseFloat(balance), | ||
acb: parseFloat(acb) | ||
}]; | ||
})); // Load the historical data. | ||
let assets = undefined; | ||
if (opts.options.assets) assets = new Set(opts.options.assets.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: parseFloat(acb) | ||
}]; | ||
})); | ||
} // 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'; | ||
@@ -141,3 +152,3 @@ let history = historyPath ? (0, _loadHistory.default)(historyPath) : null; // Create a stream to calculate the capital gains. | ||
let stream = (0, _mergeSortStream.default)(_compareTradeTime, sources.map(function (path) { | ||
return _fs.default.createReadStream(path).pipe((0, _toUtf.default)()).pipe((0, _csvParse.default)({ | ||
return _fs.default.createReadStream(path).pipe((0, _toUtf.default)()).pipe((0, _lineStream.default)()).pipe((0, _csvNormalizeStream.default)()).pipe((0, _csvParse.default)({ | ||
columns: true, | ||
@@ -148,14 +159,14 @@ skip_empty_lines: true | ||
if (opts.options.take) stream = stream.pipe((0, _takeStream.default)(parseInt(opts.options.take.value))); | ||
stream = stream.pipe((0, _tradeValueStream.default)({ | ||
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 = (0, _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)()); // 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)({ | ||
})).pipe((0, _tradeTransactionsStream.default)()).pipe((0, _capitalGainsCalculateStream.default)({ | ||
assets, | ||
forwardByAsset | ||
@@ -162,0 +173,0 @@ })).pipe((0, _capitalGainsFormatStream.default)()), _fs.default.createReadStream(__dirname + '/../res/output_footer.md')]); // Convert the output from Markdown to HTML. |
@@ -204,2 +204,38 @@ 'use strict'; | ||
/** | ||
* Transforms a CSV record from KuCoin into a trade. | ||
* @param {object} chunk The CSV record. | ||
*/ | ||
async _transformKuCoin(chunk) { | ||
// If this is not a trade, then drop it. | ||
let buySell = chunk['Buy/Sell']; | ||
if (buySell !== 'Buy' && buySell !== 'Sell') return; | ||
let [quoteAsset, baseAsset] = chunk.Coin.split('/'); | ||
baseAsset = _assets.default.normalizeCode(baseAsset); | ||
quoteAsset = _assets.default.normalizeCode(quoteAsset); | ||
const splitAmountAssetRegExp = /^([0-9.,]+)([A-Za-z][A-Za-z0-9]*)$/; | ||
let [amount, amountAsset] = chunk.Amount.match(splitAmountAssetRegExp).slice(1); | ||
let [feeAmount, feeAsset] = chunk.Fee.match(splitAmountAssetRegExp).slice(1); | ||
amountAsset = _assets.default.normalizeCode(amountAsset); | ||
feeAsset = _assets.default.normalizeCode(feeAsset); | ||
if (amountAsset !== quoteAsset) { | ||
console.log('WARNING: Expected amount of ' + quoteAsset + ' but found ' + amountAsset + ' instead.'); | ||
return; | ||
} | ||
this.push({ | ||
exchange: 'KuCoin', | ||
baseAsset: baseAsset, | ||
quoteAsset: quoteAsset, | ||
baseAmount: TradeParseStream._parseNumber(chunk['Filled Price']), | ||
quoteAmount: TradeParseStream._parseNumber(chunk.Amount), | ||
sell: buySell === 'Sell', | ||
time: TradeParseStream._parseTime(chunk.Time), | ||
feeAsset: baseAsset, | ||
feeAmount: feeAmount | ||
}); | ||
} | ||
/** | ||
* Parses a number. | ||
@@ -231,3 +267,4 @@ * @param {string} s The string. | ||
'Uuid|Exchange|TimeStamp|OrderType|Limit|Quantity|QuantityRemaining|Commission|Price|PricePerUnit|IsConditional|Condition|ConditionTarget|ImmediateOrCancel|Closed': TradeParseStream.prototype._transformBittrex2, | ||
'txid|refid|time|type|aclass|asset|amount|fee|balance': TradeParseStream.prototype._transformKraken | ||
'txid|refid|time|type|aclass|asset|amount|fee|balance': TradeParseStream.prototype._transformKraken, | ||
'Coin|Time|Buy/Sell|Filled Price|Amount|Fee|Volume': TradeParseStream.prototype._transformKuCoin | ||
/** | ||
@@ -234,0 +271,0 @@ * Initializes a new instance. |
@@ -17,10 +17,10 @@ 'use strict'; | ||
* @typedef {object} Transaction | ||
* @property {string} exchange The exchange on which the transaction was executed. | ||
* @property {string} asset The asset. | ||
* @property {number} amount The amount of assets. | ||
* @property {number} value The value of the transaction, in Canadian dollars. | ||
* @property {number} time The time of the transaction, as a UNIX timestamp. | ||
* @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. | ||
* @property {string} exchange The exchange on which the transaction was executed. | ||
* @property {string} asset The asset. | ||
* @property {number} amount The amount of assets. | ||
* @property {number} value The value of the transaction, in Canadian dollars. | ||
* @property {number} time The time of the transaction, as a UNIX timestamp. | ||
* @property {string} [feeAsset] The asset 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. | ||
*/ | ||
@@ -52,4 +52,2 @@ | ||
time: chunk.time, | ||
feeAsset: chunk.baseAsset, | ||
feeAmount: 0, | ||
feeValue: 0 | ||
@@ -63,4 +61,2 @@ }; | ||
time: chunk.time, | ||
feeAsset: chunk.quoteAsset, | ||
feeAmount: 0, | ||
feeValue: 0 | ||
@@ -71,22 +67,24 @@ }; | ||
if (chunk.sell) { | ||
quoteChunk.amount = -quoteChunk.amount; | ||
chunks.reverse(); | ||
} else { | ||
baseChunk.amount = -baseChunk.amount; | ||
} // Copy the fee. | ||
} | ||
chunks[0].amount = -chunks[0].amount; // Drop the chunks that are empty. | ||
if (chunk.sell) { | ||
quoteChunk.feeAsset = chunk.feeAsset; | ||
quoteChunk.feeAmount = chunk.feeAmount; | ||
quoteChunk.feeValue = chunk.feeValue; | ||
} else { | ||
baseChunk.feeAsset = chunk.feeAsset; | ||
baseChunk.feeAmount = chunk.feeAmount; | ||
baseChunk.feeValue = chunk.feeValue; | ||
} // Drop the chunks that represent fiat currencies. | ||
chunks = chunks.filter(function (chunk) { | ||
return chunk.amount; | ||
}); // Drop the chunks that represent fiat currencies. | ||
for (let i = 0; i < chunks.length; ++i) if (_assets.default.getPriority(chunks[i].asset) === 0) chunks.splice(i--, 1); | ||
if (!chunks.length) { | ||
callback(); | ||
return; | ||
} // Set the transaction fee of the disposed asset. | ||
chunks[0].feeValue = chunk.feeValue; // Set the transaction fee of the acquired asset. | ||
chunks[chunks.length - 1].feeAsset = chunk.feeAsset; | ||
chunks[chunks.length - 1].feeAmount = chunk.feeAmount; | ||
for (let chunk of chunks) this.push(chunk); | ||
@@ -93,0 +91,0 @@ |
{ | ||
"name": "@davidosborn/crypto-tax-calculator", | ||
"version": "0.0.14", | ||
"version": "0.0.15", | ||
"description": "A tool to calculate the capital gains of cryptocurrency assets for Canadian taxes", | ||
@@ -50,2 +50,3 @@ "keywords": [ | ||
"fromentries": "*", | ||
"line-stream": "*", | ||
"markdown-table": "*", | ||
@@ -52,0 +53,0 @@ "marked": "*", |
@@ -26,3 +26,3 @@ 'use strict' | ||
* @typedef {object} CapitalGains | ||
* @property {Map.<string, Forward>} forwardByAsset The assets that were carried forward from last year. | ||
* @property {Map.<string, Forward>} [forwardByAsset] The assets that were carried forward from last year. | ||
* @property {array.<Trade>} trades The trades. | ||
@@ -46,4 +46,5 @@ * @property {Map.<string, Ledger>} ledgerByAsset The ledger of each asset. | ||
* Initializes a new instance. | ||
* @param {object} [options] The options. | ||
* @param {object.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. | ||
* @param {object} [options] The options. | ||
* @param {Set.<string>} [options.assets] The assets to retain. | ||
* @param {Map.<string, Forward>} [options.forwardByAsset] The assets to carry forward from last year. | ||
*/ | ||
@@ -59,7 +60,5 @@ constructor(options) { | ||
* The assets to carry forward from last year. | ||
* @type {Map.<string, Ledger>} | ||
* @type {Map.<string, Forward>} | ||
*/ | ||
this._forwardByAsset = new Map(this._options?.forwardByAsset | ||
? Object.entries(this._options.forwardByAsset) | ||
: []) | ||
this._forwardByAsset = this._options?.forwardByAsset | ||
@@ -85,4 +84,4 @@ /** | ||
// Initialize the ledger of the assets to carry forward from last year. | ||
if (this._forwardByAsset) { | ||
for (let [asset, forward] of this._forwardByAsset) { | ||
if (this._forwardByAsset) | ||
for (let [asset, forward] of this._forwardByAsset.entries()) | ||
this._ledgerByAsset.set(asset, { | ||
@@ -93,4 +92,2 @@ acb: forward.acb, | ||
}) | ||
} | ||
} | ||
} | ||
@@ -116,5 +113,4 @@ | ||
if (chunk.amount < 0) { | ||
if (!ledger.balance) { | ||
if (!ledger.balance) | ||
console.log('WARNING: Disposition of ' + chunk.asset + ' from an empty balance on ' + formatTime(chunk.time) + '.') | ||
} | ||
@@ -142,3 +138,3 @@ let acbPerUnit = ledger.balance ? ledger.acb / ledger.balance : 0 | ||
// Remove the transaction fee from the balance. | ||
// Remove the transaction fee from the balance, except for fiat currencies. | ||
if (chunk.feeAmount) { | ||
@@ -153,4 +149,5 @@ let feeLedger = this._ledgerByAsset.get(chunk.feeAsset) | ||
// Record the balance in the trade. | ||
// Record the balance and ACB in the trade for information purposes. | ||
chunk.balance = ledger.balance | ||
chunk.acb = ledger.acb | ||
@@ -160,5 +157,9 @@ // Check whether the balance is negative, which would indicate an accounting error. | ||
this._assetsWithNegativeBalance.add(chunk.asset) | ||
console.log('WARNING: Encountered a negative balance for ' + chunk.asset + '.') | ||
console.log('WARNING: Encountered a negative balance for ' + chunk.asset + ' on ' + formatTime(chunk.time) + '.') | ||
} | ||
// Clear the ACB when the balance is negative. | ||
if (ledger.balance <= 0) | ||
ledger.acb = 0 | ||
callback() | ||
@@ -213,3 +214,3 @@ } | ||
ledgerByAsset: this._ledgerByAsset, | ||
aggregateDisposition: aggregateDisposition, | ||
aggregateDisposition, | ||
taxableGain: aggregateDisposition.gain / 2 // Capital gains are taxable at 50%. | ||
@@ -216,0 +217,0 @@ }) |
'use strict' | ||
import markdownTable from 'markdown-table' | ||
import marked from 'marked' | ||
import stream from 'stream' | ||
import Assets from './assets' | ||
import formatTime from './format-time' | ||
@@ -47,3 +48,3 @@ /** | ||
// Write the balance that was carried forward from last year. | ||
if (chunk.forwardByAsset.size) { | ||
if (chunk.forwardByAsset?.size) { | ||
this._pushLine() | ||
@@ -72,8 +73,9 @@ this._pushLine('## Carried forward from last year') | ||
'Asset', | ||
'Units acquired', | ||
'Units acquired (or disposed)', | ||
'Value', | ||
'Balance', | ||
'Adjusted cost base', | ||
'Fee', | ||
'Fee asset', | ||
'Date', | ||
'Time', | ||
'Exchange' | ||
@@ -86,5 +88,6 @@ ]] | ||
this._formatAmount(trade.balance), | ||
this._formatAmount(trade.feeAmount), | ||
trade.feeAsset, | ||
this._formatDate(trade.time), | ||
this._formatValue(trade.acb), | ||
trade.feeAsset ? this._formatAssetAmount(trade.feeAsset, trade.feeAmount) : '', | ||
trade.feeAsset ? trade.feeAsset : '', | ||
formatTime(trade.time), | ||
trade.exchange | ||
@@ -111,3 +114,3 @@ ])))) | ||
'Gain (or loss)', | ||
'Date', | ||
'Time', | ||
'Exchange' | ||
@@ -123,3 +126,3 @@ ]] | ||
this._formatValue(disposition.gain), | ||
this._formatDate(disposition.time), | ||
formatTime(disposition.time), | ||
disposition.exchange | ||
@@ -215,2 +218,8 @@ ]) | ||
_formatAssetAmount(asset, amount) { | ||
return Assets.getPriority(asset) !== 0 | ||
? this._formatAmount(amount) | ||
: this._formatValue(amount) | ||
} | ||
_formatAmount(amount) { | ||
@@ -230,14 +239,2 @@ let amountString = this._amountFormat.format(Math.abs(amount)) | ||
_formatDate(time) { | ||
return new Date(time) | ||
.toLocaleDateString('en-CA', { | ||
day: 'numeric', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
month: 'short', | ||
second: '2-digit', | ||
year: 'numeric' | ||
}) | ||
} | ||
_pushLine(line) { | ||
@@ -244,0 +241,0 @@ this.push(line) |
@@ -15,3 +15,3 @@ 'use strict' | ||
minute: '2-digit', | ||
month: '2-digit', | ||
month: 'short', | ||
year: 'numeric' | ||
@@ -18,0 +18,0 @@ }) |
@@ -8,2 +8,3 @@ 'use strict' | ||
import fs from 'fs' | ||
import lineStream from 'line-stream' | ||
import multiStream from 'multistream' | ||
@@ -19,6 +20,7 @@ import process from 'process' | ||
import markedStream from './marked-stream' | ||
import csvNormalizeStream from './csv-normalize-stream' | ||
import tradeFilterStream from './trade-filter-stream' | ||
import tradeParseStream from './trade-parse-stream' | ||
import tradeTransactionsStream from './trade-transactions-stream' | ||
import tradeValueStream from './trade-value-stream' | ||
import transactionFilterStream from './transaction-filter-stream' | ||
@@ -33,3 +35,3 @@ export default function main(args) { | ||
argument: 'spec', | ||
description: 'Only consider the specified assets.' | ||
description: 'Only consider trades involving the specified assets.' | ||
}, | ||
@@ -51,3 +53,3 @@ { | ||
long: 'html', | ||
description: 'Format the output as HTML instead of Markdown.' | ||
description: 'Format the results as HTML instead of Markdown.' | ||
}, | ||
@@ -58,3 +60,3 @@ { | ||
argument: 'file', | ||
description: 'Write the output to the specified file.' | ||
description: 'Write the results to the specified file.' | ||
}, | ||
@@ -64,3 +66,3 @@ { | ||
long: 'quiet', | ||
description: 'Do not produce any output.' | ||
description: 'Do not write the results.' | ||
}, | ||
@@ -110,6 +112,11 @@ { | ||
// 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.normalizeCode)) | ||
// 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(',') | ||
let forwardByAsset = undefined | ||
if (opts.options.init) { | ||
forwardByAsset = new Map(opts.options.init.value.split(',') | ||
.map(function(spec) { | ||
@@ -122,2 +129,3 @@ let [asset, balance, acb] = spec.split(':') | ||
})) | ||
} | ||
@@ -134,2 +142,4 @@ // Load the historical data. | ||
.pipe(utf8()) | ||
.pipe(lineStream()) | ||
.pipe(csvNormalizeStream()) | ||
.pipe(csvParse({ | ||
@@ -147,16 +157,8 @@ columns: true, | ||
stream = stream | ||
.pipe(tradeValueStream({ | ||
history, | ||
verbose: !!opts.options.verbose, | ||
web: !!opts.options.web | ||
// Filter the trades by their assets. | ||
if (assets) | ||
stream = stream.pipe(tradeFilterStream({ | ||
assets | ||
})) | ||
.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. | ||
@@ -166,3 +168,10 @@ stream = multiStream([ | ||
stream | ||
.pipe(tradeValueStream({ | ||
history, | ||
verbose: !!opts.options.verbose, | ||
web: !!opts.options.web | ||
})) | ||
.pipe(tradeTransactionsStream()) | ||
.pipe(capitalGainsCalculateStream({ | ||
assets, | ||
forwardByAsset | ||
@@ -169,0 +178,0 @@ })) |
@@ -36,3 +36,4 @@ 'use strict' | ||
'Uuid|Exchange|TimeStamp|OrderType|Limit|Quantity|QuantityRemaining|Commission|Price|PricePerUnit|IsConditional|Condition|ConditionTarget|ImmediateOrCancel|Closed': TradeParseStream.prototype._transformBittrex2, | ||
'txid|refid|time|type|aclass|asset|amount|fee|balance': TradeParseStream.prototype._transformKraken | ||
'txid|refid|time|type|aclass|asset|amount|fee|balance': TradeParseStream.prototype._transformKraken, | ||
'Coin|Time|Buy/Sell|Filled Price|Amount|Fee|Volume': TradeParseStream.prototype._transformKuCoin | ||
} | ||
@@ -205,2 +206,42 @@ | ||
/** | ||
* Transforms a CSV record from KuCoin into a trade. | ||
* @param {object} chunk The CSV record. | ||
*/ | ||
async _transformKuCoin(chunk) { | ||
// If this is not a trade, then drop it. | ||
let buySell = chunk['Buy/Sell'] | ||
if (buySell !== 'Buy' && buySell !== 'Sell') | ||
return | ||
let [quoteAsset,baseAsset] = chunk.Coin.split('/') | ||
baseAsset = Assets.normalizeCode(baseAsset) | ||
quoteAsset = Assets.normalizeCode(quoteAsset) | ||
const splitAmountAssetRegExp = /^([0-9.,]+)([A-Za-z][A-Za-z0-9]*)$/ | ||
let [amount, amountAsset] = chunk.Amount.match(splitAmountAssetRegExp).slice(1) | ||
let [feeAmount, feeAsset] = chunk.Fee.match(splitAmountAssetRegExp).slice(1) | ||
amountAsset = Assets.normalizeCode(amountAsset) | ||
feeAsset = Assets.normalizeCode(feeAsset) | ||
if (amountAsset !== quoteAsset) { | ||
console.log('WARNING: Expected amount of ' + quoteAsset + ' but found ' + amountAsset + ' instead.') | ||
return | ||
} | ||
this.push({ | ||
exchange: 'KuCoin', | ||
baseAsset: baseAsset, | ||
quoteAsset: quoteAsset, | ||
baseAmount: TradeParseStream._parseNumber(chunk['Filled Price']), | ||
quoteAmount: TradeParseStream._parseNumber(chunk.Amount), | ||
sell: buySell === 'Sell', | ||
time: TradeParseStream._parseTime(chunk.Time), | ||
feeAsset: baseAsset, | ||
feeAmount: feeAmount | ||
}) | ||
} | ||
/** | ||
* Parses a number. | ||
@@ -207,0 +248,0 @@ * @param {string} s The string. |
@@ -9,10 +9,10 @@ 'use strict' | ||
* @typedef {object} Transaction | ||
* @property {string} exchange The exchange on which the transaction was executed. | ||
* @property {string} asset The asset. | ||
* @property {number} amount The amount of assets. | ||
* @property {number} value The value of the transaction, in Canadian dollars. | ||
* @property {number} time The time of the transaction, as a UNIX timestamp. | ||
* @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. | ||
* @property {string} exchange The exchange on which the transaction was executed. | ||
* @property {string} asset The asset. | ||
* @property {number} amount The amount of assets. | ||
* @property {number} value The value of the transaction, in Canadian dollars. | ||
* @property {number} time The time of the transaction, as a UNIX timestamp. | ||
* @property {string} [feeAsset] The asset 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. | ||
*/ | ||
@@ -43,4 +43,2 @@ | ||
time: chunk.time, | ||
feeAsset: chunk.baseAsset, | ||
feeAmount: 0, | ||
feeValue: 0 | ||
@@ -54,4 +52,2 @@ } | ||
time: chunk.time, | ||
feeAsset: chunk.quoteAsset, | ||
feeAmount: 0, | ||
feeValue: 0 | ||
@@ -63,21 +59,12 @@ } | ||
if (chunk.sell) { | ||
quoteChunk.amount = -quoteChunk.amount | ||
chunks.reverse() | ||
} | ||
else { | ||
baseChunk.amount = -baseChunk.amount | ||
} | ||
// Copy the fee. | ||
if (chunk.sell) { | ||
quoteChunk.feeAsset = chunk.feeAsset | ||
quoteChunk.feeAmount = chunk.feeAmount | ||
quoteChunk.feeValue = chunk.feeValue | ||
} | ||
else { | ||
baseChunk.feeAsset = chunk.feeAsset | ||
baseChunk.feeAmount = chunk.feeAmount | ||
baseChunk.feeValue = chunk.feeValue | ||
} | ||
chunks[0].amount = -chunks[0].amount | ||
// Drop the chunks that are empty. | ||
chunks = chunks.filter(function(chunk) { | ||
return chunk.amount | ||
}) | ||
// Drop the chunks that represent fiat currencies. | ||
@@ -88,2 +75,14 @@ for (let i = 0; i < chunks.length; ++i) | ||
if (!chunks.length) { | ||
callback() | ||
return | ||
} | ||
// Set the transaction fee of the disposed asset. | ||
chunks[0].feeValue = chunk.feeValue | ||
// Set the transaction fee of the acquired asset. | ||
chunks[chunks.length - 1].feeAsset = chunk.feeAsset | ||
chunks[chunks.length - 1].feeAmount = chunk.feeAmount | ||
for (let chunk of chunks) | ||
@@ -90,0 +89,0 @@ this.push(chunk) |
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
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
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
97767
38
2533
13
14
+ Addedline-stream@*
+ Addedline-stream@1.0.0(transitive)