Last active
February 6, 2016 02:49
-
-
Save CaptEmulation/02b20542f97d56c94ead to your computer and use it in GitHub Desktop.
2014 Bitcoin taxes cost basis and income calculations. Uses Libra Tax files as input
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var argv = require('minimist')(process.argv.slice(2)); | |
var fs = require('fs'); | |
var csvParse = require('csv-parse'); | |
var csvStringify = require('csv-stringify'); | |
var moment = require('moment'); | |
if (argv._.length === 0) { | |
console.log('Usage: node tax --year 2014 Libra.csv output.csv'); | |
process.exit(); | |
} | |
// first full arg | |
var input = argv._[0]; | |
// second full arg | |
var output = argv._[1] || 'output.txt'; | |
var year = argv.year || argv.y; | |
var error; | |
error = !input && new Error('No input file specified'); | |
if (error) { | |
throw error; | |
} | |
// Shared global data model | |
var data = { | |
transactions: [], | |
buys: [], | |
sells: [], | |
generated: [], | |
taxables: [], | |
delta: 0 | |
} | |
/** | |
* Simple FIFO algorithm matching received coins with spent coins | |
*/ | |
var firstBuy = (function () { | |
// The current index of owned coins | |
var index = 0; | |
return function (sell) { | |
var needToSell = -sell.amount; | |
var costBasis = 0; | |
while (needToSell > 0) { | |
var buy = data.buys[index]; | |
if (buy) { | |
var buyAmountAvailable = buy.sold ? buy.amount - buy.sold : buy.amount; | |
var selling = needToSell > buyAmountAvailable ? buyAmountAvailable : needToSell; | |
var buyCostBasis = Math.abs(selling / buyAmountAvailable * buy.totalValue); | |
buy.sold += selling; | |
costBasis += buyCostBasis; | |
needToSell -= selling; | |
index++; | |
} else { | |
console.log('warning, not enough purchases to cover sales!'); | |
break; | |
} | |
} | |
// Taxable event object | |
sell.costBasis = costBasis; | |
var t = { | |
date: moment(sell.date).format('MM-DD-YYYY'), | |
amount: sell.amount.toFixed(8), | |
proceeds: Math.abs(sell.totalValue).toFixed(2), | |
costBasis: sell.costBasis.toFixed(2) | |
}; | |
t.delta = (Math.abs(sell.totalValue) - sell.costBasis); | |
data.taxables.push(t); | |
}; | |
}()); | |
/** | |
* Helps caluclate total USD profit/loss from sales | |
*/ | |
var profitLoss = function (taxable) { | |
data.delta += taxable.delta; | |
}; | |
/** | |
* Write taxable events to output CSV | |
*/ | |
var writeTaxables = function () { | |
var taxables = [['Description', 'Date', 'Amount', 'Purchase price', 'Proceeds', 'Cost Basis']].concat(data.taxables.map(function (tax) { | |
return ['Bitcoin sales', tax.date, tax.amount, 'VARIOUS', tax.proceeds, tax.costBasis]; | |
})); | |
csvStringify(taxables, function (err, data) { | |
fs.writeFile(output, data, function (err) { | |
if (err) throw err; | |
console.log('done'); | |
}); | |
}); | |
} | |
/** | |
* Caluclated all mined blocks | |
*/ | |
var calculateGeneratedIncome = function () { | |
var income = { | |
btc: 0, | |
usd: 0 | |
}; | |
data.generated.forEach(function (g) { | |
income.btc += g.amount; | |
income.usd += g.totalValue; | |
}); | |
return income; | |
} | |
/** | |
* Read input CSV and display results | |
*/ | |
fs.readFile(input, 'utf8', function (err, file) { | |
if (err) throw err; | |
importCsv(file, function (data) { | |
data.sells.forEach(firstBuy); | |
data.taxables.forEach(profitLoss); | |
writeTaxables(); | |
console.log('Mining income: ', calculateGeneratedIncome()); | |
console.log('Transaction income (USD): $' + data.delta.toFixed(2)); | |
//console.log(data.taxables); | |
}); | |
}); | |
/** | |
* Turns CSV headers into Javascript friendly object field names. No spaces and camel casing | |
*/ | |
function objectify(string) { | |
return string.split(' ').reduce(function (prev, curr, index) { | |
return prev += curr.charAt(0)[index ? 'toUpperCase' : 'toLowerCase']() + curr.slice(1); | |
}, ''); | |
} | |
/** | |
* CSV parser to data model | |
*/ | |
var importCsv = function (file, callback) { | |
var fields; | |
csvParse(file, function (err, output) { | |
if (err) throw err; | |
output.forEach(function (row, index) { | |
if (index === 0) { | |
// Creates object field names from headers and saves for later | |
fields = row.map(objectify); | |
} else if (row.length > 1) { | |
// Unpacks the row into object t using objectify names, above | |
var t = row.reduce(function (prev, curr, index) { | |
prev[fields[index]] = curr; | |
return prev; | |
}, {}); | |
// Some post processing | |
t.date = new Date(t.date); | |
t.amount = parseFloat(t.amount); | |
t.spotValue = parseFloat(t.spotValue); | |
t.totalValue = parseFloat(t.totalValue); | |
// Filtering by year or all if unspecified | |
if (year ? t.date.getFullYear() === year : true) { | |
data.transactions.push(t); | |
if (t.taxType === 'sale') { | |
data.sells.push(t); | |
} | |
if (t.taxType === 'purchase') { | |
t.sold = 0; | |
data.buys.push(t); | |
} | |
// These are what p2pool generated (mined) coins tracked by BlockChain look like in Libra Tax CSV | |
if (t.destination === 'Blockchain Wallet') { | |
data.generated.push(t); | |
t.generated = true; | |
} | |
} | |
} | |
}); | |
// All done callback | |
callback(data); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment