MoneyGo reports are written in Lua, as implemented by github.com/yuin/gopher-lua, with hooks added to query the necessary MoneyGo state to generate the report.
Before diving into the details, here's an example report that calculates the difference between income and expenses for each month in the current year:
function generate()
year = date.now().year
accounts = get_accounts()
t = tabulation.new(12)
t:title(year .. " Monthly Cash Flow")
series = t:series("Income minus expenses")
for month=1,12 do
begin_date = date.new(year, month, 1)
end_date = date.new(year, month+1, 1)
t:label(month, tostring(begin_date))
cash_flow = 0
for id, acct in pairs(accounts) do
if acct.type == account.Expense or acct.type == account.Income then
balance = acct:balance(begin_date, end_date)
--[[
Note: We should convert balance.amount to the user's default currency
before proceeding here
--]]
cash_flow = cash_flow - balance.amount
end
end
series:value(month, cash_flow)
end
return t
end
More examples can be found in the reports/ directory in the MoneyGo source tree.
The lua code behind a report must contain a generate()
function which takes
no arguments. This function is called when generating a report, and must return
a tabulation
object, created by calling t = tabulation:new(n)
, where n
is
the integer number of data values in each of the series of this tabulation (all
series in the same tabulation must have the same number of values).
Assuming your tabulation object is t
, you should then call t.label(m, "some_string")
for each value of m
in [1, n]
to set the label for the 'm'th
data element in each series. You do not need to do this before creating series,
and can do it lazily as you generate data, if needed. Titles, subtitles, and the
y-axis label (the units) can be set as follows on a tabulation object named t
:
t.title("The title of my report")
t.subtitle("The subtitle of my report")
t.units("USD ($)")
To create a new top-level series, call s = t:series("series name")
where t
is a tabulation object. Just as for labels for tabulation objects, you set
s:value(m, number)
for each value of m
in [1, n]
(where n
is the same
integer used to create the tabulation object to which this series belongs).
Nested series can be created by calling s2 = s:series("nested series name")
,
where s
is any already-created series object. Nested series allow for drilling
down to explore the information in more detail. In the web interface, they are
clickable and cause the charts to display the selected series as the new top
level.
It is assumed that nested series' reported values are not already included in their parents' values. When being displayed, all the children series values are added into the parent's. This means that a series may have no values of its own, but still show up in a chart because it is reporting the sum of its children's values.
Collecting/tabulating the data is up to you (chasing this flexibility was the impetus behind Lua reports in the first place).
You can get a table of account objects for each of your accounts (indexed by
account ID) by calling the global function, get_accounts()
. Each of these
accounts has several fields describing it:
a.Name
returns the account's namea.Description
returns the account's descriptiona.Type
returns the account's type, as an integer constant. The account type constants are available on the top-level 'account' objectaccount.Bank
account.Cash
account.Asset
account.Liability
account.Investment
account.Income
account.Expense
account.Trading
account.Equity
account.Receivable
account.Payable
a.TypeName
returns a string representation of the account's typea.Security
returns a security object representing the currency, stock, etc. of this account.a.Parent
returns this account's parent account, or nil if the account has no parent.a:Balance
is a function which returns the account balance in the account's security, optionally over a date range. If no arguments are provided, the total account balance as of the end of time is returned. If one date is provided, the balance as of that date is returned. If two dates are provided, the difference in balances between the first and second dates is returned.
You can get a table containing all the securities/currencies registered to an
account using the global function get_securities()
. Each of these securities
has several fields describing it:
s.SecurityId
s.Name
s.Description
s.Symbol
returns the symbol traditionally associated with that security (i.e. '$' for USD, or BRK.B for class B shares of Berkshire Hathaway)s.Precision
returns the number of digits of precision past the decimal point that this currency allows for (i.e. 2 for USD)s.Type
returns an int constant which represents what type of security it is (i.e. stock or currency)
Securities support a ClosestPrice function that allows you to fetch the price of the current security in a given currency that is closest to the supplied date. For example, to print the price in the user's default currency for each security in the user's account:
default_currency = get_default_currency()
for id, security in pairs(get_securities()) do
price = security.price(default_currency, date.now())
if price ~= nil then
print(tostring(security) .. ": " security.Symbol .. " " .. price.Value)
else
print("Failed to fetch price for " .. tostring(security))
end
end
You can also query for an account's default currency using the global
get_default_currency()
function.
Price objects can be queried from Security objects. Price objects contain the following fields:
p.PriceId
p.Security
returns the security object the price is forp.Currency
returns the currency that the price is inp.Value
returns the price of one unit of 'security' in 'currency', as a float
In order to make it easier to do operations like finding account balances for a month at a time, MoneyGo implements it's own date type (eschewing the traditional Lua implementation). You must use the MoneyGo date types when passing them to any MoneyGo lua functions. To create a date object, you can use one of two methods:
date.now()
returns the current datedate.new(2017, 7, 5)
returns a date object representing July 5th, 2017. Note that this method also accepts a single argument of a table with the 'year', 'month', and 'day' fields set to int's.
In addition to supporting conversion to a string, addition, subtraction, and
comparison operators, dates support returning their constituent parts using
d.Year
, d.Month
, and d.Day
.