Skip to content

Instantly share code, notes, and snippets.

@kylemanna
Last active April 17, 2021 19:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kylemanna/4104c84a0d1293d5af8e53572b5674a4 to your computer and use it in GitHub Desktop.
Save kylemanna/4104c84a0d1293d5af8e53572b5674a4 to your computer and use it in GitHub Desktop.
#armory #cointracking #taxes #fml
#!/usr/bin/env python3
"""Script to convert Bitcoin Armory transaction history to a format that Cointracking.info CSV"""
import argparse
import collections
import csv
import datetime
import json
import os
def parse_armory_csv(fp):
# Consume header lines
last_pos = fp.tell()
while not fp.readline().startswith('Date'):
last_pos = fp.tell()
fp.seek(last_pos)
dialect = csv.excel
dialect.skipinitialspace = True
reader = csv.DictReader(fp, dialect=dialect)
data = []
for row in reader:
float_keys = ['Number of Confirmations',
'Credit',
'Debit',
'Fee (paid by this wallet)',
'Wallet Balance',
'Total Balance'
]
for k in float_keys:
row[k] = float(row[k]) if len(row[k]) > 0 else 0
#row['Date'] = datetime.datetime.strptime(row['Date'], '%Y-%b-%d %I:%M%p').isoformat()
row['Date'] = datetime.datetime.strptime(row['Date'], '%Y-%b-%d %I:%M%p')
data.append(row)
return data
def armory_to_cointracking(armory):
data = []
for a in armory:
type = 'Unknown'
if a['Credit'] and not a['Debit']:
if a['Label'].lower().startswith('mining'):
type = 'Mining'
else:
type = 'Deposit'
elif not a['Credit'] and a['Debit']:
type = 'Withdrawal'
d = collections.OrderedDict()
d['Type'] = type
d['Buy'] = a['Credit']
d['Buy_Cur.'] = 'BTC'
d['Sell'] = a['Debit']
d['Sell_Cur.'] = 'BTC'
d['Fee'] = a['Fee (paid by this wallet)']
d['Fee_Cur.'] = 'BTC'
d['Exchange'] = 'Armory@' + a['Wallet ID']
d['Group'] = a['Wallet Name']
d['Comment'] = a['Label']
d['Date'] = a['Date'].strftime('%Y-%m-%d %H:%M:%S')
d['Trade ID'] = 'armory:{}:{}'.format(a['Wallet ID'], a['Transaction ID'])
data.append(d)
if '(STS)' in a['Label'] and type == 'Deposit':
# This was a sell to self aka transfer
d2 = d.copy()
d2['Type'] = 'Withdrawal'
d2['Buy'] = a['Debit']
d2['Sell'] = a['Credit'] + a['Fee (paid by this wallet)']
d2['Fee'] = 0
data.append(d2)
return data
def export_cointracking_csv(fp, ct):
keys = ct[0].keys()
dialect=csv.unix_dialect
# Use the CSV library to write the header that has multiple 'Cur.' headers:
# "Type","Buy","Cur.","Sell","Cur.","Fee","Cur.","Exchange","Group","Comment","Date"
keys_mutilated = ['Cur.' if k.endswith('_Cur.') else k for k in keys]
csv.DictWriter(fp, fieldnames=keys_mutilated, dialect=dialect).writeheader()
writer = csv.DictWriter(fp, fieldnames=keys, dialect=dialect)
for row in ct:
writer.writerow(row)
# Delete trailing newline which causes import errors with CoinTracking
# creating duplicate final entries
fp.seek(fp.tell()-1, os.SEEK_SET)
fp.truncate()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Convert Armory Transactions to CoinTracking Trade Table')
parser.add_argument('file', metavar='FILE', type=str, nargs='*',
help='CSV files to process')
args = parser.parse_args()
for f_in_name in args.file:
f_out_name = os.path.splitext(f_in_name)[0] + '.cointracking.csv'
with open(f_in_name,'r') as f_in, open(f_out_name,'w') as f_out:
armory = parse_armory_csv(f_in)
ct = armory_to_cointracking(armory)
export_cointracking_csv(f_out, ct)
#print(json.dumps(ct))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment