Skip to content

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 -f and mark fields infer-from-journal / give income a query. 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: EQUITY or a percentage split) and set a liquidation order (sellOrder:). Loans use type: 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), and contribute-remainder (sweep the rest). Cash routed to an account with an invest: 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 your globals block
  • (default) — a built-in fallback
  • (journal) — read from your -f journal
  • (inferred) — derived from a query

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