Skip to content

Commit

Permalink
Bitstamp, versioning, custom parse and write
Browse files Browse the repository at this point in the history
Parsing Bitstamp transaction exports now possible
Support for custom parse functions
New date-based versioning scheme
  • Loading branch information
bartbroere committed Apr 21, 2017
1 parent fa11d35 commit 19c9166
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 35 deletions.
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ def readme():

setup(
name='ToLedger',
version='1.0',
version='2017.04.21',
description='Convert bank statements to Ledger',
long_description=readme(),
keywords=["ledger", "convert", "conversion", "bank", "ing", "nl", "accounting", "command", "commandline", "cli"],
keywords=["ledger", "convert", "conversion", "bank", "ing", "nl",
"accounting", "command", "commandline", "cli", "bitstamp"],
packages=['toledger'],
scripts=['toledger/toledger.py'],
url='https://github.com/bartbroere/toledger/',
Expand Down
74 changes: 56 additions & 18 deletions toledger/bankstatements.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,66 @@
# Assets:Bitstamp 10 BTC
# Equity:Unspecified

bitstamp_net = {"transactiontypes": "Type",
"Withdrawal": {},
"Market": {},
"Sub Account Transfer": {},
"Deposit": {}}
def bitstamp_net_parse(header, entry, format, a):
parsed = {"meta": {}}
for name, field in bitstamp_net["fields"].items():
if field == "meta":
parsed["meta"][name] = entry[header.index(name)]
else:
parsed[field] = entry[header.index(name)]
if parsed["name"] in ["Deposit", "Withdrawal"]:
if parsed["name"] == "Withdrawal":
parsed["direction"] = "-"
elif parsed["name"] == "Market":
if parsed["meta"]["Sub Type"] == "Sell":
parsed["direction"] = "-"
elif parsed["name"] == "Sub Account Transfer":
if parsed["value"] == "Addition":
parsed["direction"] = "-"
parsed["destination"] = parsed["meta"]["Account"].replace(
" ", "")
else:
raise NotImplementedError("This transaction type is unknown")
parsed["amount"] = parsed.get("direction", "")+parsed["amount"]
#if a["--hash"]:
# parsed["hash"] = hashlib.sha256(" ".join(entry)).hexdigest()
return parsed

bitstamp_net = {"fields": {"Type": ["name", "direction"],
def bitstamp_net_write(output, a, **data):
#account = a.get("--name", "Assets:Bitstamp")
account = "Assets:Bitstamp"
output.write("\n")
output.write(data["date"]+" "+data["name"])
if data["name"] in ["Deposit", "Withdrawal"]:
output.write("\n\t"+account+"\t"+data["amount"])
output.write("\n\tAssets:Unspecified\n")
elif data["name"] == "Market":
output.write("\n\t"+account+"\t"+
data["amount"]+" @@ "+data["value"])
output.write("\n\tAssets:Bitstamp\n")
elif data["name"] == "Sub Account Transfer":
output.write("\n\t"+account+"\t"+data["amount"])
output.write("\n\t"+account+":"+data["destination"]+"\n")
for metakey, metavalue in data["meta"].items():
output.write("\t ; "+metakey.replace(" ", "")+": "+metavalue+"\n")
if "hash" in data:
output.write("\t ; hash: "+data["hash"]+"\n")
output.write("\n")
return

bitstamp_net = {"fields": {"Type": "name",
"Datetime": "date",
"Account": "meta",
"Amount": "amount",
"Value": "value",
"Rate": "meta",
"Fee": "meta",
"Sub Type": "direction"},
"interpretation": {"byfield": "Type",
"fields": {"Market": "assetconv",
"Deposit": "default",
"Withdrawal": "default",
"Sub Account Transfer": "subaccount"}
"directionout": ["Sell", "Withdrawal"],
"dateformat": ""},
"Sub Type": "meta"},
"interpretation": {}, #handled by bitstamp_net_parse
"properties": {"quotechar": "\"",
"delimiter": ","}}
"delimiter": ","},
"parse": bitstamp_net_parse,
"write": bitstamp_net_write}

ing_nl = {"fields": {"Datum": "date",
"Naam / Omschrijving": "name",
Expand All @@ -44,7 +81,8 @@
"MutatieSoort": "meta",
"Mededelingen": "meta"},
"interpretation": {"directionout": "Af",
"currency": {"symbol": u"\u20ac", #TODO fix unicode issue
"currency": {"symbol": u"\u20ac",
#TODO fix unicode issue
"code": "EUR"},
"separators": {"thousandseparator": ".",
"decimalseparator": ","},
Expand All @@ -53,7 +91,7 @@
"delimiter": ","},
"type": "default"}

kraken_com = {"group_by": "refid"
kraken_com = {"group_by": "refid",
"fields": {"time": "date",
"refid": "name",
"txid": "source",
Expand All @@ -63,4 +101,4 @@
"fee": "meta",
"balance": "balance"},
"interpretation": {},
"properties": {}}
"properties": {}}
30 changes: 15 additions & 15 deletions toledger/toledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
standalone ledger file. These files should always
have a balance of 0 to be valid.
--from=<from> Account to get balances from.
Default Equity:[Unspecified | IBAN]
--to=<to> Account to send transactions to.
Default Expenses:[Unspecified | IBAN]
--name=<name> Account name. Default Assets:[IBAN]
--name=<name> Account name.
--hash Save a hash of the transaction for duplicate removal
-c --code Use the currency code, e.g. EUR
-s --symbol Use the currency symbol, e.g. €
Expand Down Expand Up @@ -50,6 +48,8 @@ def main(a):

def parse(header, entry, format):
# TODO: Smart transaction labeling
if "parse" in specification(a["<format>"]).keys():
return specification(format)["parse"](header, entry, format, a)
parsed = {"meta": {}}
for name, field in specification(format)["fields"].items():
if field == "meta": parsed["meta"][name] = entry[header.index(name)]
Expand All @@ -76,26 +76,26 @@ def parse(header, entry, format):
return parsed

def specification(format):
try:
specification = getattr(bankstatements, format)
return specification
except:
raise NotImplementedError("The specified format does not exist")
try: return getattr(bankstatements, format)
except: raise NotImplementedError("The specified format does not exist")

def properties(format): return specification(format)["properties"]

def write(output, **data):
if "write" in specification(a["<format>"]).keys():
return specification(a["<format>"])["write"](output, a, **data)
output.write("\n")
output.write(data["date"]+" "+data["name"])
output.write("\n\t")
if data["direction"] == "-":
output.write("Expenses:"+max([data["destination"], "Unspecified"], key=len)+
if data.get("direction", "") == "-":
output.write("Expenses:"+data.get("destination", "Unspecified")+
" "+"\n\t")
else:
output.write("Equity:"+max([data["destination"], "Unspecified"], key=len)+
output.write("Equity:"+data.get("destination", "Unspecified")+
" "+"\n\t")
output.write("Assets:"+data["source"]+"\t"+data["direction"]+
data["amount"]+"\n")
output.write("Assets:"+data.get("source", "Unspecified")+"\t"+
data.get("direction", "Unspecified")+
data.get("amount", "Unspecified")+"\n")
for metakey, metavalue in data["meta"].items():
output.write("\t ; "+metakey.replace(" ", "")+": "+metavalue+"\n")
if "hash" in data:
Expand All @@ -104,5 +104,5 @@ def write(output, **data):
return

if __name__ == '__main__':
a = docopt(__doc__, version='ToLedger 1.0')
main(a)
a = docopt(__doc__, version='ToLedger 2017.04.21')
main(a)

0 comments on commit 19c9166

Please sign in to comment.