Skip to main content

Library for working with Open Financial Exchange (OFX) formatted data used by financial institutions

Project description

ofxtools is a Python library for working with Open Financial Exchange (OFX) data - both OFXv1 (SGML) and OFXv2 (pure XML) - which is the standard format for downloading financial information from banks and stockbrokers.

ofxtools is compatible with Python version 2.7+ and 3.1+. It depends on Requests .

The primary facilities provided include:

  • The OFXClient class; which dowloads OFX statements from the Internet;

  • The OFXTree class; which parses OFX data into a standard Python ElementTree structure, then converts the parsed data into normal Python types (e.g. datetime.datetime, list) and exposes them through more Pythonic attribute access (e.g. OFX.statements[0].ledgerbal); and

  • An (optional) ofxalchemy object model that persists OFX data in an SQL database where they can be queried.

Installation

ofxtools is released on PyPI, so it can be installed simply via:

pip install ofxtools

To install the most recent prerelease (which is where the magic happens, and also the bugs), you can download the current master and unzip it. Since dependencies are minimal, it’s recommended to use the Python user installation scheme:

python setup.py install --user

In addition to the Python package, this will also install a script ofxget in ~/.local/bin, and its sample configuration file in ~/.config/ofxtools.

OFX Client

Basic usage of the CLI script:

  • Copy ~/.config/ofxtools/ofxget_example.cfg to ~/.config/ofxtools/ofxget.cfg and edit:

  • Add a section for your financial institution, including URL, account information, login, etc. See comments within the example file.

  • Execute ofxget with appropriate arguments, for example:

ofxget amex stmt -s 20140101 -e 20140630 > foobar.ofx

See the --help for explanation of the script options.

To use within another program, initialize an OFXClient instance with the relevant connection parameters, then pass *StmtRq namedtuples along with a username and password to its request_statements method. See below for an example.

OFX Parser

The OFXTree parser subclasses xml.etree.ElementTree, and is used similarly – get an OFXTree instance, and pass a file-like object (or a reference to one) to its parse method. Thereafter, calling its convert method returns a tree of nested ofxtools.models.Aggregate containers that preserve the original OFX structure. Following the OFX spec , you can get a node in the parse tree with Python dotted attribute access, using standard slice notation for lists. E.g.:

ofx.invstmtmsgsrsv1[0].invstmttrnrs.invstmtrs.invtranlist[-1].invsell.invtran.dttrade

Data-bearing leaf nodes (such as DTTRADE above) are subclasses of ofxtools.Types.Element, which validate the OFX character data and convert it to standard Python types (datetime.datetime in this case, decimal.Decimal, bool, etc.)

For quick access, Aggregates also provide shortcuts via read-only properties. ofx.statements yields all {STMTRS, CCSTMTRS, INVSTMTRS} found in the response. ofx.statements[0].transactions goes to the relevant *TRANLIST ofx.statements[0].account goes to the relevant *ACCTFROM Use ofx.statements[0].balance for bank statement LEDGERBAL, or ofx.statements[0].balances for investment statement INVBAL.

Investment transactions provide lookthrough access to attributes of their SubAggregates, so you can use STOCKBUY.uniqueid or INCOME.dttrade.

For handling multicurrency transactions per OFX section 5.2, Aggregates that can contain ORIGCURRENCY have an additional curtype attribute which yields 'CURRENCY' if the money amounts have not been converted to the home currency, or yields 'ORIGCURRENCY' if they have been converted.

YIELD elements are renamed yld to avoid name collision with the Python built-in.

Proprietary OFX tags (e.g. <INTU.BROKERID>) are stripped and dropped.

Usage Example

In [1]: from ofxtools.Client import OFXClient, InvStmtRq

In [2]: client = OFXClient('https://ofxs.ameritrade.com/cgi-bin/apps/OFX',
   ...:      org='Ameritrade Technology Group', fid='AIS', brokerid='ameritrade.com')

In [3]: stmtrq = InvStmtRq(acctid='999999999')

In [4]: response = client.request_statements(user='elmerfudd', password='T0PS3CR3T',
   ...:      invstmtrqs=[stmtrq])

In [5]: response.read()[:512]  # It's a StringIO
Out[5]: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r\n<?OFX OFXHEADER="200" VERSION="200" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>\r\n<OFX>\r\n<SIGNONMSGSRSV1>\r\n<SONRS>\r\n<STATUS>\r\n<CODE>0</CODE>\r\n<SEVERITY>INFO</SEVERITY>\r\n<MESSAGE>Success</MESSAGE>\r\n</STATUS>\r\n<DTSERVER>20170421120513</DTSERVER>\r\n<LANGUAGE>ENG</LANGUAGE>\r\n<FI>\r\n<ORG>Ameritrade Technology Group</ORG>\r\n<FID>AIS</FID>\r\n</FI>\r\n</SONRS>\r\n</SIGNONMSGSRSV1>\r\n<INVSTMTMSGSRSV1>\r\n<INVSTMTTRNRS>\r\n<TRNUID>2a656f1c-5f86-4265-84f1-6c7f0dc8c37'

In [6]: response.seek(0)  # Rewind so parser read()s from beginning of file
Out[6]: 0

In [7]: from ofxtools.Parser import OFXTree

In [8]: parser = OFXTree()

In [9]: parser.parse(response)  # parser.parse('/path/to/file.ofx') works too

In [10]: parser.find('.//STATUS')[:]  # It's an ElementTree subclass
Out[10]:
[<Element 'CODE' at 0x7f27dd4a2048>,
 <Element 'SEVERITY' at 0x7f27dd4a2ea8>,
 <Element 'MESSAGE' at 0x7f27dd4a2318>]

In [11]: ofx = parser.convert()

In [12]: ofx.statements  # It's a tree of ofxtools.models.Aggregate
Out[12]: [<INVSTMTRS dtasof='2017-03-31 22:06:09' curdef='USD'>]

In [13]: ofx.statements[0].transactions
Out[13]: <INVTRANLIST dtstart=2015-04-21 12:05:13 dtend=2017-04-20 00:00:00 len=47>

In [14]: t = ofx.statements[0].transactions[9]

In [15]: t
Out[15]: <BUYSTOCK buytype='BUY'>

In [16]: t.dttrade
Out[16]: datetime.datetime(2016, 9, 7, 13, 10, 4)

In [17]: t.uniqueid
Out[17]: '233242106'

In [18]: t.units
Out[18]: Decimal('18000.00')

In [19]: t.total
Out[19]: Decimal('-4509.99')

In [20]: tree = ofx.to_etree()  # ElementTree(ofx.to_etree()) is a little nicer

In [21]: tree.find('.//STATUS')[:]  # Back to ElementTree
Out[21]:
[<Element 'CODE' at 0x7f27dc9870e8>,
 <Element 'SEVERITY' at 0x7f27dc987a98>,
 <Element 'MESSAGE' at 0x7f27dc987098>]

In [22]: import xml.etree.ElementTree as ET

In [23]: ET.tostring(tree)[:512]  # Back to str
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'

Contributing

If you want to contribute to this project, it’s recommended to use Git to clone the repository:

git clone https://github.com/csingley/ofxtools.git

Feel free to create pull requests on ofxtools repository on GitHub.

Again, the minimal dependencies make it simple to install with the Python user installation scheme:

python setup.py develop --user

However, you’ll want to run the tests, either with make:

make test

or directly with nosetests:

nosetests -dsv --with-yanc --with-coverage --cover-package ofxtools

if you don’t already have nose, coverage, etc. installed, and you don’t want to clutter your system libraries just for this work, you can create a virtual environment and install all development requirements:

virtualenv .venv
source .venv/bin/activate
pip install -r requirements-development.txt

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ofxtools-0.5.tar.gz (82.5 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page