Skip to content

Commit

Permalink
Add importer for Kraken
Browse files Browse the repository at this point in the history
  • Loading branch information
OSadovy committed Oct 8, 2023
1 parent 0841aff commit 03ea68a
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 0 deletions.
2 changes: 2 additions & 0 deletions my_import.py.sample
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from uabean.importers import (
pumb_xls,
procredit_business,
procredit_xls,
kraken,
)

from uabean.hooks import detect_transfers
Expand Down Expand Up @@ -120,6 +121,7 @@ CONFIG = [
("USD", "UA000000000000000000000000000"): "Assets:Procreditbank:Cash:USD",
}
),
kraken.Importer(),
]


Expand Down
182 changes: 182 additions & 0 deletions src/uabean/importers/kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""Imports transactions from Kraken crypto exchange ledger csv file.
The CSV header is the following:
"txid","refid","time","type","subtype","aclass","asset","amount","fee","balance"
"""


import csv

import beangulp
import dateutil.parser
from beancount.core import data, flags
from beancount.core.amount import Amount
from beancount.core.number import D

from uabean.importers.mixins import IdentifyMixin


class Importer(IdentifyMixin, beangulp.Importer):
FLAG = flags.FLAG_OKAY
matchers = [
("content", __doc__.split("\n")[-2]),
("mime", "text/csv"),
]

def __init__(
self,
spot_account="Assets:Kraken:Spot",
staking_account="Assets:Kraken:Staking",
fee_account="Expenses:Fees:Kraken",
staking_income_account="Income:Staking:Kraken",
):
self.spot_account = spot_account
self.staking_account = staking_account
self.fee_account = fee_account
self.staking_income_account = staking_income_account
super().__init__()

def account(self, _):
return "kraken"

def parse_date(self, s):
return dateutil.parser.parse(s)

def extract(self, filename, existing_entries=None):
entries = []
balances = {}
for index, row in enumerate(csv.DictReader(open(filename)), 1):
meta = data.new_metadata(filename, index)
entry = self.get_entry_from_row(row, meta, balances)
if entry is not None:
entries.append(entry)
for (account, currency), (date, balance) in balances.items():
entries.append(
data.Balance(
data.new_metadata(filename, -1),
date.date() + dateutil.relativedelta.relativedelta(days=1),
account,
Amount(balance, currency),
None,
None,
)
)
return entries

def get_entry_from_row(self, row, meta, balances):
if not row["txid"]:
return None
date = self.parse_date(row["time"])
meta["time"] = date.strftime("%H:%M:%S")
postings = []
match (row["type"], row["subtype"]):
case ("deposit", "") | ("transfer", "spottostaking"):
postings.append(
data.Posting(
self.spot_account,
self.ammount_from_row(row),
None,
None,
None,
None,
)
)
self.update_balance(
balances,
date,
self.spot_account,
row["asset"],
D(row["balance"]),
)
case ("transfer", "stakingfromspot"):
postings.append(
data.Posting(
self.staking_account,
self.ammount_from_row(row),
None,
None,
None,
None,
)
)
self.update_balance(
balances,
date,
self.staking_account,
row["asset"],
D(row["balance"]),
)
case ("staking", ""):
postings.append(
data.Posting(
self.staking_account,
self.ammount_from_row(row),
None,
None,
None,
None,
)
)
postings.append(
data.Posting(
self.staking_income_account,
None,
None,
None,
None,
None,
)
)
self.update_balance(
balances,
date,
self.staking_account,
row["asset"],
D(row["balance"]),
)
case _:
raise NotImplementedError(
f"Unknown transaction type {row['type']} row['subtype']"
)
if float(row["fee"]):
postings.append(
data.Posting(
self.fee_account,
self.ammount_from_row(row),
None,
None,
None,
None,
)
)
return data.Transaction(
meta,
date.date(),
self.FLAG,
"",
"",
data.EMPTY_SET,
data.EMPTY_SET,
postings,
)

def ammount_from_row(self, row):
return Amount(D(row["amount"]), row["asset"])

def update_balance(self, balances, date, account, currency, balance):
if (account, currency) not in balances:
balances[(account, currency)] = (date, balance)
else:
balances[(account, currency)] = max(
balances[(account, currency)], (date, balance)
)


def get_test_importer():
return Importer()


if __name__ == "__main__":
from beangulp.testing import main

main(get_test_importer())
9 changes: 9 additions & 0 deletions tests/importers/kraken/ledgers.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"txid","refid","time","type","subtype","aclass","asset","amount","fee","balance"
"","TX-ID1","2000-01-01 08:48:50","deposit","","currency","USDC",1000.00000000,0.00000000,""
"TX-ID2","TX-ID1","2000-01-01 08:57:47","deposit","","currency","USDC",1000.00000000,0.00000000,1000.00000000
"","TX-id3","2000-01-01 09:00:21","withdrawal","","currency","USDC",-1000.00000000,0.00000000,""
"TX-id4","TX-ID3","2000-01-01 09:00:22","transfer","spottostaking","currency","USDC",-1000.00000000,0.00000000,0.00000000
"","TX-ID5","2000-01-01 09:00:30","deposit","","currency","USDC.M",1000.00000000,0.00000000,""
"TX-ID6","TX-ID5","2000-01-01 09:00:32","transfer","stakingfromspot","currency","USDC.M",1000.00000000,0.00000000,1000.00000000
"","TX-ID7","2001-01-02 14:30:21","deposit","","currency","USDC.M",1.12345678,0.00000000,""
"TX-ID8","TX-ID7","2000-01-02 14:33:28","staking","","currency","USDC.M",1.12345678,0.00000000,1001.12345678
24 changes: 24 additions & 0 deletions tests/importers/kraken/ledgers.csv.beancount
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
;; Account: kraken
;; Date:
;; Name:

2000-01-01 *
time: "08:57:47"
Assets:Kraken:Spot 1000.00000000 USDC

2000-01-01 *
time: "09:00:22"
Assets:Kraken:Spot -1000.00000000 USDC

2000-01-01 *
time: "09:00:32"
Assets:Kraken:Staking 1000.00000000 USDC.M

2000-01-02 balance Assets:Kraken:Spot 0.00000000 USDC

2000-01-02 *
time: "14:33:28"
Assets:Kraken:Staking 1.12345678 USDC.M
Income:Staking:Kraken

2000-01-03 balance Assets:Kraken:Staking 1001.12345678 USDC.M
7 changes: 7 additions & 0 deletions tests/importers/test_kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from common import run_importer_test

from uabean.importers.kraken import get_test_importer


def test_kraken_importer(capsys):
run_importer_test(get_test_importer(), capsys)

0 comments on commit 03ea68a

Please sign in to comment.