Documentation
Quickstart
Hforecast is a personal finance simulation tool that generates hledger journals from declarative YAML configuration. You describe your financial situation — accounts, income, expenses, investments, real estate, taxes — and it simulates period by period, producing a journal you can query with standard hledger commands.
The output is rough by design. It models US federal taxes (with NY state/city bundled), inflation-adjusted growth, and compound interest, but it does not model capital-loss carryforward, AMT, multi-currency portfolios, or non-US tax systems. See Known limitations for the full list. (And, given that finances and tax law are complex and I’m not an accountant, please know this tool should not be relied upon for decisions.)
Two ways to start
You always write a small YAML config. The only choice is where the numbers come from:
- From an existing hledger journal — pass it with
-fand mark fieldsinfer-from-journal/ give income aquery. Inference is opt-in per field, never automatic. - Typed in directly — put literal balances and amounts in the config; no journal needed.
You can mix the two (some fields inferred, some literal).
Path A: pull numbers from your journal
You still describe the shape of the plan (which accounts, ages), and the journal fills in the balances and recurring flows.
Config
# hforecast.yaml
age: 40
endOfPlanAge: 90
accounts:
- name: assets:bank:checking
type: savings
balance: infer-from-journal # read this account's balance from the journal
- name: assets:brokerage
balance: infer-from-journal # type + per-commodity class come from journal tags (below)
income:
- description: Salary
query: income:salary # annualize last full year's salary from the journal
toAge: 65
expenses:
- description: Discretionary
query: "expr:expenses:discretionary AND not:desc:Rent"
toAge: 65
Run
-f is required for any infer-from-journal / query field. Write the result somewhere with -o:
hforecast -f ~/.hledger.journal -o hforecast.journal
Tag your journal so it classifies correctly
Account types and asset classes are not guessed from account names — inference reads a few
hforecast- tags (or you set type:/class: in the config). Tag the account and commodity
directives:
commodity VOO ; hforecast-asset-class:equity
account assets:brokerage ; hforecast-account-type:brokerage
Loans and owned real estate use further tags (hforecast-loan-rate:, hforecast-property:, …). See
Tagging and Journal inference for the full set and how
inference resolves each value.
Check what was inferred
hforecast -f ~/.hledger.journal --explain >/dev/null
--explain prints the resolved plan with each value’s provenance ((journal), (inferred), …); see
Seeing what was resolved below. -v additionally traces every step of
the run.
Path B: type the numbers in
No journal needed — put literal values in the config.
Minimal config
# hforecast.yaml
age: 35
endOfPlanAge: 85
accounts:
- name: assets:bank:checking
type: savings
balance: $100000
income:
- type: salary
amount: 90000
fromAge: 35
toAge: 65
expenses:
- description: Living
type: general
period: monthly
account: expenses:living
amount: 4000
fromAge: 35
toAge: 65
Run it (the config defaults to hforecast.yaml, output to stdout — write it to a file with -o):
hforecast -o hforecast.journal
Everything you don’t specify takes a sensible default: inflation 2.5%, and growth by asset class —
7% equity, 4% cash/treasury, 3% real estate. Growth is a property of an asset class (set globally
in globals: or per ticker in symbols:), not of an account — there is no per-account growth rate.
Net income lands in the assets:general operating account, from which expenses are paid and
incomeRules allocate the rest.
Building out the config
Add sections incrementally and re-run to see their effect:
- Accounts — beyond a balance, an account can automatically invest its cash (
invest: EQUITYor a percentage split) and set a liquidation order (sellOrder:). Loans usetype: loan. - Income priorities (
incomeRules) — allocate the general account’s cash to your accounts each period, in list order:build-target-balance(fill to a target),contribute-continuously(a fixed amount), andcontribute-remainder(sweep the rest). Cash routed to an account with aninvest:allocation is then invested. - The funding cascade (automatic, no config) — when the general account can’t cover a cost, accounts are liquidated in a fixed order by account type to raise it.
- Real estate — model home purchases with financing, recurring costs, and eventual sales.
- Actions — one-off events on a specific date: an inheritance, a large transfer, a one-time expense, selling a property.
- Taxes — flat rates or bracket files (federal/state/city/FICA).
See Configuration for the full reference with every field and its default.
Seeing what was resolved
Pass --explain to print a scannable summary of the resolved plan to stderr before the run: the
resolved flags, globals, taxes, accounts (with opening balances valued by asset class), income,
expenses, real estate, and — with -f — what was inferred from your journal (including the
last-full-year calculation behind each derived amount). Every value is tagged with its provenance:
hforecast -c hforecast.yaml -f ~/.hledger.journal --explain >/dev/null
- (no tag) — set explicitly in your config
(global)— inherited from yourglobalsblock(default)— a built-in fallback(journal)— read from your-fjournal(inferred)— derived from aquery
--explain composes with -v/--verbose (which additionally traces every step during the run). The
panel goes to stderr, so it never mixes into a piped journal, and color is used only on a terminal
(disable it with NO_COLOR=1).
For daily granularity (-g day), the simulation runs off an existing journal’s real
transactions, and the tax engine reads gross income and deductions from posting tags — so those
postings need tags like ; earnings:income and ; deductions:pre-tax. Annual and monthly mode
create their own tagged transactions, so this only matters for daily mode. See
Tagging.
Analyzing output
The output is a standard hledger journal (written wherever you pointed -o). Query it with hledger:
# Net worth over time
hledger -f hforecast.journal bal assets liabilities --tree -Y
# Income vs. expenses by year
hledger -f hforecast.journal is -Y
# Balance sheet at retirement
hledger -f hforecast.journal bs -e 2060-01-01
# Tax burden by year
hledger -f hforecast.journal bal expenses:taxes -Y
Viewing results in today’s dollars
Hforecast amounts are nominal (future) dollars — a $2M balance in 2060 buys far less than $2M today.
To restate the results in today’s (plan-start) dollars, the journal carries a real-value price for
the default currency each period: the inflation discount of one unit back to the start date. The
commodity is named after the default currency — TODAYUSD for a $ plan, TODAYEUR for €, and so
on (TODAYCURR if the currency isn’t recognized). Query it with hledger’s --value:
# Final net worth in today's dollars
hledger -f hforecast.journal bal assets liabilities --infer-market-prices --value=end,TODAYUSD
# Net worth by year, each year already in today's dollars
hledger -f hforecast.journal bal assets liabilities --infer-market-prices --value=end,TODAYUSD --historical -Y
--infer-market-prices lets hledger read investment prices from the @/@@ cost annotations, so
equities and property convert through the default currency to TODAYUSD. Only the default currency
is discounted this way. Use --value=end for balances (a paid-off mortgage then reads zero) and
--value=then for income/expense flows — see Analyzing output.
Next steps
- Configuration — full configuration reference
- Journal inference — how inference works in detail
- Tagging — tagging reference for daily-mode tax accuracy
- Known limitations — what the tool doesn’t model