├── f8949.pdf ├── .gitignore ├── turbo_tax.py ├── LICENSE ├── bittrex_reader.py ├── coinbase_reader.py ├── fill_8949.py ├── README.md ├── cost_basis.py ├── CryptoTaxes.py └── gdax_reader.py /f8949.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsugar87/CryptoTaxes/HEAD/f8949.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | credentials.py 2 | .idea/ 3 | FDFs/ 4 | PDFs/ 5 | *.pyc 6 | *.csv 7 | *.p 8 | *.txf 9 | *.js 10 | -------------------------------------------------------------------------------- /turbo_tax.py: -------------------------------------------------------------------------------- 1 | # This deals with all Turbo Tax related things 2 | import datetime 3 | 4 | 5 | def make_txf(full_orders): 6 | with open("CryptoTurboTax.txf", "w") as text_file: 7 | # Write the header 8 | text_file.write("V042\n") 9 | text_file.write("ACyrptoTaxes\n") 10 | text_file.write("D " + datetime.datetime.now().strftime('%m/%d/%Y') + "\n") 11 | text_file.write("^\n") 12 | for order in full_orders: 13 | text_file.write("TD\n") 14 | text_file.write("N712\n") 15 | text_file.write("C1\n") 16 | text_file.write("L1\n") 17 | text_file.write("P" + order[0] + "\n") 18 | text_file.write("D" + order[1] + "\n") 19 | text_file.write("D" + order[2] + "\n") 20 | text_file.write("$%.2f\n" % order[4]) 21 | text_file.write("$%.2f\n" % order[3]) 22 | text_file.write("^\n") 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Glenn Sugar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bittrex_reader.py: -------------------------------------------------------------------------------- 1 | # This will read in bittrex transactions 2 | # Note that you must supply the fullOrders.csv file 3 | # This only works with BTC transactions!!!! 4 | import csv 5 | import dateutil.parser 6 | 7 | 8 | def parse_order(order): 9 | # Get the currency 10 | product = order[1][order[1].index('-')+1:] 11 | amount = float(order[3]) 12 | fees = float(order[5]) 13 | if 'SELL' in order[2]: 14 | buysell = 'sell' 15 | cost = float(order[6])-fees 16 | elif 'BUY' in order[2]: 17 | buysell = 'buy' 18 | cost = float(order[6])+fees 19 | else: 20 | print('UNKNOWN BUY/SELL ORDER!!') 21 | print(order) 22 | cost_per_coin = cost/amount 23 | exchange_currency = order[1][0:order[1].index('-')] 24 | order_time = dateutil.parser.parse(order[8] + " UTC") 25 | return [order_time, product, buysell, cost, amount, cost_per_coin, exchange_currency] 26 | 27 | 28 | def get_buys_sells(order_file='fullOrders.csv'): 29 | # Get the buys and sells for all orders 30 | buys = [] 31 | sells = [] 32 | print('Loading Bittrex orders from ' + order_file) 33 | # First make sure there are no NULL bytes in the file 34 | with open(order_file, 'rb') as csvfile: 35 | reader = csv.reader((line.replace('\0', '') for line in csvfile)) 36 | first_row = True 37 | for row in reader: 38 | # ignore the header line 39 | if first_row: 40 | first_row = False 41 | elif len(row) > 0: 42 | save_row = row 43 | order = parse_order(row) 44 | if order[2] == 'buy': 45 | buys.append(order) 46 | elif order[2] == 'sell': 47 | sells.append(order) 48 | else: 49 | print("WEIRD! Order is neither buy nor sell!") 50 | print(order) 51 | print('Done parsing Bittrex orders!') 52 | return buys, sells 53 | -------------------------------------------------------------------------------- /coinbase_reader.py: -------------------------------------------------------------------------------- 1 | # This will read in coinbase transactions 2 | import credentials 3 | from coinbase.wallet.client import Client 4 | import dateutil.parser 5 | import datetime 6 | import time 7 | 8 | 9 | def get_client(): 10 | client = Client(credentials.coinbase_key, credentials.coinbase_secret) 11 | return client 12 | 13 | 14 | def get_accounts(client): 15 | accounts = client.get_accounts() 16 | return accounts 17 | 18 | 19 | # get the buys and sells for an account 20 | def get_account_transactions(client, account): 21 | buys = [] 22 | sells = [] 23 | buys_complex = client.get_buys(account['id']) 24 | sells_complex = client.get_sells(account['id']) 25 | for order in buys_complex['data']: 26 | order_time = dateutil.parser.parse(order['payout_at']) 27 | product = order['amount']['currency'] 28 | cost = float(order['total']['amount']) 29 | amount = float(order['amount']['amount']) 30 | cost_per_coin = cost/amount 31 | exchange_currency = order['total']['currency'] 32 | buys.append([order_time, product, 'buy', cost, amount, cost_per_coin, exchange_currency]) 33 | for order in sells_complex['data']: 34 | # WARNING! This is not tested since I never sell on coinbase (use GDAX instead!) 35 | order_time = dateutil.parser.parse(order['payout_at']) 36 | product = order['amount']['currency'] 37 | cost = float(order['total']['amount']) 38 | amount = float(order['amount']['amount']) 39 | cost_per_coin = cost/amount 40 | exchange_currency = order['total']['currency'] 41 | sells.append([order_time, product, 'sell', cost, amount, cost_per_coin, exchange_currency]) 42 | return buys, sells 43 | 44 | 45 | def get_buys_sells(): 46 | print('Connecting to Coinbase...') 47 | client = get_client() 48 | print('Connected to Coinbase!') 49 | # Get the Coinbase accounts 50 | accounts = client.get_accounts() 51 | buys = [] 52 | sells = [] 53 | for account in accounts['data']: 54 | # Only use the USD and BTC accounts since they will contain all transaction ids 55 | if account['currency'] != 'USD': 56 | buys_dummy, sells_dummy = get_account_transactions(client, account) 57 | buys += buys_dummy 58 | sells += sells_dummy 59 | return buys, sells 60 | -------------------------------------------------------------------------------- /fill_8949.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fdfgen import forge_fdf 3 | 4 | 5 | def makePDF(fifoResult, fname, person, social): 6 | # Write to the PDF 7 | # Create the directories if they don't already exist 8 | if not os.path.exists("FDFs"): 9 | os.makedirs("FDFs") 10 | if not os.path.exists("PDFs"): 11 | os.makedirs("PDFs") 12 | 13 | counter = 0 14 | fileCounter = 0 15 | fields = [('topmostSubform[0].Page1[0].f1_1[0]', person), 16 | ('topmostSubform[0].Page1[0].f1_2[0]', social)] 17 | fnums = [3+i*8 for i in range(14)] 18 | lastRow1 = 0 19 | lastRow2 = 0 20 | lastRow3 = 0 21 | lastRow4 = 0 22 | # loop through all FIFO sales 23 | for sale in fifoResult: 24 | counter += 1 25 | # append to the form 26 | row = counter 27 | fnum = fnums[row-1] 28 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum), sale[0])) 29 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum+1), sale[1])) 30 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum+2), sale[2])) 31 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum+3), "%1.2f" % sale[3])) 32 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum+4), "%1.2f" % sale[4])) 33 | if (sale[3]-sale[4]) < 0: 34 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum + 7), 35 | "(%1.2f)" % (sale[4] - sale[3]))) 36 | else: 37 | fields.append(('topmostSubform[0].Page1[0].Table_Line1[0].Row%d[0].f1_%d[0]' % (row, fnum+7), "%1.2f" % (sale[3]-sale[4]))) 38 | 39 | lastRow1 += float("%1.2f" % sale[3]) 40 | lastRow2 += float("%1.2f" % sale[4]) 41 | lastRow3 += 0 42 | lastRow4 += float("%1.2f" % (sale[3]-sale[4])) 43 | 44 | if row == 14 or sale == fifoResult[-1]: 45 | fields.append(("topmostSubform[0].Page1[0].f1_115[0]", "%1.2f" % lastRow1)) 46 | fields.append(("topmostSubform[0].Page1[0].f1_116[0]", "%1.2f" % lastRow2)) 47 | if lastRow4 < 0: 48 | fields.append(("topmostSubform[0].Page1[0].f1_118[0]", "(%1.2f)" % abs(lastRow4))) 49 | else: 50 | fields.append(("topmostSubform[0].Page1[0].f1_118[0]", "%1.2f" % lastRow4)) 51 | fields.append(("topmostSubform[0].Page1[0].c1_1[2]", 3)) 52 | # save the file and reset the counter 53 | fdf = forge_fdf("", fields, [], [], []) 54 | fdf_file = open("FDFs\\" + fname + "_%03d.fdf" % fileCounter, "w") 55 | fdf_file.write(fdf) 56 | fdf_file.close() 57 | # call PDFTK to make the PDF 58 | os.system("pdftk f8949.pdf fill_form FDFs\\" + fname + "_%03d.fdf" % fileCounter + " output PDFs\\" + 59 | fname + "_%03d.pdf" % fileCounter) 60 | # delete the FDF 61 | os.system("del FDFs\\" + fname + "_%03d.fdf" % fileCounter) 62 | counter = 0 63 | fileCounter += 1 64 | fields = [] 65 | lastRow1 = 0 66 | lastRow2 = 0 67 | lastRow3 = 0 68 | lastRow4 = 0 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CryptoTaxes 2 | This was the original code that started www.bitcointaxhelper.com 3 | 4 | This will fill out IRS form 8949 with Coinbase, GDAX, and Bittrex data. It assumes all short term 5 | sales and will use the highest cost buy order for cost basis. This will lower the amount 6 | of taxes you will have to pay. It will make a .txf that you can import into TurboTax, and 7 | it will fill out the IRF form 8949. This has only been tested on Windows. 8 | 9 | Requirements: 10 | 11 | PDFtk (Make sure it is in your system's path environment variable): 12 | 13 | https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/ 14 | 15 | GDAX Python Library: 16 | 17 | pip install GDAX 18 | 19 | Coinbase Python Library: 20 | 21 | pip install Coinbase 22 | 23 | fdfgen Python Library: 24 | 25 | pip install fdfgen 26 | 27 | Instructions: 28 | 29 | In the same directory that you cloned this repository, make a file "credentials.py" that 30 | contains the following: 31 | 32 | coinbase_key = '' 33 | coinbase_secret = '' 34 | gdax_key = '' 35 | gdax_secret = '' 36 | gdax_passphrase = '' 37 | 38 | You will populate this file with your Coinbase and GDAX API Key details. First, get the 39 | Coinbase keys. You can get the Coinbase keys by logging into your Coinbase 40 | account and going to https://www.coinbase.com/settings/api and clicking on "+ New API Key." 41 | A window will pop up and you should check "all" under Accounts and "wallet:accounts:read," 42 | "wallet:addresses:read," "wallet:buys:read," "wallet:deposits:read," "wallet:sells:read," 43 | "wallet:transactions:read," and "wallet:user:read" under Permissions, and then click Create. 44 | A new window will pop up with the API Key Details. Put the API Key into the coinbaseKey variable 45 | and the API Secret into the coinbaseSecret variable. For example if your Coinbase API Key is 46 | abcdefg1234 and your API Secret is zxcvbasdf1234qwer, then in the credentials.py file you should 47 | have: 48 | 49 | coinbase_key = 'abcdefg1234' 50 | coinbase_secret = 'zxcvbasdf1234qwer' 51 | 52 | Next, you need to get the GDAX API Key details. Sign into GDAX and go to 53 | https://www.gdax.com/settings/api Under Permissions, check "View" and then click 54 | "Create API Key." Enter the two-factor authenication code 55 | if you are asked for it, and then put the API key, the API secret, and 56 | the passphrase in the credentials.py file. Note that the passphrase 57 | is located in a text box directory under the Permissions area where you checked 58 | "View." If the API key is qwerty123, the API secret is poiuyt999, and passphrase is 59 | mnbvc000, then you should finish filling out the credentials.py file (note that your 60 | keys, secrets, and passphases could be longer or shorter than the examples given here): 61 | 62 | coinbase_key = 'abcdefg1234' 63 | coinbase_secret = 'zxcvbasdf1234qwer' 64 | gdax_key = 'qwerty123' 65 | gdax_secret = 'poiuyt999' 66 | gdax_passphrase = 'mnbvc000' 67 | 68 | Unfortunately, the Bittrex API does not let you get your entire transaction history via 69 | an API. In order to get your entire history, you must login to your Bittrex account, 70 | go to https://bittrex.com/History, and then click on "Load All." This will download 71 | your entire history in a csv file called "fullOrders.csv". Move this file into the 72 | CryptoTaxes directory, and it will be read in. 73 | 74 | Once the five variables (coinbase_key, coinbase_secret, gdax_key, gdax_secret, and 75 | gdax_passphrase) are set in credentials.py and the Bittrex fullOrders.csv has been 76 | moved into the CryptoTaxes directory, you can run the program at the command line: 77 | 78 | python CryptoTaxes.py 79 | 80 | When you run this, you will be prompted to enter your full name and then your social security 81 | number (these are used only for filling out the tax forms). The filled out form 8949s will be 82 | in a new directory called PDFs. 83 | 84 | Legal disclaimer: 85 | This is not indended to provide tax, legal, or accounting advice. This material has been prepared for informational purposes only, and is not intended to provide, and should not be relied on for, tax, legal or accounting advice. You should consult your own tax, legal and accounting advisors before engaging in any transaction. 86 | 87 | If you find this code useful, feel free to donate! 88 | 89 | BTC: 1LSTU2pNgeZKeD2CiTNPJRgcnhaUAkjpWJ 90 | 91 | LTC: Ld6CF6LSy3K2PVpdm7qHbpFTarcVxdzE3L 92 | -------------------------------------------------------------------------------- /cost_basis.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import dateutil.parser 4 | import csv 5 | 6 | 7 | def is_forked(product): 8 | # This will determine if the order was a sell and a forked coin 9 | forked_list = ['BCH', 'BCC', 'BGD'] 10 | return product in forked_list 11 | 12 | 13 | def get_forked_time(product): 14 | if product == 'BCH': 15 | forked_time = '8/17/2018 00:00:00 UTC' 16 | elif product == 'BGD': 17 | forked_time = '10/24/2017 00:00:00 UTC' 18 | else: 19 | print('Unknown fork product: ' + product) 20 | return dateutil.parser.parse(forked_time) 21 | 22 | 23 | def parse_cost_basis_row(row): 24 | # We want the order to be: [order_time, product, 'buy', cost, amount, cost_per_coin, exchange_currency] 25 | order = [dateutil.parser.parse(row[0]+" 0:0:0 UTC"), row[2], 'buy', float(row[6]), float(row[1]), 26 | float(row[6])/float(row[1]), row[4]] 27 | return order 28 | 29 | 30 | def parse_cost_basis_file(filename): 31 | # This function will read in a cost_basis.csv file and output the cost basis (buys) 32 | # This is based off the csv format given in bitcoin.tax 33 | # The order should be: Date Volume Symbol Price Currency Fee Cost Source(year bought) 34 | buys = [] 35 | print('Loading cost basis data from ' + filename) 36 | # First make sure there are no NULL bytes in the file 37 | with open(filename, 'rb') as csvfile: 38 | reader = csv.reader((line.replace('\0', '') for line in csvfile)) 39 | first_row = True 40 | for row in reader: 41 | # ignore the header line 42 | if first_row: 43 | first_row = False 44 | elif len(row) > 0: 45 | save_row = row 46 | order = parse_cost_basis_row(row) 47 | buys.append(order) 48 | print('Done parsing cost basis data!') 49 | return buys 50 | 51 | 52 | def get_cost_basis(sells_sorted, buys_sorted, basis_type='highest', tax_year=2017): 53 | # start_year = dateutil.parser.parse('%d/1/1 00:00:00Z' % tax_year) 54 | # end_year = dateutil.parser.parse('%d/1/1 00:00:00Z' % (tax_year+1)) 55 | # Set the start of the tax year at Jan 2 because bitcoin.tax uses that 56 | start_year = dateutil.parser.parse('%d/1/2 00:00:00Z' % tax_year) 57 | end_year = dateutil.parser.parse('%d/1/2 00:00:00Z' % (tax_year+1)) 58 | full_orders = [] 59 | # Loop through the sell orders and find the best cost basis 60 | for sell_index in range(len(sells_sorted)): 61 | sell_time = sells_sorted[sell_index][0] 62 | product = sells_sorted[sell_index][1] 63 | # Loop through the buy index to get the cost basis 64 | # Create a cost basis and subtract sell volume 65 | # Continue looping until sell volume goes away 66 | count = 0 67 | while sells_sorted[sell_index][4] > 0 and count < 1 and (start_year < sell_time < end_year): 68 | count = 0 69 | max_cost_index = -1 70 | max_cost = 0 71 | max_cost_volume = -1 72 | for buy_index in range(len(buys_sorted)): 73 | # See if the buy is the correct product and earlier than the sell time and there are coins left 74 | if (buys_sorted[buy_index][1] == product) and (sell_time >= buys_sorted[buy_index][0]) and \ 75 | (buys_sorted[buy_index][4] > 0): 76 | cost = buys_sorted[buy_index][5] 77 | # See if the max cost is higher 78 | if cost > max_cost: 79 | max_cost_index = buy_index 80 | max_cost = cost 81 | max_cost_volume = buys_sorted[buy_index][4] 82 | # If no cost basis was found, see if the coin was forked 83 | if max_cost_volume < 0 and is_forked(product): 84 | print("Found a forked coin") 85 | print(sells_sorted[sell_index]) 86 | # Set forked coin cost basis (0) 87 | max_cost = 0 88 | max_cost_volume = sells_sorted[sell_index][4] 89 | # See if the max cost volume is still negative 90 | if max_cost_volume < 0: 91 | print("WARNING! COULD NOT FIND A COST BASIS FOR sell_index=%d!" % sell_index) 92 | print(sells_sorted[sell_index]) 93 | count = 1 94 | else: 95 | cost_basis_volume = min(max_cost_volume, sells_sorted[sell_index][4]) 96 | # reduce the buy and sell volumes 97 | # Make sure the max_cost_index is not -1 (forked coin airdrop) 98 | if max_cost_index >= 0: 99 | bought_time = buys_sorted[max_cost_index][0] 100 | buys_sorted[max_cost_index][4] = round(buys_sorted[max_cost_index][4] - cost_basis_volume, 8) 101 | #cost_basis_per_coin = cost 102 | cost_basis_per_coin = max_cost 103 | else: 104 | # This is a forked coin, get the forked date 105 | bought_time = get_forked_time(product) 106 | cost_basis_per_coin = 0 107 | sells_sorted[sell_index][4] = round(sells_sorted[sell_index][4] - cost_basis_volume, 8) 108 | 109 | # Full order [description, date acquired, date sold, proceeds, cost basis, gain/loss, datetimebought, datetimesold] 110 | full_orders.append(['%1.8f ' % cost_basis_volume + product, 111 | bought_time.strftime('%m/%d/%Y'), 112 | sell_time.strftime('%m/%d/%Y'), 113 | cost_basis_volume*sells_sorted[sell_index][5], 114 | cost_basis_volume*cost_basis_per_coin, 115 | cost_basis_volume*(sells_sorted[sell_index][5]-cost_basis_per_coin), 116 | bought_time, sell_time]) 117 | return full_orders 118 | -------------------------------------------------------------------------------- /CryptoTaxes.py: -------------------------------------------------------------------------------- 1 | # This will calculate cryptocurrency taxes based off coinbase, gdax, and bittrex logs 2 | import gdax_reader 3 | import bittrex_reader 4 | import coinbase_reader 5 | import fill_8949 6 | import cPickle as pickle 7 | import os 8 | import turbo_tax 9 | import argparse 10 | import cost_basis 11 | import dateutil.parser 12 | import sys 13 | import copy 14 | 15 | 16 | def fix_orders(orders): 17 | buys_fixed = [] 18 | sells_fixed = [] 19 | for order in orders: 20 | # See if the exchange currency is BTC 21 | if order[6] == 'BTC': 22 | # This is a coin-coin transaction 23 | # We need to get the btc value in $$ and create another trade (a sell order) 24 | bitcoin_price_usd = gdax_reader.get_btc_price(order[0]) 25 | cost_btc = order[3] 26 | cost_usd = cost_btc * bitcoin_price_usd 27 | cost_per_coin_usd = cost_usd/order[4] 28 | # get the coin name 29 | product = order[1] 30 | # Fix any coin discrepancies (right now call all bitcoin cash BCH, sometimes it is called BCC) 31 | if product == 'BCC': 32 | product = 'BCH' 33 | if order[2] == 'buy': 34 | buys_fixed.append([order[0], product, 'buy', cost_usd, order[4], cost_per_coin_usd, 'USD']) 35 | sells_fixed.append([order[0], 'BTC', 'sell', cost_usd, order[3], bitcoin_price_usd, 'USD']) 36 | elif order[2] == 'sell': 37 | sells_fixed.append([order[0], product, 'sell', cost_usd, order[4], cost_per_coin_usd, 'USD']) 38 | buys_fixed.append([order[0], 'BTC', 'buy', cost_usd, order[3], bitcoin_price_usd, 'USD']) 39 | else: 40 | print("WEIRD! Unknown order buy sell type!") 41 | print(order) 42 | else: 43 | # This order was already paid/received with USD 44 | if order[2] == 'buy': 45 | buys_fixed.append(order) 46 | elif order[2] == 'sell': 47 | sells_fixed.append(order) 48 | else: 49 | print("WEIRD! Unknown order buy/sell type!") 50 | print(order) 51 | return buys_fixed, sells_fixed 52 | 53 | 54 | if __name__ == '__main__': 55 | # Get the user's name and social security number 56 | # Parse potential inputs 57 | parser = argparse.ArgumentParser() 58 | parser.add_argument('-name', default='Full Name', help='Your full name for filling out form 8949.') 59 | parser.add_argument('-social', default='123456789', help='Your social security number for filling out form 8949.') 60 | parser.add_argument('-year', type=int, default=2017, help='The tax year you want to fill out.') 61 | parser.add_argument('-startyear', type=int, default=0, help='The year to start looking for buy orders. ' + 62 | 'Use this if you have the cost basis for previous ' + 63 | 'years (pass the filename with -costbasis)') 64 | parser.add_argument('-costbasis', default='', help='An optional file containing the cost basis of coins not ' + 65 | 'included in your GDAX, Coinbase, or Bittrex history.') 66 | parser.add_argument('--download', action='store_true', help='Use this flag to download the transaction history. ' + 67 | 'Otherwise the data will be loaded from save.p') 68 | parser.add_argument('--turbotax', action='store_true', help='Use this flag to make a Turbo Tax txf import file.') 69 | parser.add_argument('--form8949', action='store_true', help='Use this flag to make the IRS form 8949 pdfs.') 70 | parser.add_argument('--saveorders', action='store_true', help='Use this flag to save the orders in a Python ' + 71 | 'pickle file.') 72 | # Use a preset argument list if using pycharm console 73 | if 'pydevconsole' in sys.argv[0]: 74 | args = parser.parse_args([ 75 | '-name', "Glenn Sugar", 76 | '-year', '2017', 77 | '-startyear', '2017']) 78 | else: 79 | args = parser.parse_args() 80 | 81 | if args.download: 82 | # Read in the GDAX buys and sells 83 | gdax_buys, gdax_sells = gdax_reader.get_buys_sells() 84 | # Read in the Coinbase buys and sells 85 | coinbase_buys, coinbase_sells = coinbase_reader.get_buys_sells() 86 | # Read in the Bittrex buys and sells 87 | bittrex_buys, bittrex_sells = bittrex_reader.get_buys_sells() 88 | # Go through the buys and sells and see if they are coin-coin transactions 89 | # Fixed means that coin-coin transactions are now coin-usd, usd-coin 90 | print('Fixing coin-coin transactions...') 91 | buys_fixed = [] 92 | sells_fixed = [] 93 | for orders in [coinbase_buys, coinbase_sells, gdax_buys, gdax_sells, bittrex_buys, bittrex_sells]: 94 | b, s = fix_orders(orders) 95 | buys_fixed += b 96 | sells_fixed += s 97 | 98 | # See if any buy orders should be removed due to startyear input argument 99 | if args.startyear > 0: 100 | # Loop through the buys and remove any that are before the startyear 101 | buys_fixed_startyear = [] 102 | startyear_buys = dateutil.parser.parse('%d/1/2 00:00:00Z' % args.startyear) 103 | for b in buys_fixed: 104 | if b[0] >= startyear_buys: 105 | buys_fixed_startyear.append(b) 106 | buys_fixed = buys_fixed_startyear 107 | 108 | # Add cost basis file buys if needed 109 | if len(args.costbasis) > 0: 110 | # parse the costbasis file 111 | other_buys = cost_basis.parse_cost_basis_file(args.costbasis) 112 | buys_fixed += other_buys 113 | # sort the buys and sells by date 114 | print('Sorting the buy and sell orders by time') 115 | buys_sorted = sorted(buys_fixed, key=lambda buy_order: buy_order[0]) 116 | sells_sorted = sorted(sells_fixed, key=lambda buy_order: buy_order[0]) 117 | # Get the full order information to be used on form 8949 (use list to prevent overwriting buys/sells) 118 | full_orders = cost_basis.get_cost_basis(copy.deepcopy(sells_sorted), copy.deepcopy(buys_sorted), 119 | basis_type='highest', tax_year=args.year) 120 | # Save the files in a pickle 121 | if args.saveorders: 122 | pickle.dump([buys_sorted, sells_sorted, full_orders], open("save.p", "wb")) 123 | pickle.dump([buys_sorted, sells_sorted, full_orders, coinbase_buys, coinbase_sells, gdax_buys, gdax_sells, 124 | bittrex_buys, bittrex_sells], open("save_everything.p", "wb")) 125 | print('Orders saved into save.p and save_everything.p') 126 | else: 127 | # Load the transaction data 128 | [buys_sorted, sells_sorted, full_orders] = pickle.load(open("save.p", "rb")) 129 | print('Buy and sell orders are loaded.') 130 | # See if any buy orders should be removed due to startyear input argument 131 | if args.startyear > 0: 132 | # Loop through the buys and remove any that are before the startyear 133 | buys_fixed_startyear = [] 134 | startyear_buys = dateutil.parser.parse('%d/1/2 00:00:00Z' % args.startyear) 135 | for b in buys_sorted: 136 | if b[0] >= startyear_buys: 137 | buys_fixed_startyear.append(b) 138 | buys_sorted = buys_fixed_startyear 139 | # See if we should add more buys via a costbasis file 140 | if len(args.costbasis) > 0: 141 | # parse the costbasis file 142 | other_buys = cost_basis.parse_cost_basis_file(args.costbasis) 143 | buys_sorted += other_buys 144 | buys_sorted = sorted(list(buys_sorted), key=lambda buy_order: buy_order[0]) 145 | full_orders = cost_basis.get_cost_basis(copy.deepcopy(sells_sorted), copy.deepcopy(buys_sorted), 146 | basis_type='highest', tax_year=args.year) 147 | 148 | # Make the Turbo Tax import file 149 | if args.turbotax: 150 | print('Creating the Turbo Tax import file.') 151 | turbo_tax.make_txf(full_orders) 152 | 153 | # Make the 8949 forms 154 | if args.form8949: 155 | print('Creating the 8949 forms.') 156 | fill_8949.makePDF(full_orders, "test", args.name, args.social) 157 | 158 | # Get the net data (net cost basis, net revenue, net gain/loss 159 | net_cost = 0 160 | net_rev = 0 161 | net_gain = 0 162 | for o in full_orders: 163 | net_cost += o[4] 164 | net_rev += o[3] 165 | net_gain += o[5] 166 | 167 | # make a cumulative gain array for debugging purposes 168 | # cumulative_gains = [] 169 | # running_gain = 0 170 | # for i in range(len(full_orders)): 171 | # cumulative_gains.append(full_orders[i][5] + running_gain) 172 | # running_gain += full_orders[i][5] 173 | print('Done! Net Gain: %1.2f, Net Revenue: %1.2f, Net Cost: %1.2f' % (net_gain, net_rev, net_cost)) 174 | -------------------------------------------------------------------------------- /gdax_reader.py: -------------------------------------------------------------------------------- 1 | # This will read in gdax transactions 2 | import credentials 3 | try: 4 | import gdax 5 | except ImportError: 6 | import GDAX as gdax 7 | import dateutil.parser 8 | import datetime 9 | import pytz 10 | import time 11 | import cPickle as pickle 12 | import os 13 | from bisect import bisect 14 | 15 | 16 | # There should only be 4 transaction types, transfer, match, fee, rebate. We only care about fee and match 17 | good_transaction_types = ['fee', 'match'] 18 | # load the bitcoin price history if it exists 19 | if os.path.isfile('bitcoin_history.p'): 20 | bitcoin_history = pickle.load(open('bitcoin_history.p', 'rb')) 21 | else: 22 | bitcoin_history = [] 23 | 24 | 25 | def get_order_ids(history, ignore_products=[]): 26 | # Loop through the history 27 | # Transactions are in groups of 100 or less in history (a list of lists) 28 | # Get all of the order ids 29 | order_ids = [] 30 | for history_group in history: 31 | for transaction in history_group: 32 | if 'order_id' in transaction['details'] and 'product_id' in transaction['details']: 33 | if transaction['details']['order_id'] not in order_ids and \ 34 | transaction['details']['product_id'] not in ignore_products: 35 | order_ids.append(transaction['details']['order_id']) 36 | elif 'source' in transaction['details'] and transaction['details']['source'] == 'fork': 37 | # this was a forked coin deposit 38 | print(transaction['amount'] + transaction['details']['ticker'] + " obtained from a fork.") 39 | elif 'transfer_id' not in transaction['details']: 40 | print("No order_id or transfer_id in details for the following order (WEIRD!)") 41 | print(transaction) 42 | return order_ids 43 | 44 | 45 | def parse_order(order): 46 | if order['status'] == 'done': 47 | fees = float(order['fill_fees']) 48 | buysell = order['side'] 49 | order_time = dateutil.parser.parse(order['done_at']) 50 | product = order['product_id'][0:order['product_id'].index('-')] 51 | amount = float(order['filled_size']) 52 | exchange_currency = order['product_id'][order['product_id'].index('-')+1:] 53 | if buysell == 'sell': 54 | cost = float(order['executed_value']) - fees 55 | else: 56 | cost = float(order['executed_value']) + fees 57 | 58 | cost_per_coin = cost/amount 59 | else: 60 | print('Order status is not done! (WEIRD)') 61 | print(order) 62 | return [order_time, product, buysell, cost, amount, cost_per_coin, exchange_currency] 63 | 64 | 65 | # get the buys and sells for an account 66 | def get_account_transactions(client, account, ignore_products=[]): 67 | # Get the account history 68 | try: 69 | # history = client.getAccountHistory(account['id']) 70 | history = client.get_account_history(account['id']) 71 | except: 72 | time.sleep(5) 73 | # history = client.getAccountHistory(account['id']) 74 | history = client.get_account_history(account['id']) 75 | # Get all order ids from the account 76 | order_ids = get_order_ids(history, ignore_products=ignore_products) 77 | # Get the information from each order_id 78 | sells = [] 79 | buys = [] 80 | for order_id in order_ids: 81 | #order = parse_order(client.getOrder(order_id)) 82 | order = parse_order(client.get_order(order_id)) 83 | 84 | if len(order[1]) < 3: 85 | print('WEIRD ORDER. NO PRODUCT!!!') 86 | 87 | # put order in a buy or sell list 88 | if order[2] == 'sell': 89 | sells.append(order) 90 | elif order[2] == 'buy': 91 | buys.append(order) 92 | else: 93 | print('order is not buy or sell! WEIRD') 94 | print(order) 95 | return buys, sells 96 | 97 | 98 | def get_client(): 99 | client = gdax.AuthenticatedClient(credentials.gdax_key, credentials.gdax_secret, credentials.gdax_passphrase) 100 | return client 101 | 102 | 103 | def get_btc_price(order_time): 104 | if len(bitcoin_history) == 0: 105 | try: 106 | client = get_client() 107 | #history_btc = client.getProductHistoricRates(product='BTC-USD', start=order_time - datetime.timedelta(hours=1), 108 | # end=order_time) 109 | history_btc = client.get_product_historic_rates('BTC-USD', order_time - datetime.timedelta(hours=1), order_time) 110 | bitcoin_price = history_btc[0][4] 111 | except: 112 | time.sleep(5) 113 | client = get_client() 114 | history_btc = client.get_product_historic_rates('BTC-USD', order_time - datetime.timedelta(hours=1), order_time) 115 | bitcoin_price = history_btc[0][4] 116 | else: 117 | timestamps = [a[0] for a in bitcoin_history] 118 | ind = bisect(timestamps, order_time, hi=len(timestamps)-1) 119 | bitcoin_price = bitcoin_history[ind][1] 120 | 121 | return bitcoin_price 122 | 123 | 124 | def get_buys_sells(): 125 | # Get the buys and sells for all orders 126 | # connect to client first 127 | print('Connecting to GDAX...') 128 | client = get_client() 129 | print('Connected to GDAX!') 130 | # Get the GDAX accounts 131 | #accounts = client.getAccounts() 132 | accounts = client.get_accounts() 133 | # for account in accounts: 134 | # # Only use the USD and BTC accounts since they will contain all transaction ids 135 | # if account['currency'] == 'USD': 136 | # [buys_usd, sells_usd] = get_account_transactions(client, account) 137 | # elif account['currency'] == 'BTC': 138 | # [buys_btc, sells_btc] = get_account_transactions(client, account, ignore_products=['BTC-USD']) 139 | # return buys_usd+buys_btc, sells_usd+sells_btc 140 | return transactions_to_buysells(get_all_transactions(client, accounts)) 141 | 142 | 143 | def get_transactions_from_account(client, account): 144 | # Get the account history 145 | try: 146 | # history = client.getAccountHistory(account['id']) 147 | history = client.get_account_history(account['id']) 148 | except: 149 | time.sleep(5) 150 | # history = client.getAccountHistory(account['id']) 151 | history = client.get_account_history(account['id']) 152 | transactions = [] 153 | for history_group in history: 154 | for transaction in history_group: 155 | if transaction['type'] in good_transaction_types: 156 | # Save this transaction (time, currency, amount changed, order id, trade id) 157 | transactions.append([dateutil.parser.parse(transaction['created_at']), account['currency'], 158 | float(transaction['amount']), transaction['details']['order_id'], 159 | transaction['details']['trade_id'], transaction['type']]) 160 | return transactions 161 | 162 | 163 | def get_all_transactions(client, accounts): 164 | transactions = [] 165 | for account in accounts: 166 | transactions += get_transactions_from_account(client, account) 167 | # sort the transactions 168 | transactions.sort(key=lambda x: x[0]) 169 | return transactions 170 | 171 | 172 | def transactions_to_buysells(transactions): 173 | buys = [] 174 | sells = [] 175 | order_transactions = [] 176 | current_order_id = '' 177 | for transaction in transactions: 178 | # if current_order_id != transaction[3]: 179 | if current_order_id != transaction[4]: 180 | # save the order if it has been created 181 | if len(order_transactions) > 0: 182 | sell_amount = 0 183 | buy_amount = 0 184 | fee_amount = 0 185 | fee_product = '' 186 | for t in order_transactions: 187 | order_time = t[0] 188 | if t[5] == 'match': 189 | if t[2] < 0: 190 | sell_amount -= t[2] 191 | sell_product = t[1] 192 | else: 193 | buy_amount += t[2] 194 | buy_product = t[1] 195 | else: 196 | fee_amount += abs(t[2]) 197 | fee_product = t[1] 198 | if sell_product == fee_product: 199 | sell_amount += fee_amount 200 | elif buy_product == fee_product: 201 | buy_amount -= fee_amount 202 | # an order: [order_time, product, buysell, cost, amount, cost_per_coin, exchange_currency] 203 | if buy_product == 'USD': 204 | # We sold a coin for USD 205 | sells.append([order_time, sell_product, 'sell', buy_amount, sell_amount, buy_amount/sell_amount, 206 | buy_product]) 207 | elif sell_product == 'USD': 208 | # We bought a coin using USD 209 | buys.append([order_time, buy_product, 'buy', sell_amount, buy_amount, sell_amount/buy_amount, 210 | sell_product]) 211 | elif buy_product == 'BTC': 212 | # We sold a coin for BTC 213 | sells.append([order_time, sell_product, 'sell', buy_amount, sell_amount, buy_amount/sell_amount, 214 | buy_product]) 215 | elif sell_product == 'BTC': 216 | # We bought a coin using BTC 217 | buys.append([order_time, buy_product, 'buy', sell_amount, buy_amount, sell_amount/buy_amount, 218 | sell_product]) 219 | else: 220 | print('Unknown trading pair!!! Neither BTC nor USD used in the transaction!') 221 | order_transactions = [transaction] 222 | # current_order_id = transaction[3] 223 | current_order_id = transaction[4] 224 | else: 225 | # batch the transactions in the order 226 | order_transactions.append(transaction) 227 | 228 | return buys, sells 229 | 230 | 231 | # This will get the bitcoin price history 232 | def get_bitcoin_price_history(start_date='2017/1/1', end_date='', save=False): 233 | client = get_client() 234 | date = start_date 235 | bitcoin_history = [] 236 | if isinstance(date, basestring): 237 | date = dateutil.parser.parse(date) 238 | if isinstance(end_date, basestring): 239 | if len(end_date) == 0: 240 | end_date = datetime.datetime.now() 241 | else: 242 | end_date = dateutil.parser.parse(end_date) 243 | while date < end_date: 244 | history_btc = client.get_product_historic_rates('BTC-USD', date, date+datetime.timedelta(days=12), 3600) 245 | while 'message' in history_btc: 246 | print history_btc['message'] + ' Sleeping for 30 seconds...' 247 | time.sleep(30) 248 | history_btc = client.get_product_historic_rates('BTC-USD', date, date+datetime.timedelta(days=12), 3600) 249 | date = date + datetime.timedelta(days=7) 250 | for price in history_btc: 251 | bitcoin_history.append([datetime.datetime.fromtimestamp(price[0], pytz.UTC), price[4]]) 252 | # sort the history 253 | bitcoin_history.sort(key=lambda x: x[0]) 254 | if save: 255 | pickle.dump(bitcoin_history, open("bitcoin_history.p", "wb")) 256 | else: 257 | return bitcoin_history 258 | --------------------------------------------------------------------------------