hledger icon indicating copy to clipboard operation
hledger copied to clipboard

Forecasting should start from the date of the last transaction, by default, not the date today

Open olimorris opened this issue 2 years ago • 5 comments

I have two journal files:

  • example_transactions.journal that contains my "actuals"
  • example_forecast.journal that contains my future forecasted transactions

If I run the following command:

hledger -f $FINANCES/example_transactions.journal -f $FINANCES/example_forecast.journal --forecast --auto bal "^(ass|liab)" --tree -M --debug=2

I see the following output:

journalEndDate: Nothing
forecastspan: DateSpan 2023-06-07..2023-12-03
Balance changes in 2023-05-01..2023-12-31:

                    ||       May      Jun      Jul      Aug        Sep      Oct      Nov      Dec
====================++============================================================================
 Assets:Wells Fargo || $1,425.00  $-58.00  $542.00  $542.00  $-2458.00  $513.00  $542.00  $600.00
--------------------++----------------------------------------------------------------------------
                    || $1,425.00  $-58.00  $542.00  $542.00  $-2458.00  $513.00  $542.00  $600.00

Clearly the forecasting is starting from today (2023-06-07) and not at the date of the last transaction (2023-05-15).

Note: The $58 in June is because Hledger is multiplying the Expenses:Dating transaction by two in that period

example_transactions.journal

2023-05-01  Opening Balance
    Assets:Wells Fargo      $1,000.00
    Equity:Opening Balance

2023-05-01  Salary
    Income:Salary
    Assets:Wells Fargo      $3,000.00

2023-05-01  groceries
    Expenses:Groceries
    Assets:Wells Fargo      -$200.00

2023-05-01  mortgage
    Expenses:House:Mortgage
    Assets:Wells Fargo      -$1,800.00

2023-05-15  energy
    Expenses:Energy
    Assets:Wells Fargo      -$500.00

2023-05-15  fuel
    Expenses:Fuel
    Assets:Wells Fargo      -$75.00

example_forecast.journal

~ monthly from 2023-05-01  * Income - Salary, Mortgage, Groceries
    Income:Salary                     -$3,000.00            ;  Income - Salary
    Expenses:House:Mortgage           $1,800.00             ;  Mortgage
    Expenses:Groceries                $600.00               ;  Groceries
    Assets:Wells Fargo

~ every 2 weeks from 2023-05-15  * Flowers for wife
    Expenses:Dating                   $29.00                ;  Flowers for wife
    Assets:Wells Fargo

~ yearly from 2023-09-01  * Holidays
    Expenses:Holidays                 $3,000.00             ;  Holidays
    Assets:Wells Fargo

= Expenses:Groceries date:2024-01-01..2024-12-31
    Expenses:Groceries                *0.05                 ;  Food - Inflation
    Assets:Wells Fargo                *-0.05

olimorris avatar Jun 07 '23 11:06 olimorris

Ah, thanks for the example and report @olimorris. https://hledger.org/dev/hledger.html#forecast-period-in-detail is the reference doc:

Here are (with luck) the exact rules ... The forecast period starts on:

  • the later of
    • the start date in the periodic transaction rule
    • the start date in --forecast's argument
  • otherwise (if those are not available): the later of
    • the report start date specified with -b/-p/date:
    • the day after the latest ordinary transaction in the journal
  • otherwise (if none of these are available): today.

while Hledger.Read.InputOptions.forecastPeriod 's comment says:

-- ... -- This begins on: -- - the start date supplied to the --forecast argument, if present -- - otherwise, the later of -- - the report start date if specified with -b/-p/date: -- - the day after the latest normal (non-periodic) transaction in the journal, if any -- - otherwise today. -- ...

and the code is

forecastPeriod :: InputOpts -> Journal -> Maybe DateSpan
forecastPeriod iopts j = do
    DateSpan requestedStart requestedEnd <- forecast_ iopts
    let forecastStart = fromEFDay <$> requestedStart <|> max mjournalend (fromEFDay <$> reportStart) <|> Just (_ioDay iopts)
        forecastEnd   = fromEFDay <$> requestedEnd <|> fromEFDay <$> reportEnd <|> (Just $ addDays 180 $ _ioDay iopts)
        mjournalend   = dbg2 "journalEndDate" $ journalEndDate False j  -- ignore secondary dates
        DateSpan reportStart reportEnd = reportspan_ iopts
    return . dbg2 "forecastspan" $ DateSpan (Exact <$> forecastStart) (Exact <$> forecastEnd)

My own recollection (and apparently the actual behaviour) is that it will not start before today unless you force it to with an explicit forecast period.

So there is some disagreement to clear up. Some very careful contemplation is needed before making any change here as this topic has been/can be hugely confusing.

simonmichael avatar Jun 07 '23 19:06 simonmichael

PRs also welcome!

simonmichael avatar Jun 07 '23 19:06 simonmichael

Thanks @simonmichael, completely understand.

Also worthwhile noting that I can achieve my desired behaviour by adding --forecast="this month"... I'd propose this is actually just a documentation change as there may be users who update their actuals on a daily basis and this change would result in them having to add something like --forecast="tomorrow"...

Seeing as this hasn't been brought up before, it can't be too big a deal for most users.

olimorris avatar Jun 07 '23 19:06 olimorris

Doesn't --forecast="this month".. (or equivalently: --forecast=1..) risk duplicating forecast and recorded transactions in the period between first of month and today ? A clunky way to start forecasting after last transaction (roughly) would be: --forecast=$(hledger stats | rg 'Last.*?([-0-9]+)' -or '$1')..

simonmichael avatar Jun 07 '23 19:06 simonmichael

Yep it absolutely does! Thankfully my numbers are quite small so I'd notice the double count 😆. But I did make a comment in my month end notes to watch out for this.

olimorris avatar Jun 07 '23 19:06 olimorris