Skip to content

Instantly share code, notes, and snippets.

@tdenewiler
Last active April 10, 2017 14:15
Show Gist options
  • Save tdenewiler/a2d45762c480721f9c2528eca9ff7e5c to your computer and use it in GitHub Desktop.
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.
#!/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