Last active
May 7, 2021 21:31
-
-
Save Phylliida/eada69815db10071b99328df2130332a to your computer and use it in GitHub Desktop.
Simple FIFO Tax Compute for Coinbase History
This file contains hidden or 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
from os import walk | |
import os | |
import json | |
import pandas as pd | |
from decimal import Decimal | |
MAX_DATE = "2022-01-01 00:00:00+0000" | |
def formatAsDollars(x): | |
return '${:,.2f}'.format(x) | |
COLUMNS = ['description', 'dateAcquired', 'dateSold', 'salesPrice', 'purchasePrice', 'profit'] | |
class TransactionInfo(object): | |
def __init__(self, description, dateAcquired, dateSold, salesPrice, purchasePrice, profit): | |
self.description = description | |
self.dateAcquired = dateAcquired | |
self.dateSold = dateSold | |
self.salesPrice = formatAsDollars(salesPrice) | |
self.purchasePrice = formatAsDollars(purchasePrice) | |
self.profit = formatAsDollars(profit) | |
def toRow(self): | |
return ",".join([str(getattr(self, k)) for k in COLUMNS]) | |
# Use https://github.com/robertwb/bitcoin-taxes/blob/master/download-coinbase.py to download the data from your coinbase account | |
# In that code, remove the two lines | |
''' | |
if account['currency']['code'] != 'BTC': | |
continue | |
''' | |
# at 57-58 to get it to download all currency history, not just BTC | |
# pass the name of whatever folder you use into the stuff below as the folder param | |
# Makes a report of everything sold before MAX_DATE, each entry will have buy price, sell price, buy date, sell date, and profit | |
# I'm assuming you aren't doing transfers from other non-coinbase stuff, dunno if this code works if you're doing that | |
def getReport(folder, outFile): | |
totalProfit, totalBuy, totalSell, formattedTransactions, sortedTransactions = getTaxInfo(folder) | |
data = [",".join(COLUMNS)] + [x.toRow() for x in sortedTransactions] | |
res = "\n".join(data) | |
f = open(outFile, "w") | |
f.write(res) | |
f.close() | |
def getAllTransactions(folder): | |
_, _, filenames = next(walk(folder)) | |
transactions = {} | |
for fn in filenames: | |
f = open(os.path.join(folder, fn), "r") | |
dat = json.loads("\n".join(f.read().split("\n")[1:])) # first line has comment | |
f.close() | |
wallet = [] | |
currency = dat['account']['balance']['currency'] | |
for k,v in dat['transactions'].items(): | |
wallet.append(parseTransaction(v)) | |
wallet.sort(key=lambda x: x[0]) | |
transactions[currency] = wallet | |
return transactions | |
def parseTransaction(transaction): | |
date = pd.to_datetime(transaction['updated_at']) | |
usd_amount = Decimal(transaction['native_amount']['amount']) | |
currency_amount = Decimal(transaction['amount']['amount']) | |
return date, usd_amount, currency_amount | |
def getTaxInfo(folder): | |
transactions = getAllTransactions(folder) | |
totalProfit, totalBuy, totalSell = 0, 0, 0 | |
formattedTransactions = [] | |
for k in transactions.keys(): | |
profit, buy, sell, trans = parseTaxInfo(k, transactions[k], pd.to_datetime(MAX_DATE)) | |
totalProfit += profit | |
totalBuy += buy | |
totalSell += sell | |
formattedTransactions += trans | |
sortedTransactions = [x for x in formattedTransactions] | |
sortedTransactions.sort(key=lambda x: x.dateSold) | |
formattedTransactions | |
return totalProfit, totalBuy, totalSell, formattedTransactions, sortedTransactions | |
# Uses First in First out (FIFO) | |
# Note this ignores transaction fees so its slightly off (I think? seems to line up so I could be wrong) | |
# TODO: Fix that, or at least double check | |
def parseTaxInfo(k, wallet, maxDate): | |
values = [] | |
#print(wallet) | |
totalProfit = 0 | |
totalBuyPrices = 0 | |
totalSellPrices = 0 | |
infos = [] | |
transactions = [] | |
for date, usd_amount, currency_amount in wallet: | |
if date > maxDate: continue | |
# flow into wallet, add to queue | |
if currency_amount > 0: | |
#print("adding", currency_amount, usd_amount) | |
values.append([usd_amount, currency_amount, date]) | |
#print(values) | |
# flow out of wallet, remove from queue | |
else: | |
currencyRemoving = -currency_amount # they are negative | |
usdRemoving = -usd_amount | |
#print("removing", currencyRemoving, usdRemoving) | |
while True: | |
popOffUsd, popOffCurrency, dateBought = values[0] # first in first out, it's a queue | |
# we exhaust it all, we can remove it | |
if popOffCurrency <= currencyRemoving: | |
values.pop(0) | |
percentPopOffIsOfSale = popOffCurrency/currencyRemoving | |
usdWhenSold = usdRemoving*percentPopOffIsOfSale | |
profit = usdWhenSold - popOffUsd | |
totalBuyPrices += popOffUsd | |
totalSellPrices += usdWhenSold | |
transactions.append(TransactionInfo(description="Sell " + k, dateAcquired=dateBought, dateSold=date, salesPrice=usdWhenSold, purchasePrice=popOffUsd, profit=profit)) | |
#print("bought", popOffCurrency, " for ", popOffUsd, " sell for ", usdWhenSold, " for profit ", profit) | |
currencyRemoving -= popOffCurrency | |
usdRemoving -= usdWhenSold | |
totalProfit += profit | |
# we don't exhaust it all, we need to decrease it | |
else: | |
percentSaleIsOfPopOff = currencyRemoving/popOffCurrency | |
usdWhenBought = percentSaleIsOfPopOff*popOffUsd | |
profit = usdRemoving - usdWhenBought | |
totalBuyPrices += usdWhenBought | |
totalSellPrices += usdRemoving | |
transactions.append(TransactionInfo(description="Sell " + k, dateAcquired=dateBought, dateSold=date, salesPrice=usdRemoving, purchasePrice=usdWhenBought, profit=profit)) | |
#print("bought", currencyRemoving, " for ", usdWhenBought, " sell for ", usdRemoving, " for profit ", profit) | |
#print("bin", values[0]) | |
values[0] = [popOffUsd - usdWhenBought, popOffCurrency - currencyRemoving, dateBought] | |
#print("new bin", values[0]) | |
currencyRemoving = 0 | |
usdRemoving = 0 | |
totalProfit += profit | |
if usdRemoving < 0.001: break | |
print(k) | |
print("leftover", values) | |
print("total profit", totalProfit) | |
print("total buy prices", totalBuyPrices) | |
print("total sell prices", totalSellPrices) | |
return totalProfit, totalBuyPrices, totalSellPrices, transactions |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment