# Program to display past and current net worth. # Author: Ric Werme, Oct 2005 # Run with: moneydance -invoke_and_quit moneydance:fmodule:jpython:runfile?=networth.py # Configuration variables, ** set these before you run this program. ** # Datestr is a list of dates of when you want the net worth computed. # Edit it to fit your needs. The dates must be sorted from earliest # to latest. datestr = ['2005-1-1', '2005-4-1', '2005-7-1', '2005-10-1', '2005-11-1'] # This program doesn't have ready access to displaying data on screen, so # we create text and HTML files. textfile = 'networth.txt' htmlfile = 'networth.html' # Set verbose to 1 to enable some diagnostic printing. verbose = 0 # [End of configuration variables.] import time # As the program scans each account computing net worth at the requested # dates, it creates an account record for each. Later it sorts and # scans the records times to determine which accounts to print # and to print the data. Fields are: # .id: ID # for the account. Not really needed. # .type: The accountType, used to summarize each type. Not really needed. # .name: The name for the account or subaccount. _Not_ a multilevel name! # .subaccount: True (1) if this has a parent. # .printflag: True if we need to print (subaccount with a non-zero value # or parent of such a subaccount). # .values: List of values (in local currency) matching dates of interest. # Records are kept in the accountRecs dictionary which is indexed by the # full account name. class accountRec: def __init__(self, id, name, type, subaccount): self.id = id self.name = name self.type = type self.subaccount = subaccount self.printflag = 0 self.values = [] # Routine to scan accountRecs and decide what to print. That is any # subaccount with a non-zero value for some reporting data and its # parent, plus any top level account with a non zero balance. def checkPrint(accountRecs): keys = accountRecs.keys() keys.sort() for i in keys: rec = accountRecs[i] for value in rec.values: if value: rec.printflag = 1 break if not rec.subaccount: # Parent acct precedes subaccts parent = rec else: if rec.printflag: parent.printflag = 1 # Call this to make a text file of transactions. The HTML file looks # better. def maketext(file, datestr, accountRecs, typeWorth, netWorth): fo = open(file, 'w') pass # First, we print the worth of each account for each date: fo.write('Type Account ') for date in datestr: fo.write(' %11s' % date) fo.write('\n') keys = accountRecs.keys() keys.sort() for key in keys: rec = accountRecs[key] if not rec.printflag: continue if rec.subaccount: tmp = '+' else: tmp = '' fo.write('%-11s %-40s' % (rec.type, tmp + rec.name)) for val in rec.values: fo.write(' %11.2f' % (val / 100.0)) fo.write('\n') pass # Next we print the worth of each account type for each date: fo.write('\nType ') for date in datestr: fo.write(' %11s' % date) fo.write('\n') keys = typeWorth.keys() keys.sort() for key in keys: data = typeWorth[key] fo.write('%-11s' % key) for val in data: fo.write(' %11.2f' % (val / 100.0)) fo.write('\n') pass # Finally we print the net worth for each date: fo.write('-----------') for val in netWorth: fo.write(' -----------') fo.write('\nTotal ') for val in netWorth: fo.write(' %11.2f' % (val / 100.0)) fo.write('\n') fo.close() # HTML support routines for makehtml(): # Routine to display one cell of simple data. def easy_cell(fo, val): if val == None: val = ' ' fo.write('
\n
Type | \nAccount | \n''') for date in datestr: fo.write('%s | \n' % date) fo.write('
---|---|---|
\n
Type | \n') for date in datestr: fo.write('%s | \n' % date) fo.write('
---|---|
Page generated on %s.\n\n\n\n''' % date) fo.close() # Return a list of account balances for an account for a list of requested # dates. We have to scan all transactions for the account which we get as an # unsorted list. We derive a list of changes in the account from one date to # the next to the current balance. Each transacation is matched with the # appropriate time period and its value is accumulated as a change that will # let use quickly compute the balances just before we return the list of # balances. # Key local variables: # lv: Local Verbose value, set to 1 or 2 for debugging. # deltas: Amounts account changed between dates of interest. # balances: Dollar balances of account on dates of interest. # indexes: List of indexes to access latest date of interest to earliest. def acctValues(rootAccount, account, dates): lv = 0 txnEnum = rootAccount.getTransactionSet().getTransactionsForAccount(account) txns = txnEnum.getAllTxns() deltas = [] balances = [] for i in dates: deltas.append(0L) balances.append(0L) indexes = range(len(dates)) indexes.reverse() for txn in txns: date = txn.getDate() if date < dates[0]: # Earlier than earliest date of interest? if lv > 1: print 'skipping', date continue if lv > 1: print repr(txn) for dateIndex in indexes: if date > dates[dateIndex]: break deltas[dateIndex] = deltas[dateIndex] + txn.getValue() if lv > 1: print 'txn counted in', date, dateIndex, deltas[dateIndex] balances[indexes[0]] = account.getBalance() - deltas[indexes[0]] for dateIndex in indexes[1:]: balances[dateIndex] = balances[dateIndex + 1] - deltas[dateIndex] if lv: print 'deltas:', deltas, 'balances', balances for dateIndex in indexes: if account.getAccountType() == account.ACCOUNT_TYPE_SECURITY: currencytype = account.getCurrencyType() rate = currencytype.getRawRateByDate(dates[dateIndex]) balances[dateIndex] = long(round(balances[dateIndex] / rate)) if lv > 1: print 'Value for', dates[dateIndex], 'is', balances[dateIndex] return balances # Main routine. # After some initialization, we scan each account that has a worth # associated with it (any but deleted, expense, or income), call # acctValues to get the balances we want, and accumulate the data in the # account type and overall net worth lists. Then, since I don't know if # there's a way to call swing, generate new files for viewing and # printing. def doit(datestr, dates): rootAccount = moneydance.getRootAccount() if verbose: print 'Looking through %d accounts\n' % rootAccount.getHighestAccountNum() typeWorth = {} aTNames = {} aTNames[rootAccount.ACCOUNT_TYPE_ASSET] = 'Asset' aTNames[rootAccount.ACCOUNT_TYPE_BANK] = 'Bank' aTNames[rootAccount.ACCOUNT_TYPE_CREDIT_CARD] = 'Credit card' aTNames[rootAccount.ACCOUNT_TYPE_EXPENSE] = 'Expense' aTNames[rootAccount.ACCOUNT_TYPE_INCOME] = 'Income' aTNames[rootAccount.ACCOUNT_TYPE_INVESTMENT] = 'Investment' aTNames[rootAccount.ACCOUNT_TYPE_LIABILITY] = 'Liability' aTNames[rootAccount.ACCOUNT_TYPE_LOAN] = 'Loan' aTNames[rootAccount.ACCOUNT_TYPE_ROOT] = 'Root' aTNames[rootAccount.ACCOUNT_TYPE_SECURITY] = 'Security' accountRecs = {} zeroes = [] for i in dates: zeroes.append(0L) netWorth = zeroes[:] # Make a copy, there will be several more for id in range(1, rootAccount.getHighestAccountNum() + 1): account = rootAccount.getAccountById(id) if account == None: continue balance = account.getBalance() accountType = account.getAccountType() aTName = aTNames[accountType] if accountType == account.ACCOUNT_TYPE_EXPENSE or accountType == account.ACCOUNT_TYPE_INCOME: continue try: tmp = typeWorth[aTName] except: typeWorth[aTName] = zeroes[:] name = account.getAccountName() fullName = account.getFullAccountName() subaccount = account.getDepth() > 1 accountRecs[fullName] = accountRec(id, name, aTName, subaccount) values = acctValues(rootAccount, account, dates) accountRecs[fullName].values = values[:] for dateIndex in range(len(dates)): netWorth[dateIndex] = netWorth[dateIndex] + values[dateIndex] typeWorth[aTName][dateIndex] = typeWorth[aTName][dateIndex] + values[dateIndex] if verbose: print '%3d: %-40s %11.2f %d' % (id, name, values[-1] / 100.0, aTName) pass # if len(accountRecs) == 10: # Early termination? pass # break checkPrint(accountRecs) maketext(textfile, datestr, accountRecs, typeWorth, netWorth) makehtml(htmlfile, datestr, accountRecs, typeWorth, netWorth) return dates = [] td = time.localtime(time.time()) for date in datestr: tmp = date.split('-') td = (int(tmp[0]), int(tmp[1]), int(tmp[2]), 12, 0, 0, 0, 0, 0) dates.append(long(time.mktime(td)) * 1000L) # print 'datestr', datestr # print 'dates', dates doit(datestr, dates)