ofxtools 0.5.1__tar.gz → 0.5.2__tar.gz
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.
- {ofxtools-0.5.1/ofxtools.egg-info → ofxtools-0.5.2}/PKG-INFO +145 -24
- {ofxtools-0.5.1 → ofxtools-0.5.2}/README.rst +143 -21
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/Client.py +103 -17
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/Parser.py +2 -6
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/Types.py +35 -25
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/header.py +48 -23
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/creditcard.py +78 -7
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/ofx.py +3 -1
- ofxtools-0.5.2/ofxtools/models/signup.py +120 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/utils.py +27 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2/ofxtools.egg-info}/PKG-INFO +145 -24
- {ofxtools-0.5.1 → ofxtools-0.5.2}/setup.py +2 -2
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_bank.py +9 -7
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_creditcard.py +133 -2
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_header.py +43 -4
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_investment.py +9 -8
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_models_common.py +2 -1
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_models_ofx.py +7 -4
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_parser.py +38 -36
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_profile.py +3 -2
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_seclist.py +7 -6
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_signon.py +6 -5
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_types.py +40 -20
- ofxtools-0.5.1/ofxtools/models/signup.py +0 -44
- {ofxtools-0.5.1 → ofxtools-0.5.2}/LICENSE +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/MANIFEST.in +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/__init__.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/config/fi.cfg +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/config/ofxget_example.cfg +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/legacyclient.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/lib.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/__init__.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/bank.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/base.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/common.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/i18n.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/investment.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/profile.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/seclist.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/signon.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/models/tax1099.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/ofxalchemy/Parser.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/ofxalchemy/Types.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/ofxalchemy/__init__.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/ofxalchemy/database.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools/ofxalchemy/models.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools.egg-info/SOURCES.txt +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools.egg-info/dependency_links.txt +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools.egg-info/entry_points.txt +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools.egg-info/requires.txt +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/ofxtools.egg-info/top_level.txt +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/setup.cfg +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/__init__.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/base.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_alchemy.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_client.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_i18n.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_parser_functional.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_signup.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_tax1099.py +0 -0
- {ofxtools-0.5.1 → ofxtools-0.5.2}/tests/test_utils.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: ofxtools
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Library for working with Open Financial Exchange (OFX) formatted data used by financial institutions
|
|
5
5
|
Home-page: https://github.com/csingley/ofxtools
|
|
6
6
|
Author: Christopher Singley
|
|
7
7
|
Author-email: csingley@gmail.com
|
|
8
8
|
License: MIT
|
|
9
|
-
Download-URL: https://github.com/csingley/ofxtools/tarball/0.5.
|
|
10
|
-
Description-Content-Type: UNKNOWN
|
|
9
|
+
Download-URL: https://github.com/csingley/ofxtools/tarball/0.5.2
|
|
11
10
|
Description: ====================
|
|
12
11
|
OFX tools for Python
|
|
13
12
|
====================
|
|
@@ -27,7 +26,7 @@ Description: ====================
|
|
|
27
26
|
ElementTree structure, then converts the parsed data into normal Python
|
|
28
27
|
types (e.g. datetime.datetime, list) and exposes them through more Pythonic
|
|
29
28
|
attribute access (e.g. ``OFX.statements[0].ledgerbal``); and
|
|
30
|
-
-
|
|
29
|
+
- A (deprecated) ``ofxalchemy`` object model that persists OFX data in an
|
|
31
30
|
SQL database where they can be queried.
|
|
32
31
|
|
|
33
32
|
Installation
|
|
@@ -39,18 +38,20 @@ Description: ====================
|
|
|
39
38
|
pip install ofxtools
|
|
40
39
|
|
|
41
40
|
To install the most recent prerelease (which is where the magic happens, and
|
|
42
|
-
also the bugs), you can download the `current master`_
|
|
43
|
-
|
|
44
|
-
scheme:
|
|
41
|
+
also the bugs), you can download the `current master`_, unzip it, and install
|
|
42
|
+
via the included setup file:
|
|
45
43
|
|
|
46
44
|
::
|
|
47
45
|
|
|
48
|
-
python setup.py install
|
|
46
|
+
python setup.py install
|
|
49
47
|
|
|
50
48
|
In addition to the Python package, this will also install a script ``ofxget``
|
|
51
49
|
in ``~/.local/bin``, and its sample configuration file in
|
|
52
50
|
``~/.config/ofxtools``.
|
|
53
51
|
|
|
52
|
+
For any installation method, it's recommended to install ``ofxtools`` in a
|
|
53
|
+
`virtual environment`_.
|
|
54
|
+
|
|
54
55
|
OFX Client
|
|
55
56
|
==========
|
|
56
57
|
|
|
@@ -181,6 +182,129 @@ Description: ====================
|
|
|
181
182
|
In [23]: ET.tostring(tree)[:512] # Back to str
|
|
182
183
|
Out[23]: b'<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY><MESSAGE>Success</MESSAGE></STATUS><DTSERVER>20170421170513</DTSERVER><LANGUAGE>ENG</LANGUAGE><FI><ORG>Ameritrade Technology Group</ORG><FID>AIS</FID></FI></SONRS></SIGNONMSGSRSV1><INVSTMTMSGSRSV1><INVSTMTTRNRS><TRNUID>2a656f1c-5f86-4265-84f1-6c7f0dc8c370</TRNUID><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY><MESSAGE>pr-ctlvofx-pp03-clientsys Success</MESSAGE></STATUS><INVSTMTRS><DTASOF>20170401030609</DTASOF><CURDEF>USD</CURDEF><IN'
|
|
183
184
|
|
|
185
|
+
ofxtools and SQL
|
|
186
|
+
================
|
|
187
|
+
|
|
188
|
+
``ofxtools`` does include the ``ofxalchemy`` subpackage, but you probably don't
|
|
189
|
+
want to use it. The implementation is pretty nasty, and the SQL tables it
|
|
190
|
+
generates are cumbersome and inefficient.
|
|
191
|
+
|
|
192
|
+
Frankly, OFX is a poor data format for serious use, as is obvious to anyone
|
|
193
|
+
who's tried to work with its handling of online bill payees or securities
|
|
194
|
+
reorganizations. A better approach is to decouple your SQL data model from
|
|
195
|
+
OFX, which will also allow you to better handle other financial data formats.
|
|
196
|
+
|
|
197
|
+
It's recommended to define your own ORM models based on your needs. Import
|
|
198
|
+
OFX into Python using the main ``ofxtools.Parser.OFXTree`` parser, extract
|
|
199
|
+
the relevant data, and feed it to your model classes. Something like this:
|
|
200
|
+
|
|
201
|
+
.. code:: python
|
|
202
|
+
|
|
203
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
204
|
+
from sqlalchemy import (
|
|
205
|
+
Column, Integer, String, Text, DateTime, Numeric, ForeignKey, Enum,
|
|
206
|
+
)
|
|
207
|
+
from sqlalchemy.orm import (relationship, sessionmaker, )
|
|
208
|
+
from sqlalchemy import create_engine
|
|
209
|
+
|
|
210
|
+
from ofxtools.models.i18n import CURRENCY_CODES
|
|
211
|
+
from ofxtools.Client import (OFXClient, InvStmtRq, )
|
|
212
|
+
from ofxtools.Parser import OFXTree
|
|
213
|
+
from ofxtools.models.investment import (BUYSTOCK, SELLSTOCK)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Data model
|
|
217
|
+
Base = declarative_base()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class Account(Base):
|
|
221
|
+
id = Column(Integer, primary_key=True)
|
|
222
|
+
brokerid = Column(String, nullable=False, unique=True)
|
|
223
|
+
number = Column(String, nullable=False)
|
|
224
|
+
name = Column(String)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class Security(Base):
|
|
228
|
+
id = Column(Integer, primary_key=True)
|
|
229
|
+
name = Column(String)
|
|
230
|
+
ticker = Column(String)
|
|
231
|
+
uniqueidtype = Column(String, nullable=False)
|
|
232
|
+
uniqueid = Column(String, nullable=False)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class Transaction(Base):
|
|
236
|
+
id = Column(Integer, primary_key=True)
|
|
237
|
+
uniqueid = Column(String, nullable=False, unique=True)
|
|
238
|
+
datetime = Column(DateTime, nullable=False)
|
|
239
|
+
dtsettle = Column(DateTime)
|
|
240
|
+
type = Column(Enum('returnofcapital', 'split', 'spinoff', 'transfer',
|
|
241
|
+
'trade', 'exercise', name='transaction_type'),
|
|
242
|
+
nullable=False)
|
|
243
|
+
memo = Column(Text)
|
|
244
|
+
currency = Column(Enum(*CURRENCY_CODES, name='transaction_currency'))
|
|
245
|
+
cash = Column(Numeric)
|
|
246
|
+
account_id = Column(Integer,
|
|
247
|
+
ForeignKey('account.id', onupdate='CASCADE'),
|
|
248
|
+
nullable=False)
|
|
249
|
+
account = relationship('Account', foreign_keys=[account_id],
|
|
250
|
+
backref='transactions')
|
|
251
|
+
security_id = Column(Integer,
|
|
252
|
+
ForeignKey('security.id', onupdate='CASCADE'),
|
|
253
|
+
nullable=False)
|
|
254
|
+
security = relationship('Security', foreign_keys=[security_id],
|
|
255
|
+
backref='transactions')
|
|
256
|
+
units = Column(Numeric)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# Import
|
|
260
|
+
client = OFXClient('https://ofxs.ameritrade.com/cgi-bin/apps/OFX',
|
|
261
|
+
org='Ameritrade Technology Group', fid='AIS',
|
|
262
|
+
brokerid='ameritrade.com')
|
|
263
|
+
stmtrq = InvStmtRq(acctid='999999999')
|
|
264
|
+
response = client.request_statements(user='elmerfudd',
|
|
265
|
+
password='T0PS3CR3T', invstmtrqs=[stmtrq])
|
|
266
|
+
parser = OFXTree()
|
|
267
|
+
parser.parse(response)
|
|
268
|
+
ofx = parser.convert()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Extract
|
|
272
|
+
def make_security(secinfo):
|
|
273
|
+
return Security(
|
|
274
|
+
name=secinfo.secname, ticker=secinfo.ticker,
|
|
275
|
+
uniqueidtype=secinfo.uniqueidtype, uniqueid=secinfo.uniqueid)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
securities = {(sec.uniqueidtype, sec.uniqueid): make_security(sec)
|
|
279
|
+
for sec in ofx.securities}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
stmt = ofx.statements[0]
|
|
283
|
+
account = Account(brokerid=stmt.brokerid, number=stmt.acctid)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def make_trade(invtran):
|
|
287
|
+
security = securities[(invtran.uniqueidtype, invtran.uniqueid)]
|
|
288
|
+
return Transaction(
|
|
289
|
+
uniqueid=invtran.fitid, datetime=invtran.dttrade,
|
|
290
|
+
dtsettle=invtran.dtsettle, type='trade', memo=invtran.memo,
|
|
291
|
+
currency=invtran.currency, cash=invtran.total, account=account,
|
|
292
|
+
security=security, units=invtran.units)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
trades = [make_trade(tx) for tx in stmt.transactions
|
|
296
|
+
if isinstance(tx, (BUYSTOCK, SELLSTOCK))] # dispatch by model class
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# Persist
|
|
300
|
+
engine = create_engine('')
|
|
301
|
+
Session = sessionmaker(bind=engine)
|
|
302
|
+
session = Session()
|
|
303
|
+
session.add(account)
|
|
304
|
+
session.add_all(securities.values())
|
|
305
|
+
session.add_all(trades)
|
|
306
|
+
session.commit()
|
|
307
|
+
|
|
184
308
|
Contributing
|
|
185
309
|
============
|
|
186
310
|
|
|
@@ -191,16 +315,20 @@ Description: ====================
|
|
|
191
315
|
|
|
192
316
|
git clone https://github.com/csingley/ofxtools.git
|
|
193
317
|
|
|
194
|
-
|
|
318
|
+
Set up a `virtual environment`_, and install the package in development mode
|
|
319
|
+
so you're working on live code:
|
|
195
320
|
|
|
196
|
-
|
|
197
|
-
|
|
321
|
+
::
|
|
322
|
+
|
|
323
|
+
python setup.py develop
|
|
324
|
+
|
|
325
|
+
Install all development requirements:
|
|
198
326
|
|
|
199
327
|
::
|
|
200
328
|
|
|
201
|
-
|
|
329
|
+
pip install -r requirements-development.txt
|
|
202
330
|
|
|
203
|
-
|
|
331
|
+
Run the tests, either with ``make``:
|
|
204
332
|
|
|
205
333
|
::
|
|
206
334
|
|
|
@@ -210,18 +338,10 @@ Description: ====================
|
|
|
210
338
|
|
|
211
339
|
::
|
|
212
340
|
|
|
213
|
-
nosetests -dsv
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if you don't already have `nose`, `coverage`, etc. installed, and you don't
|
|
217
|
-
want to clutter your system libraries just for this work, you can create a
|
|
218
|
-
`virtual environment`_ and install all development requirements:
|
|
341
|
+
nosetests -dsv --with-coverage --cover-package ofxtools
|
|
219
342
|
|
|
220
|
-
|
|
343
|
+
Feel free to `create pull requests`_ on `ofxtools repository on GitHub`_.
|
|
221
344
|
|
|
222
|
-
virtualenv .venv
|
|
223
|
-
source .venv/bin/activate
|
|
224
|
-
pip install -r requirements-development.txt
|
|
225
345
|
|
|
226
346
|
Resources
|
|
227
347
|
=========
|
|
@@ -236,14 +356,15 @@ Description: ====================
|
|
|
236
356
|
.. _Requests: http://docs.python-requests.org/en/master/
|
|
237
357
|
.. _PyPI: https://pypi.python.org/pypi/ofxtools
|
|
238
358
|
.. _current master: https://github.com/csingley/ofxtools/archive/master.zip
|
|
359
|
+
.. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments
|
|
239
360
|
.. _OFX spec: http://www.ofx.net/downloads.html
|
|
240
361
|
.. _Git: https://git-scm.com/
|
|
241
362
|
.. _create pull requests: https://help.github.com/articles/using-pull-requests/
|
|
242
363
|
.. _ofxtools repository on GitHub: https://github.com/csingley/ofxtools
|
|
243
|
-
.. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/
|
|
244
364
|
.. _Quicken data mapping guide: https://fi.intuit.com/downloads/QW_DataMappingGuide.pdf
|
|
245
365
|
.. _OFX Home: http://www.ofxhome.com/
|
|
246
366
|
|
|
367
|
+
|
|
247
368
|
Keywords: ofx,Open Financial Exchange
|
|
248
369
|
Platform: UNKNOWN
|
|
249
370
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -17,7 +17,7 @@ The primary facilities provided include:
|
|
|
17
17
|
ElementTree structure, then converts the parsed data into normal Python
|
|
18
18
|
types (e.g. datetime.datetime, list) and exposes them through more Pythonic
|
|
19
19
|
attribute access (e.g. ``OFX.statements[0].ledgerbal``); and
|
|
20
|
-
-
|
|
20
|
+
- A (deprecated) ``ofxalchemy`` object model that persists OFX data in an
|
|
21
21
|
SQL database where they can be queried.
|
|
22
22
|
|
|
23
23
|
Installation
|
|
@@ -29,18 +29,20 @@ Installation
|
|
|
29
29
|
pip install ofxtools
|
|
30
30
|
|
|
31
31
|
To install the most recent prerelease (which is where the magic happens, and
|
|
32
|
-
also the bugs), you can download the `current master`_
|
|
33
|
-
|
|
34
|
-
scheme:
|
|
32
|
+
also the bugs), you can download the `current master`_, unzip it, and install
|
|
33
|
+
via the included setup file:
|
|
35
34
|
|
|
36
35
|
::
|
|
37
36
|
|
|
38
|
-
python setup.py install
|
|
37
|
+
python setup.py install
|
|
39
38
|
|
|
40
39
|
In addition to the Python package, this will also install a script ``ofxget``
|
|
41
40
|
in ``~/.local/bin``, and its sample configuration file in
|
|
42
41
|
``~/.config/ofxtools``.
|
|
43
42
|
|
|
43
|
+
For any installation method, it's recommended to install ``ofxtools`` in a
|
|
44
|
+
`virtual environment`_.
|
|
45
|
+
|
|
44
46
|
OFX Client
|
|
45
47
|
==========
|
|
46
48
|
|
|
@@ -171,6 +173,129 @@ Usage Example
|
|
|
171
173
|
In [23]: ET.tostring(tree)[:512] # Back to str
|
|
172
174
|
Out[23]: b'<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY><MESSAGE>Success</MESSAGE></STATUS><DTSERVER>20170421170513</DTSERVER><LANGUAGE>ENG</LANGUAGE><FI><ORG>Ameritrade Technology Group</ORG><FID>AIS</FID></FI></SONRS></SIGNONMSGSRSV1><INVSTMTMSGSRSV1><INVSTMTTRNRS><TRNUID>2a656f1c-5f86-4265-84f1-6c7f0dc8c370</TRNUID><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY><MESSAGE>pr-ctlvofx-pp03-clientsys Success</MESSAGE></STATUS><INVSTMTRS><DTASOF>20170401030609</DTASOF><CURDEF>USD</CURDEF><IN'
|
|
173
175
|
|
|
176
|
+
ofxtools and SQL
|
|
177
|
+
================
|
|
178
|
+
|
|
179
|
+
``ofxtools`` does include the ``ofxalchemy`` subpackage, but you probably don't
|
|
180
|
+
want to use it. The implementation is pretty nasty, and the SQL tables it
|
|
181
|
+
generates are cumbersome and inefficient.
|
|
182
|
+
|
|
183
|
+
Frankly, OFX is a poor data format for serious use, as is obvious to anyone
|
|
184
|
+
who's tried to work with its handling of online bill payees or securities
|
|
185
|
+
reorganizations. A better approach is to decouple your SQL data model from
|
|
186
|
+
OFX, which will also allow you to better handle other financial data formats.
|
|
187
|
+
|
|
188
|
+
It's recommended to define your own ORM models based on your needs. Import
|
|
189
|
+
OFX into Python using the main ``ofxtools.Parser.OFXTree`` parser, extract
|
|
190
|
+
the relevant data, and feed it to your model classes. Something like this:
|
|
191
|
+
|
|
192
|
+
.. code:: python
|
|
193
|
+
|
|
194
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
195
|
+
from sqlalchemy import (
|
|
196
|
+
Column, Integer, String, Text, DateTime, Numeric, ForeignKey, Enum,
|
|
197
|
+
)
|
|
198
|
+
from sqlalchemy.orm import (relationship, sessionmaker, )
|
|
199
|
+
from sqlalchemy import create_engine
|
|
200
|
+
|
|
201
|
+
from ofxtools.models.i18n import CURRENCY_CODES
|
|
202
|
+
from ofxtools.Client import (OFXClient, InvStmtRq, )
|
|
203
|
+
from ofxtools.Parser import OFXTree
|
|
204
|
+
from ofxtools.models.investment import (BUYSTOCK, SELLSTOCK)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Data model
|
|
208
|
+
Base = declarative_base()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class Account(Base):
|
|
212
|
+
id = Column(Integer, primary_key=True)
|
|
213
|
+
brokerid = Column(String, nullable=False, unique=True)
|
|
214
|
+
number = Column(String, nullable=False)
|
|
215
|
+
name = Column(String)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class Security(Base):
|
|
219
|
+
id = Column(Integer, primary_key=True)
|
|
220
|
+
name = Column(String)
|
|
221
|
+
ticker = Column(String)
|
|
222
|
+
uniqueidtype = Column(String, nullable=False)
|
|
223
|
+
uniqueid = Column(String, nullable=False)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Transaction(Base):
|
|
227
|
+
id = Column(Integer, primary_key=True)
|
|
228
|
+
uniqueid = Column(String, nullable=False, unique=True)
|
|
229
|
+
datetime = Column(DateTime, nullable=False)
|
|
230
|
+
dtsettle = Column(DateTime)
|
|
231
|
+
type = Column(Enum('returnofcapital', 'split', 'spinoff', 'transfer',
|
|
232
|
+
'trade', 'exercise', name='transaction_type'),
|
|
233
|
+
nullable=False)
|
|
234
|
+
memo = Column(Text)
|
|
235
|
+
currency = Column(Enum(*CURRENCY_CODES, name='transaction_currency'))
|
|
236
|
+
cash = Column(Numeric)
|
|
237
|
+
account_id = Column(Integer,
|
|
238
|
+
ForeignKey('account.id', onupdate='CASCADE'),
|
|
239
|
+
nullable=False)
|
|
240
|
+
account = relationship('Account', foreign_keys=[account_id],
|
|
241
|
+
backref='transactions')
|
|
242
|
+
security_id = Column(Integer,
|
|
243
|
+
ForeignKey('security.id', onupdate='CASCADE'),
|
|
244
|
+
nullable=False)
|
|
245
|
+
security = relationship('Security', foreign_keys=[security_id],
|
|
246
|
+
backref='transactions')
|
|
247
|
+
units = Column(Numeric)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Import
|
|
251
|
+
client = OFXClient('https://ofxs.ameritrade.com/cgi-bin/apps/OFX',
|
|
252
|
+
org='Ameritrade Technology Group', fid='AIS',
|
|
253
|
+
brokerid='ameritrade.com')
|
|
254
|
+
stmtrq = InvStmtRq(acctid='999999999')
|
|
255
|
+
response = client.request_statements(user='elmerfudd',
|
|
256
|
+
password='T0PS3CR3T', invstmtrqs=[stmtrq])
|
|
257
|
+
parser = OFXTree()
|
|
258
|
+
parser.parse(response)
|
|
259
|
+
ofx = parser.convert()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Extract
|
|
263
|
+
def make_security(secinfo):
|
|
264
|
+
return Security(
|
|
265
|
+
name=secinfo.secname, ticker=secinfo.ticker,
|
|
266
|
+
uniqueidtype=secinfo.uniqueidtype, uniqueid=secinfo.uniqueid)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
securities = {(sec.uniqueidtype, sec.uniqueid): make_security(sec)
|
|
270
|
+
for sec in ofx.securities}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
stmt = ofx.statements[0]
|
|
274
|
+
account = Account(brokerid=stmt.brokerid, number=stmt.acctid)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def make_trade(invtran):
|
|
278
|
+
security = securities[(invtran.uniqueidtype, invtran.uniqueid)]
|
|
279
|
+
return Transaction(
|
|
280
|
+
uniqueid=invtran.fitid, datetime=invtran.dttrade,
|
|
281
|
+
dtsettle=invtran.dtsettle, type='trade', memo=invtran.memo,
|
|
282
|
+
currency=invtran.currency, cash=invtran.total, account=account,
|
|
283
|
+
security=security, units=invtran.units)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
trades = [make_trade(tx) for tx in stmt.transactions
|
|
287
|
+
if isinstance(tx, (BUYSTOCK, SELLSTOCK))] # dispatch by model class
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# Persist
|
|
291
|
+
engine = create_engine('')
|
|
292
|
+
Session = sessionmaker(bind=engine)
|
|
293
|
+
session = Session()
|
|
294
|
+
session.add(account)
|
|
295
|
+
session.add_all(securities.values())
|
|
296
|
+
session.add_all(trades)
|
|
297
|
+
session.commit()
|
|
298
|
+
|
|
174
299
|
Contributing
|
|
175
300
|
============
|
|
176
301
|
|
|
@@ -181,16 +306,20 @@ clone the repository:
|
|
|
181
306
|
|
|
182
307
|
git clone https://github.com/csingley/ofxtools.git
|
|
183
308
|
|
|
184
|
-
|
|
309
|
+
Set up a `virtual environment`_, and install the package in development mode
|
|
310
|
+
so you're working on live code:
|
|
185
311
|
|
|
186
|
-
|
|
187
|
-
|
|
312
|
+
::
|
|
313
|
+
|
|
314
|
+
python setup.py develop
|
|
315
|
+
|
|
316
|
+
Install all development requirements:
|
|
188
317
|
|
|
189
318
|
::
|
|
190
319
|
|
|
191
|
-
|
|
320
|
+
pip install -r requirements-development.txt
|
|
192
321
|
|
|
193
|
-
|
|
322
|
+
Run the tests, either with ``make``:
|
|
194
323
|
|
|
195
324
|
::
|
|
196
325
|
|
|
@@ -200,18 +329,10 @@ or directly with ``nosetests``:
|
|
|
200
329
|
|
|
201
330
|
::
|
|
202
331
|
|
|
203
|
-
nosetests -dsv
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if you don't already have `nose`, `coverage`, etc. installed, and you don't
|
|
207
|
-
want to clutter your system libraries just for this work, you can create a
|
|
208
|
-
`virtual environment`_ and install all development requirements:
|
|
332
|
+
nosetests -dsv --with-coverage --cover-package ofxtools
|
|
209
333
|
|
|
210
|
-
|
|
334
|
+
Feel free to `create pull requests`_ on `ofxtools repository on GitHub`_.
|
|
211
335
|
|
|
212
|
-
virtualenv .venv
|
|
213
|
-
source .venv/bin/activate
|
|
214
|
-
pip install -r requirements-development.txt
|
|
215
336
|
|
|
216
337
|
Resources
|
|
217
338
|
=========
|
|
@@ -226,10 +347,11 @@ Resources
|
|
|
226
347
|
.. _Requests: http://docs.python-requests.org/en/master/
|
|
227
348
|
.. _PyPI: https://pypi.python.org/pypi/ofxtools
|
|
228
349
|
.. _current master: https://github.com/csingley/ofxtools/archive/master.zip
|
|
350
|
+
.. _virtual environment: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments
|
|
229
351
|
.. _OFX spec: http://www.ofx.net/downloads.html
|
|
230
352
|
.. _Git: https://git-scm.com/
|
|
231
353
|
.. _create pull requests: https://help.github.com/articles/using-pull-requests/
|
|
232
354
|
.. _ofxtools repository on GitHub: https://github.com/csingley/ofxtools
|
|
233
|
-
.. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/
|
|
234
355
|
.. _Quicken data mapping guide: https://fi.intuit.com/downloads/QW_DataMappingGuide.pdf
|
|
235
356
|
.. _OFX Home: http://www.ofxhome.com/
|
|
357
|
+
|