return sink
 
 
+class Account:
+    """Combine name, position in tree of own, and wealth of self + children."""
+
+    def __init__(self, parent: Optional['Account'], basename: str) -> None:
+        self.local_wealth = Wealth()
+        self.basename = basename
+        self.children: list[Account] = []
+        self.parent = parent
+        if self.parent:
+            self.parent.children += [self]
+
+    @property
+    def wealth(self) -> Wealth:
+        """Total of .local_wealth with that of .children."""
+        total = Wealth()
+        total += self.local_wealth
+        for child in self.children:
+            total += child.wealth
+        return total
+
+
 class DatLine:
     """Line of .dat file parsed into comments and machine-readable data."""
 
             dat_line.booking_line = TransferLine(self, dat_line.code)
             self._transfer_lines += [dat_line.booking_line]
         changes = Wealth()
+        sink_account = None
         self.account_changes: dict[str, Wealth] = {}
-        self.sink_account = None
         for transfer_line in [tl for tl in self._transfer_lines
                               if not tl.errors]:
             if transfer_line.account not in self.account_changes:
                 self.account_changes[transfer_line.account] = Wealth()
             if transfer_line.amount is None:
-                if self.sink_account:
+                if sink_account:
                     transfer_line.errors += ['too many sinks']
-                self.sink_account = transfer_line.account
+                sink_account = transfer_line.account
                 continue
             change = Wealth({transfer_line.currency: transfer_line.amount})
             self.account_changes[transfer_line.account] += change
             changes += change
-        if self.sink_account:
-            self.account_changes[self.sink_account] += changes.as_sink
+        if sink_account:
+            self.account_changes[sink_account] += changes.as_sink
         elif not changes.sink_empty:
             self._transfer_lines[-1].errors += ['needed sink missing']
         self.id_ = id_
 
     def do_GET(self) -> None:
         # pylint: disable=invalid-name,missing-function-docstring
-        if self.pagename == 'booking':
+        if self.pagename == 'balance':
+            self.send_rendered(Path('balance.tmpl'),
+                               {'roots': self.server.balance_roots})
+        elif self.pagename == 'booking':
             self.send_rendered(
                     Path('booking.tmpl'),
                     {'dat_lines':
         """Return only those .data_lines with .code or .comment."""
         return [dl for dl in self.dat_lines if not dl.is_empty]
 
+    @property
+    def balance_roots(self) -> list[Account]:
+        """Return tree of calculated Accounts over all .bookings."""
+        account_names = set()
+        for booking in self.bookings:
+            for account_name in booking.account_changes:
+                account_names.add(account_name)
+        full_names_to_accounts: dict[str, Account] = {}
+        for full_name in sorted(list(account_names)):
+            step_names = full_name.split(':')
+            path = ''
+            for step_name in step_names:
+                parent_name = path[:]
+                path = ':'.join([path, step_name]) if path else step_name
+                if path not in full_names_to_accounts:
+                    full_names_to_accounts[path] = Account(
+                        full_names_to_accounts[parent_name] if parent_name
+                        else None,
+                        step_name)
+        for booking in self.bookings:
+            for account_name in booking.account_changes:
+                full_names_to_accounts[account_name].local_wealth +=\
+                        booking.account_changes[account_name]
+        return [ac for ac in full_names_to_accounts.values() if not ac.parent]
+
 
 if __name__ == "__main__":
     if not LEDGER_DAT: