tariochbctools 0.36__py2.py3-none-any.whl → 0.38__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tariochbctools/importers/general/priceLookup.py +12 -3
- tariochbctools/importers/netbenefits/__init__.py +0 -0
- tariochbctools/importers/netbenefits/importer.py +173 -0
- tariochbctools/importers/revolut/importer.py +43 -20
- tariochbctools/plugins/check_portfolio_sum.py +1 -0
- tariochbctools/plugins/generate_base_ccy_prices.py +1 -0
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/METADATA +9 -9
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/RECORD +12 -10
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/WHEEL +1 -1
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/LICENSE.txt +0 -0
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/entry_points.txt +0 -0
- {tariochbctools-0.36.dist-info → tariochbctools-0.38.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,25 @@
|
|
1
1
|
from datetime import date
|
2
2
|
|
3
3
|
from beancount.core import amount, prices
|
4
|
+
from beancount.core.number import D
|
4
5
|
|
5
6
|
|
6
7
|
class PriceLookup:
|
7
8
|
def __init__(self, existing_entries, baseCcy: str):
|
8
|
-
|
9
|
+
if existing_entries:
|
10
|
+
self.priceMap = prices.build_price_map(existing_entries)
|
11
|
+
else:
|
12
|
+
self.priceMap = None
|
9
13
|
self.baseCcy = baseCcy
|
10
14
|
|
11
15
|
def fetchPriceAmount(self, instrument: str, date: date):
|
12
|
-
|
13
|
-
|
16
|
+
if self.priceMap:
|
17
|
+
price = prices.get_price(
|
18
|
+
self.priceMap, tuple([instrument, self.baseCcy]), date
|
19
|
+
)
|
20
|
+
return price[1]
|
21
|
+
else:
|
22
|
+
return D(1)
|
14
23
|
|
15
24
|
def fetchPrice(self, instrument: str, date: date):
|
16
25
|
if instrument == self.baseCcy:
|
File without changes
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import csv
|
2
|
+
from collections.abc import Iterable
|
3
|
+
from datetime import date
|
4
|
+
from io import StringIO
|
5
|
+
|
6
|
+
from beancount.core import amount, data
|
7
|
+
from beancount.core.number import D
|
8
|
+
from beancount.core.position import CostSpec
|
9
|
+
from beancount.ingest import importer
|
10
|
+
from beancount.ingest.importers.mixins import identifier
|
11
|
+
from dateutil.parser import parse
|
12
|
+
|
13
|
+
from tariochbctools.importers.general.priceLookup import PriceLookup
|
14
|
+
|
15
|
+
|
16
|
+
class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
17
|
+
"""An importer for Fidelity Netbenefits Activity CSV files."""
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
regexps: str | Iterable[str],
|
22
|
+
cashAccount: str,
|
23
|
+
investmentAccount: str,
|
24
|
+
dividendAccount: str,
|
25
|
+
taxAccount: str,
|
26
|
+
capGainAccount: str,
|
27
|
+
symbol: str,
|
28
|
+
ignoreTypes: Iterable[str],
|
29
|
+
baseCcy: str,
|
30
|
+
):
|
31
|
+
identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
|
32
|
+
self.cashAccount = cashAccount
|
33
|
+
self.investmentAccount = investmentAccount
|
34
|
+
self.dividendAccount = dividendAccount
|
35
|
+
self.taxAccount = taxAccount
|
36
|
+
self.capGainAccount = capGainAccount
|
37
|
+
self.symbol = symbol
|
38
|
+
self.ignoreTypes = ignoreTypes
|
39
|
+
self.baseCcy = baseCcy
|
40
|
+
|
41
|
+
def name(self):
|
42
|
+
return super().name() + self.cashAccount
|
43
|
+
|
44
|
+
def file_account(self, file):
|
45
|
+
return self.cashAccount
|
46
|
+
|
47
|
+
def extract(self, file, existing_entries):
|
48
|
+
entries = []
|
49
|
+
|
50
|
+
self.priceLookup = PriceLookup(existing_entries, self.baseCcy)
|
51
|
+
|
52
|
+
with StringIO(file.contents()) as csvfile:
|
53
|
+
reader = csv.DictReader(
|
54
|
+
csvfile,
|
55
|
+
[
|
56
|
+
"Transaction date",
|
57
|
+
"Transaction type",
|
58
|
+
"Investment name",
|
59
|
+
"Shares",
|
60
|
+
"Amount",
|
61
|
+
],
|
62
|
+
delimiter=",",
|
63
|
+
skipinitialspace=True,
|
64
|
+
)
|
65
|
+
next(reader)
|
66
|
+
for row in reader:
|
67
|
+
if not row["Transaction type"]:
|
68
|
+
break
|
69
|
+
|
70
|
+
if row["Transaction type"] in self.ignoreTypes:
|
71
|
+
continue
|
72
|
+
|
73
|
+
book_date = parse(row["Transaction date"].strip()).date()
|
74
|
+
amt = amount.Amount(D(row["Amount"].replace("$", "")), "USD")
|
75
|
+
shares = None
|
76
|
+
if row["Shares"] != "-":
|
77
|
+
shares = amount.Amount(D(row["Shares"]), self.symbol)
|
78
|
+
|
79
|
+
metakv = {}
|
80
|
+
|
81
|
+
if not amt and not shares:
|
82
|
+
continue
|
83
|
+
|
84
|
+
meta = data.new_metadata(file.name, 0, metakv)
|
85
|
+
description = row["Transaction type"].strip()
|
86
|
+
|
87
|
+
if "TAX" in description:
|
88
|
+
postings = self.__createDividend(amt, book_date, self.taxAccount)
|
89
|
+
elif "DIVIDEND" in description:
|
90
|
+
postings = self.__createDividend(
|
91
|
+
amt, book_date, self.dividendAccount
|
92
|
+
)
|
93
|
+
elif "YOU BOUGHT" in description:
|
94
|
+
postings = self.__createBuy(amt, shares, book_date)
|
95
|
+
elif "YOU SOLD" in description:
|
96
|
+
postings = self.__createSell(amt, shares, book_date)
|
97
|
+
else:
|
98
|
+
postings = [
|
99
|
+
data.Posting(self.cashAccount, amt, None, None, None, None),
|
100
|
+
]
|
101
|
+
|
102
|
+
if shares is not None:
|
103
|
+
postings.append(
|
104
|
+
data.Posting(
|
105
|
+
self.investmentAccount, shares, None, None, None, None
|
106
|
+
),
|
107
|
+
)
|
108
|
+
|
109
|
+
entry = data.Transaction(
|
110
|
+
meta,
|
111
|
+
book_date,
|
112
|
+
"*",
|
113
|
+
"",
|
114
|
+
description,
|
115
|
+
data.EMPTY_SET,
|
116
|
+
data.EMPTY_SET,
|
117
|
+
postings,
|
118
|
+
)
|
119
|
+
entries.append(entry)
|
120
|
+
|
121
|
+
return entries
|
122
|
+
|
123
|
+
def __createBuy(self, amt: amount, shares: amount, book_date: date):
|
124
|
+
price = self.priceLookup.fetchPrice("USD", book_date)
|
125
|
+
cost = CostSpec(
|
126
|
+
number_per=None,
|
127
|
+
number_total=round(-amt.number * price.number, 2),
|
128
|
+
currency=self.baseCcy,
|
129
|
+
date=None,
|
130
|
+
label=None,
|
131
|
+
merge=None,
|
132
|
+
)
|
133
|
+
postings = [
|
134
|
+
data.Posting(self.investmentAccount, shares, cost, None, None, None),
|
135
|
+
data.Posting(self.cashAccount, amt, None, price, None, None),
|
136
|
+
]
|
137
|
+
|
138
|
+
return postings
|
139
|
+
|
140
|
+
def __createSell(self, amt: amount, shares: amount, book_date: date):
|
141
|
+
price = self.priceLookup.fetchPrice("USD", book_date)
|
142
|
+
cost = CostSpec(
|
143
|
+
number_per=None,
|
144
|
+
number_total=None,
|
145
|
+
currency=None,
|
146
|
+
date=None,
|
147
|
+
label=None,
|
148
|
+
merge=None,
|
149
|
+
)
|
150
|
+
postings = [
|
151
|
+
data.Posting(self.investmentAccount, shares, cost, None, None, None),
|
152
|
+
data.Posting(self.cashAccount, amt, None, price, None, None),
|
153
|
+
data.Posting(self.capGainAccount, None, None, None, None, None),
|
154
|
+
]
|
155
|
+
|
156
|
+
return postings
|
157
|
+
|
158
|
+
def __createDividend(self, amt: amount, book_date: date, incomeAccount: str):
|
159
|
+
price = self.priceLookup.fetchPrice("USD", book_date)
|
160
|
+
postings = [
|
161
|
+
data.Posting(
|
162
|
+
self.investmentAccount,
|
163
|
+
amount.Amount(D(0), self.symbol),
|
164
|
+
None,
|
165
|
+
None,
|
166
|
+
None,
|
167
|
+
None,
|
168
|
+
),
|
169
|
+
data.Posting(self.cashAccount, amt, None, price, None, None),
|
170
|
+
data.Posting(incomeAccount, None, None, None, None, None),
|
171
|
+
]
|
172
|
+
|
173
|
+
return postings
|
@@ -4,7 +4,7 @@ from datetime import timedelta
|
|
4
4
|
from io import StringIO
|
5
5
|
|
6
6
|
from beancount.core import amount, data
|
7
|
-
from beancount.core.number import D
|
7
|
+
from beancount.core.number import ZERO, D
|
8
8
|
from beancount.ingest import importer
|
9
9
|
from beancount.ingest.importers.mixins import identifier
|
10
10
|
from dateutil.parser import parse
|
@@ -13,10 +13,11 @@ from dateutil.parser import parse
|
|
13
13
|
class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
14
14
|
"""An importer for Revolut CSV files."""
|
15
15
|
|
16
|
-
def __init__(self, regexps, account, currency):
|
16
|
+
def __init__(self, regexps, account, currency, fee=None):
|
17
17
|
identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
|
18
18
|
self.account = account
|
19
19
|
self.currency = currency
|
20
|
+
self._fee = fee
|
20
21
|
|
21
22
|
def name(self):
|
22
23
|
return super().name() + self.account
|
@@ -46,6 +47,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
46
47
|
skipinitialspace=True,
|
47
48
|
)
|
48
49
|
next(reader)
|
50
|
+
is_fee_mode = self._fee is not None
|
49
51
|
for row in reader:
|
50
52
|
try:
|
51
53
|
bal = D(row["Balance"].replace("'", "").strip())
|
@@ -53,37 +55,58 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
53
55
|
amt = amount.Amount(amount_raw, row["Currency"])
|
54
56
|
balance = amount.Amount(bal, self.currency)
|
55
57
|
book_date = parse(row["Completed Date"].strip()).date()
|
58
|
+
fee_amt_raw = D(row["Fee"].replace("'", "").strip())
|
59
|
+
fee = amount.Amount(-fee_amt_raw, row["Currency"])
|
56
60
|
except Exception as e:
|
57
61
|
logging.warning(e)
|
58
62
|
continue
|
59
63
|
|
64
|
+
if is_fee_mode and fee_amt_raw == ZERO:
|
65
|
+
continue
|
66
|
+
|
67
|
+
postings = [
|
68
|
+
data.Posting(self.account, amt, None, None, None, None),
|
69
|
+
]
|
70
|
+
description = row["Description"].strip()
|
71
|
+
if is_fee_mode:
|
72
|
+
postings = [
|
73
|
+
data.Posting(self.account, fee, None, None, None, None),
|
74
|
+
data.Posting(
|
75
|
+
self._fee["account"], -fee, None, None, None, None
|
76
|
+
),
|
77
|
+
]
|
78
|
+
description = f"Fees for {description}"
|
79
|
+
|
80
|
+
assert isinstance(
|
81
|
+
description, str
|
82
|
+
), "Actual type of description is " + str(type(description))
|
83
|
+
|
60
84
|
entry = data.Transaction(
|
61
85
|
data.new_metadata(file.name, 0, {}),
|
62
86
|
book_date,
|
63
87
|
"*",
|
64
88
|
"",
|
65
|
-
|
89
|
+
description,
|
66
90
|
data.EMPTY_SET,
|
67
91
|
data.EMPTY_SET,
|
68
|
-
|
69
|
-
data.Posting(self.account, amt, None, None, None, None),
|
70
|
-
],
|
92
|
+
postings,
|
71
93
|
)
|
72
94
|
entries.append(entry)
|
73
95
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
data.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
if not is_fee_mode:
|
97
|
+
# only add balance after the last (newest) transaction
|
98
|
+
try:
|
99
|
+
book_date = book_date + timedelta(days=1)
|
100
|
+
entry = data.Balance(
|
101
|
+
data.new_metadata(file.name, 0, {}),
|
102
|
+
book_date,
|
103
|
+
self.account,
|
104
|
+
balance,
|
105
|
+
None,
|
106
|
+
None,
|
107
|
+
)
|
108
|
+
entries.append(entry)
|
109
|
+
except NameError:
|
110
|
+
pass
|
88
111
|
|
89
112
|
return entries
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: tariochbctools
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.38
|
4
4
|
Summary: Importers, plugins and price fetchers for Beancount
|
5
5
|
Home-page: https://github.com/tarioch/beancounttools/
|
6
6
|
Author: Patrick Ruckstuhl
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
19
19
|
Classifier: License :: OSI Approved :: MIT License
|
20
20
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
21
21
|
License-File: LICENSE.txt
|
22
|
-
Requires-Dist: beancount
|
22
|
+
Requires-Dist: beancount<3,>=2
|
23
23
|
Requires-Dist: bitstampclient
|
24
24
|
Requires-Dist: mt-940
|
25
25
|
Requires-Dist: pyyaml
|
@@ -30,14 +30,14 @@ Requires-Dist: opencv-python
|
|
30
30
|
Requires-Dist: blockcypher
|
31
31
|
Requires-Dist: imap-tools
|
32
32
|
Requires-Dist: undictify
|
33
|
-
Requires-Dist: importlib-metadata
|
33
|
+
Requires-Dist: importlib-metadata; python_version < "3.8"
|
34
34
|
Provides-Extra: testing
|
35
|
-
Requires-Dist: pytest
|
36
|
-
Requires-Dist: pytest-cov
|
37
|
-
Requires-Dist: pytest-mock
|
38
|
-
Requires-Dist: flake8
|
39
|
-
Requires-Dist: black
|
40
|
-
Requires-Dist: isort
|
35
|
+
Requires-Dist: pytest; extra == "testing"
|
36
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
37
|
+
Requires-Dist: pytest-mock; extra == "testing"
|
38
|
+
Requires-Dist: flake8; extra == "testing"
|
39
|
+
Requires-Dist: black; extra == "testing"
|
40
|
+
Requires-Dist: isort; extra == "testing"
|
41
41
|
|
42
42
|
.. image:: https://img.shields.io/pypi/l/tariochbctools.svg
|
43
43
|
:target: https://pypi.python.org/pypi/tariochbctools
|
@@ -11,11 +11,13 @@ tariochbctools/importers/cembrastatement/importer.py,sha256=fqfJxcU1SDiyyV3iDvfv
|
|
11
11
|
tariochbctools/importers/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
tariochbctools/importers/general/mailAdapterImporter.py,sha256=B6r9ycPY1ZiWFPNnl2-h73OgMZAMaq_QSdURW6gxR3c,1805
|
13
13
|
tariochbctools/importers/general/mt940importer.py,sha256=gLE77YhMaQdHE3nDg344eLh-hSofGKHTW5O_h6kmUdM,2102
|
14
|
-
tariochbctools/importers/general/priceLookup.py,sha256=
|
14
|
+
tariochbctools/importers/general/priceLookup.py,sha256=2nqDcSHC2BMmfSaJtgdmZKNejvTglmAj4oMqOf-SLXg,839
|
15
15
|
tariochbctools/importers/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
tariochbctools/importers/ibkr/importer.py,sha256=ie8LjnIpok8bWGGItxfin_aBSyqe5DXpGSdg3kcNID0,8616
|
17
17
|
tariochbctools/importers/neon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
tariochbctools/importers/neon/importer.py,sha256=SNG6podG1PI3gABy6eYfv1-mRnPwRjK-j5_GoNz2-Wc,2547
|
19
|
+
tariochbctools/importers/netbenefits/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
tariochbctools/importers/netbenefits/importer.py,sha256=V1C9t9LU09osxBDOz-V8DL4LxhtnuhLRj6WV_idxrTM,5816
|
19
21
|
tariochbctools/importers/nordigen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
22
|
tariochbctools/importers/nordigen/importer.py,sha256=pHRs1WyvNmb_zIUsq1O6tcUq2xMDt7AFpIbok4R1B9A,3633
|
21
23
|
tariochbctools/importers/nordigen/nordigen_config.py,sha256=jT4vgEKc35qgXqF8_bRbXLHUrkgsSDKHfhDbEw5OXD4,4903
|
@@ -25,7 +27,7 @@ tariochbctools/importers/quickfile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
|
|
25
27
|
tariochbctools/importers/quickfile/importer.py,sha256=0HRVl-OXkCq8T5Uz04gGCGlkRXZ3Mp1y8AldW8ezBHg,6544
|
26
28
|
tariochbctools/importers/raiffeisench/importer.py,sha256=L6X0vlAuL8__DmZbs_T-r-A8SH9VH05KLkL7SubFouA,995
|
27
29
|
tariochbctools/importers/revolut/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
|
-
tariochbctools/importers/revolut/importer.py,sha256=
|
30
|
+
tariochbctools/importers/revolut/importer.py,sha256=TtGzHl5v_i7IYBNFkdNosIvpBRO6_p5i_7qU2Cml2L0,3885
|
29
31
|
tariochbctools/importers/schedule/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
32
|
tariochbctools/importers/schedule/importer.py,sha256=g1q7NwGzwkj25LknOguf3b7iJ0v4JXET01a8N3cWu2U,1526
|
31
33
|
tariochbctools/importers/swisscard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -41,13 +43,13 @@ tariochbctools/importers/zak/importer.py,sha256=9HyFhsavUHLAlO1Pi3eJ7qlNmPsmKzC0
|
|
41
43
|
tariochbctools/importers/zkb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
42
44
|
tariochbctools/importers/zkb/importer.py,sha256=yrUck3DL2uyuliw0OTBGqBLYl8WBHxgTNnmaG_vA68o,1421
|
43
45
|
tariochbctools/plugins/__init__.py,sha256=DSZtoTVosmExbFmMlHVrZAxKewEILDZlGh_7-uWcc_w,26
|
44
|
-
tariochbctools/plugins/check_portfolio_sum.py,sha256=
|
45
|
-
tariochbctools/plugins/generate_base_ccy_prices.py,sha256=
|
46
|
+
tariochbctools/plugins/check_portfolio_sum.py,sha256=naJ2j6BFpQhJhT2c-gfjyIdcYe0l_ZzpdlqgwrsIv2g,2340
|
47
|
+
tariochbctools/plugins/generate_base_ccy_prices.py,sha256=Phw314qox3jpNgC5-GcnmyYcLkMkrd8xsWS-wYwdj6o,1236
|
46
48
|
tariochbctools/plugins/prices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
49
|
tariochbctools/plugins/prices/ibkr.py,sha256=9OwaZvI55bCj7H80K2iLZVsGLpuCyaoCnyuTS9e1_-c,1294
|
48
|
-
tariochbctools-0.
|
49
|
-
tariochbctools-0.
|
50
|
-
tariochbctools-0.
|
51
|
-
tariochbctools-0.
|
52
|
-
tariochbctools-0.
|
53
|
-
tariochbctools-0.
|
50
|
+
tariochbctools-0.38.dist-info/LICENSE.txt,sha256=VR2hkz3p9Sw4hSXc7S5iZTOXGeV4h-i8AO_q0zEmtkE,1074
|
51
|
+
tariochbctools-0.38.dist-info/METADATA,sha256=mJlkfUq-CmYt8LqpxguxQaUesNmH_zVQlY9wUexmLlI,2102
|
52
|
+
tariochbctools-0.38.dist-info/WHEEL,sha256=XRxW4r1PNiVhMpP4bT9oWtu3HyndxpJ84SkubFgzp_Y,109
|
53
|
+
tariochbctools-0.38.dist-info/entry_points.txt,sha256=9xrCCY1wx2zCIsQUOWZelLHDmHw9Oc-ZBAKUIY9lKTA,88
|
54
|
+
tariochbctools-0.38.dist-info/top_level.txt,sha256=CiA_NepCI6zDNsaORA55zmpuJFSnTvLESraIL13xiOQ,15
|
55
|
+
tariochbctools-0.38.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|