moneygo/docs/lua_reports.md

7.0 KiB

Lua Reports

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.

An Example: Monthly Cash Flow 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.

Basic Operation

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).

Titles and Labels

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 ($)")

Data Series

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.

Gathering Data

Collecting/tabulating the data is up to you (chasing this flexibility was the impetus behind Lua reports in the first place).

Accounts and Balances

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 name
  • a.Description returns the account's description
  • a.Type returns the account's type, as an integer constant. The account type constants are available on the top-level 'account' object
    • account.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 type
  • a.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.

Securities

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.

Prices

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 for
  • p.Currency returns the currency that the price is in
  • p.Value returns the price of one unit of 'security' in 'currency', as a float

Dates

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:

  1. date.now() returns the current date
  2. date.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.