Last active
April 10, 2017 14:15
-
-
Save tdenewiler/a2d45762c480721f9c2528eca9ff7e5c to your computer and use it in GitHub Desktop.
Useful calculations from banking account information. Calculates and plots monthly savings from a combined set of account data in CSV format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: utf-8 | |
""" | |
Use at your own risk. No warranty implied. There might be errors in the code. | |
Useful calculations from banking account information. | |
Assumes data has been downloaded in CSV format from Wells Fargo website. | |
CSV files should all be placed in data/ directory with arbitrary names. | |
This is intended to provide savings by month for multiple accounts. The Money | |
Map from Wells Fargo is supposed to do this but does not accurately account | |
for times when money is moved from checking to savings accounts. | |
:copyright: 2016, Thomas Denewiler | |
:license: BSD 3-Clause | |
""" | |
from __future__ import division | |
from datetime import datetime, timedelta | |
from itertools import groupby | |
from os import listdir | |
from os.path import isfile, join | |
import csv | |
import matplotlib | |
matplotlib.use('Qt4Agg') # For use over SSH. | |
import matplotlib.pyplot as plt | |
class BankAccounts(object): | |
""" | |
Bank account calculations. | |
""" | |
def __init__(self): | |
transfer = 'ONLINE TRANSFER' | |
data_dir = 'data' | |
max_weeks = 600 | |
files = [f for f in listdir(data_dir) if isfile(join(data_dir, f))] | |
data = [] | |
income = 0 | |
for f in files: | |
data, income = self.get_transactions(join(data_dir, f), data, | |
income, transfer) | |
trxs = self.find_monthly_transactions(data, max_weeks) | |
monthly, num_months = self.filter_transactions(trxs) | |
if num_months > 0: | |
print 'Average monthly income for past {0} months: ${1:.2f}'. \ | |
format(num_months, income / num_months) | |
self.plot_savings(monthly) | |
@classmethod | |
def find_monthly_transactions(cls, data, max_weeks): | |
""" | |
Find all transactions for each month in combined account data. | |
Ignore transactions that occur as transfers between accounts or that | |
are greater than a specified time ago. | |
""" | |
trxs = [] | |
now = datetime.now() | |
for trx in data: | |
try: | |
date_object = datetime.strptime(trx[0], '%m/%d/%Y') | |
except IndexError: | |
continue | |
if date_object < now - timedelta(weeks=max_weeks): | |
continue | |
date = date_object.strftime('%Y-%m-%d') | |
if float(trx[1]) > 0: | |
entry = {'date': date, 'in': trx[1], 'out': 0} | |
else: | |
entry = {'date': date, 'in': 0, 'out': trx[1]} | |
trxs.append(entry) | |
return trxs | |
@classmethod | |
def filter_transactions(cls, data): | |
""" | |
Filter transactions. | |
Very useful sorting tips from: | |
http://stackoverflow.com/questions/16176469/ | |
how-do-i-group-this-list-of-dicts-by-the-same-month | |
""" | |
monthly = [] | |
total_savings = 0.0 | |
num_months = 0 | |
data.sort(key=lambda x: x['date'][:7]) | |
print '{0:10} {1:5} {2:10} {3:10} {4:10}'.format('Month', 'Year', | |
'Income', 'Spending', | |
'Savings') | |
for k, val in groupby(data, key=lambda x: x['date'][:7]): | |
money_in = 0.0 | |
money_out = 0.0 | |
for value in val: | |
money_in += float(value['in']) | |
money_out += float(value['out']) | |
date = datetime.strptime(k, '%Y-%m') | |
savings = money_in + money_out | |
total_savings += savings | |
num_months += 1 | |
entry = {'date': date, 'savings': savings} | |
monthly.append(entry) | |
print '{0:10} {1:5} {2:10} {3:10} {4:10}'. \ | |
format(date.strftime('%B'), date.strftime('%Y'), | |
money_in, money_out, savings) | |
if num_months > 0: | |
print 'Average monthly savings for past {0} months: ${1:.2f}'. \ | |
format(num_months, total_savings / num_months) | |
return monthly, num_months | |
@classmethod | |
def plot_savings(cls, data): | |
""" | |
Plot monthly savings. | |
Could try to make plots a bit nicer with: | |
http://composition.al/blog/2015/11/29/ | |
a-better-way-to-add-labels-to-bar-charts-with-matplotlib/ | |
""" | |
x = [] | |
y = [] | |
labels = [] | |
colors = [] | |
for entry in data: | |
x.append(entry['date']) | |
y.append(entry['savings']) | |
labels.append(entry['savings']) | |
if entry['savings'] < 0: | |
colors.append('r') | |
else: | |
colors.append('b') | |
axis = plt.subplot(111) | |
axis.bar(x, y, align='center', color=colors, width=30, alpha=0.5) | |
axis.xaxis_date() | |
axis.autoscale_view() | |
plt.setp(plt.gca().get_xticklabels(), rotation=45) | |
plt.xlabel('Month') | |
plt.ylabel('Savings ($)') | |
plt.title('Savings by Month') | |
rects = axis.patches | |
for rect, label in zip(rects, labels): | |
height = rect.get_height() | |
offset = 10 | |
if float(label) < 0: | |
height *= -1 | |
offset *= -1 | |
axis.text(rect.get_x() + rect.get_width() / 2, height + offset, | |
label, ha='center', va='bottom') | |
plt.show() | |
@classmethod | |
def get_transactions(cls, filename, data, income, transfer): | |
""" | |
Extract account information from CSV file. | |
""" | |
with open(filename, 'rb') as fcsv: | |
reader = csv.reader(fcsv) | |
for line in reader: | |
if transfer in line[4]: | |
continue | |
data.append(line) | |
if 'checking' in filename and float(line[1]) > 0: | |
income += float(line[1]) | |
#print 'Found income of ${} with {}'.format(line[1], line[4]) | |
return data, income | |
if __name__ == '__main__': | |
BankAccounts() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment