Skip to content

Instantly share code, notes, and snippets.

@Phylliida
Last active May 7, 2021 21:31
Show Gist options
  • Save Phylliida/eada69815db10071b99328df2130332a to your computer and use it in GitHub Desktop.
Save Phylliida/eada69815db10071b99328df2130332a to your computer and use it in GitHub Desktop.
Simple FIFO Tax Compute for Coinbase History
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