Calculating Asset Allocation

272 views
Skip to first unread message

Craig Earls

unread,
Nov 19, 2011, 12:36:06 AM11/19/11
to ledge...@googlegroups.com
I have written a new section for the manual that goes into something
that I have spent a bit of time working on.  Just in case there is a
far easier way to do it I thought I would broadcast the section to get
any comments.  It may be a while before it shows up in the *next*
branch. This is best read in fixed width font. It is taken from the
info version of the manual.

6.3.1 Asset Allocation
----------------------

A very popular method of managing retirement portfolios is to
control the percent allocation of assets by certain categories. The
mix of categories and the weights applied to them vary by investing
philosophy, but most follow a similar pattern. Tracking asset
allocation in ledger is not difficult but does require some additional
effort to describe how the various assets you own contribute to the
asset classes you want to track.

In our simple example we assume you want to apportion you assets into
the general categories of domestic and international equities (stocks)
and a combined category of bonds and cash. For illustrative purposes we
will use several publicly available mutual funds from Vanguard. the
three funds we will track are the Vanguard 500 IDX FD Signal (VIFSX),
the Vanguard Target Retirement 2030 (VTHRX), and the Vanguard Short Term
Federal Fund (VSGBX). Each of these funds allocates assets to different
categories of the investment universe and in different proportions.
When you buy a share of VTHRX, that share is partially invested in
equities, and partially invested in bonds and cash. Below is the asset
allocation for each of the instruments listed above:

Domestic Global
Symbol Equity Equity bonds/cash
VIFSX 100%
VTHRX 24.0% 56.3% 19.7%
VSGBX 100%

These numbers are available from the prospectus of any publicly
available mutual fund. Of course a single stock issue is 100% equity
and a single bond issue is 100% bonds.

We track purchases of specific investments using the symbol of that
investment as its commodity. How do we tell Ledger that a share of
VTHRX is 24% Global equity etc.? Enter automatic transactions and
virtual accounts.

At the top of our ledger we enter automatic transactions that
describe these proportions to Ledger. In the same entries we set up
virtual accounts that let us separate these abstract calculations from
our actual balances.

For the three instruments listed above, those automatic transactions
would look like:
;
; automatic calculations for asset allocation tracking
;
= expr ( commodity == 'VIFSX' )
(Allocation:Equities:Domestic) 1.000

= expr ( commodity == 'VTHRX' )
(Allocation:Equities:Global) 0.240
(Allocation:Equities:Domestic) 0.563
(Allocation:Bonds/Cash) 0.197

= expr ( commodity == 'VBMFX')
(Allocation:Bonds/Cash) 1.000

How do these work? First the `=' sign at the beginning of the line
tells ledger this is an automatic transaction to be applied when the
condition following the `=' is true. After the `=' sign is a value
expression (*note Value Expressions::) that returns true any time a
posting contains the commodity of interest.

The following line gives the proportions (not percentages) of each
unit of commodity that belongs to each asset class. Whenever Ledger
sees a buy or sell of a particular commodity it will credit or debit
these virtual accounts with that proportion of the number of shares
moved.

Now that Ledger understands how to distribute the commodities amongst
the various asset classes how do we get a report that tells us our
current allocation? Using the balance command and some tricky
formatting!
ledger bal Allocation --current --format "\
%-17((depth_spacer)+(partial_account))\
%10(percent(market(display_total), market(parent.total)))\
%16(market(display_total))\n"

Which yields:

Allocation 100.00% $100000.00
Bonds/Cash 38.94% $38940.00
Equities 61.06% $61060.00
Domestic 95.31% $58196.29
Global 4.69% $2863.71
Let's look at the Ledger invocation a bit closer. The command above
is split into lines for clarity. The first line is very vanilla Ledger
asking for the current balances of the account in the "Allocation"
tree, using a special formatter.

The magic is in the formatter. The second line simply tells Ledger to
print the partial account name indented by its depth in the tree. The
third line is where we calculate and display the percentages. The
`display_total' command give the values of the total calculated for the
account in this line. The `parent.total' command gives the total for
the next level up in the tree. `percent' format their ratio as a
percentage. The fourth line tells ledger to display the current market
value of the the line.

--
Craig, Corona De Tucson, AZ
enderw88.wordpress.com

John Rakestraw

unread,
Nov 19, 2011, 8:17:49 PM11/19/11
to ledge...@googlegroups.com
Hi, Craig --

On Sat, 19 Nov 2011, Craig Earls wrote:
> I have written a new section for the manual that goes into something
> that I have spent a bit of time working on.  Just in case there is a
> far easier way to do it I thought I would broadcast the section to get
> any comments.  It may be a while before it shows up in the *next*
> branch. This is best read in fixed width font. It is taken from the
> info version of the manual.

This looks promising, but I can't make it work. I tried it with a simple
test file, using your example allocations below, but the ledger report
query generates this error message:

While parsing file "ledgerfile/test", line 3:
While parsing automated transaction:


> = expr ( commodity == 'VIFSX' )

Error: Missing ')'
While parsing file "ledgerfile/test", line 6:
While parsing automated transaction:


> = expr ( commodity == 'VTHRX' )

Error: Missing ')'
While parsing file "ledgerfile/test", line 11:
While parsing automated transaction:


> = expr ( commodity == 'VBMFX')

Error: Missing ')'

(The automatic entries are a straight copy/paste from your email.)

Thanks.
--
John Rakestraw

Craig Earls

unread,
Nov 19, 2011, 8:43:51 PM11/19/11
to ledge...@googlegroups.com
Yeah, you need the latest build from'next' John fixed this last week.

John Rakestraw

unread,
Nov 19, 2011, 9:15:46 PM11/19/11
to ledge...@googlegroups.com
On Sat, 19 Nov 2011, Craig Earls wrote:
> Yeah, you need the latest build from'next' John fixed this last week.

Indeed, thanks.

--John

--
John Rakestraw

Jim Robinson

unread,
Mar 14, 2012, 8:16:09 PM3/14/12
to ledge...@googlegroups.com
This is awesome, thanks for writing it up.  I tried it out and found
that ledger  3.0.0-20120217 complaints about not understanding
the format string:

While calculating format expression:
  percent(market(display_total), market((parent.total)))
While evaluating value expression:
  percent(market(display_total), market((parent.total)))
                                        ^^^^^^^^^^^^^^
While calling function 'market <#EXPR (parent.total)>':
While calling function 'percent (<#EXPR market(display_total)>, <#EXPR market((parent.total))>)':
Error: Left operand does not evaluate to an object

While I did find that this appeared to work:

  ledger balance -X '$' Allocation --current -%


Jim

Craig Earls

unread,
Mar 14, 2012, 8:21:17 PM3/14/12
to ledge...@googlegroups.com
I have been building the latest ledger but I am still using a custom build witha few tweeks of my own.  I need to switch so I can see if anything I need has broken. (I have modified mine to tag automatic transactions as cleared if the base transaction is cleared and I have a special tag that returns only the filename, and not the entire path.

I will check this out.

Craig Earls

unread,
Mar 14, 2012, 11:16:49 PM3/14/12
to ledge...@googlegroups.com
This is very strange.  It works for me under the latest build exactly as presented in the manual.

John Wiegley

unread,
Mar 15, 2012, 12:54:00 AM3/15/12
to ledge...@googlegroups.com
>>>>> Jim Robinson <jim.robinson-Re5J...@public.gmane.org> writes:

--percent only works with balance reports. The error reporting should be
clearer about this. Do you have a balance report where it's failing?

John

Jim Robinson

unread,
Mar 15, 2012, 1:29:35 AM3/15/12
to ledge...@googlegroups.com
On Wednesday, March 14, 2012 9:54:00 PM UTC-7, John Wiegley wrote:
>>>>> Jim Robinson writes:

> This is awesome, thanks for writing it up.  I tried it out and found that
> ledger 3.0.0-20120217 complaints about not understanding the format string 

... 

> While I did find that this appeared to work:

>   ledger balance -X '$' Allocation --current -%

--percent only works with balance reports.  The error reporting should be
clearer about this.  Do you have a balance report where it's failing?

Well, I was saying that "-%" did work for me using 'balance',
but the --format example did not... So am I doing something
wrong with this example entry below?

$ cat test.txt 
= expr ( commodity == 'VTHRX' )
      (Allocation:Equities:Global)               0.240
      (Allocation:Equities:Domestic)             0.563
      (Allocation:Bonds/Cash)                    0.197

2012-01-01 * Fidelity - 403b Contributions
        Assets:403b:Fidelity:VTHRX               10.000 VTHRX @@  $1,000.00
        Assets:403b:SCRP

So I can get a balance:

$ ledger -f test.txt balance Allocation -X '$'
               $1000  Allocation
                $197    Bonds/Cash
                $803    Equities
                $563      Domestic
                $240      Global
--------------------
               $1000

And I can get a percentage breakdown:

$ ledger -f test.txt balance Allocation -X '$' -%
             100.00%  Allocation
              19.70%    Bonds/Cash
              80.30%    Equities
              70.11%      Domestic
              29.89%      Global

But the format example doesn't like parent.total:

$ ledger -f test.txt balance Allocation --current --format "\
                    %-17((depth_spacer)+(partial_account))\
                    %10(percent(market(display_total), market(parent.total)))\
                    %16(market(display_total))\n"
While calculating format expression:
  percent(market(display_total), market((parent.total)))
While evaluating value expression:
  percent(market(display_total), market((parent.total)))
                                        ^^^^^^^^^^^^^^
While calling function 'market <#EXPR (parent.total)>':
While calling function 'percent (<#EXPR market(display_total)>, <#EXPR market((parent.total))>)':
Error: Left operand does not evaluate to an object

Apologies if I'm missing something obvious!

Jim

John Wiegley

unread,
Mar 15, 2012, 5:07:57 AM3/15/12
to ledge...@googlegroups.com
>>>>> Jim Robinson <jim.robinson-Re5J...@public.gmane.org> writes:

> But the format example doesn't like parent.total:

> $ ledger -f test.txt balance Allocation --current --format "\
> %-17((depth_spacer)+(partial_account))\
> %10(percent(market(display_total),

> %market(parent.total)))\ 16(market(display_total))\n"


> While calculating format expression:
> percent(market(display_total), market((parent.total)))
> While evaluating value expression:
> percent(market(display_total), market((parent.total)))
> ^^^^^^^^^^^^^^

Ah, you need to do something closer to what -% is doing, to be more defensive:

%((is_account&parent&parent.total) ? percent(scrub(market(display.total)), %scrub(market(parent.total))) : 0)

Parent is null when it gets to "root" account (which contain the final total
at the bottom).

> Apologies if I'm missing something obvious!

I must admit, the solution is far from obvious. :(

John

Jim Robinson

unread,
Mar 15, 2012, 8:45:17 AM3/15/12
to ledge...@googlegroups.com

On Thursday, March 15, 2012 2:07:57 AM UTC-7, John Wiegley wrote:

Parent is null when it gets to "root" account (which contain the final total
at the bottom).

Ah, gotcha.  So either wrapping it in a test like you did, or
just indicating we don't want a total would work.  A variation
that I liked after playing around with the format a bit:

$ ledger balance Allocation -X '$' --no-total --format \
"%8(__tmp = market(parent.total, value_date, exchange);"\
" ((partial_account & parent & __tmp) ?"\
"   percent(scrub(market(total, value_date, exchange)), "\
"           scrub(__tmp)) : 0))    "\
"%14(market(display_total))"\
"%(depth_spacer)  "\
"%(partial_account)\n";
 

Craig Earls

unread,
Mar 15, 2012, 10:34:12 AM3/15/12
to ledge...@googlegroups.com
This doesn't explain why it IS working for me without the protection you mention.  "Allocation" is a top level account so I would think the paretn.total would puke on it, but it doesn't.

I use exactly the format string seen below and it works like a champ.

Jim Robinson

unread,
Mar 15, 2012, 10:36:44 AM3/15/12
to ledge...@googlegroups.com
On Thursday, March 15, 2012 7:34:12 AM UTC-7, enderw88 wrote:
This doesn't explain why it IS working for me without the protection you mention.  "Allocation" is a top level account so I would think the paretn.total would puke on it, but it doesn't.

And your call to ledger is producing a total at the end?

Jim
 

Craig Earls

unread,
Mar 15, 2012, 10:40:33 AM3/15/12
to ledge...@googlegroups.com
Yes.  Here is the entire call to Ledger is use:


ledger bal Allocation  --current --format "\
| %-20((depth_spacer)+(partial_account))\
%10(percent(market(display_total), market(parent.total)))\
%16(market(display_total)) |\n%/\
+------------------------------------------------+

Craig Earls

unread,
Mar 15, 2012, 10:43:23 AM3/15/12
to ledge...@googlegroups.com
Ooh, now I see it.  I DO suppress the total at the end since I print out a bounding box rather than letting ledger finish up on its own. I tried taking out the final line and it fails as you saw.  I will update the documentation.
Message has been deleted

Jim Robinson

unread,
Mar 15, 2012, 10:49:16 AM3/15/12
to ledge...@googlegroups.com
On Thursday, March 15, 2012 7:43:23 AM UTC-7, enderw88 wrote:
Ooh, now I see it.  I DO suppress the total at the end since I print out a bounding box rather than letting ledger finish up on its own. I tried taking out the final line and it fails as you saw.  I will update the documentation.

Yes, I had missed that trailing "%/" at the end of your
example!

Jim

Jim Robinson

unread,
Mar 15, 2012, 10:51:59 AM3/15/12
to ledge...@googlegroups.com
Ah, and it looks like '%/' is missing from the end of your
original post here as well, I don't know if it was missing from
the real documentation or not.

Mentioning both the guard code and the --no-total option would
probably make sense, right?

Jim Robinson

unread,
Mar 15, 2012, 10:59:14 AM3/15/12
to ledge...@googlegroups.com
On Friday, November 18, 2011 9:36:06 PM UTC-8, enderw88 wrote:

philosophy, but most follow a similar pattern.  Tracking asset
allocation in ledger is not difficult but does require some additional
effort to describe how the various assets you own contribute to the 

... 

   These numbers are available from the prospectus of any publicly available mutual fund. 


As an aside, I found it much easier to grab information from
online summaries such as


rather than hunting through the prospectus PDFs.

Jim

Craig Earls

unread,
Mar 15, 2012, 11:01:27 AM3/15/12
to ledge...@googlegroups.com
Yes.  I took out part of the formatting that produced "pretty" output.  The "%/\" is code that tells ledger to use the following format code only for the last line.  I have a consolidated report that asset allocation is only a part of.  It is surrounded by a box to help it stand out:

+-----------Current Allocation-------------------+
| Allocation             100.00%     $ XXXXXX |
|   Bonds/Cash            44.97%      $ XXXX |
|   Equities              55.03%      $ XXXXXX |
|     Domestic            82.49%      $ XXXXX |
|     Global              17.51%      $ XXXXX |
+------------------------------------------------+

Craig Earls

unread,
Mar 15, 2012, 11:02:31 AM3/15/12
to ledge...@googlegroups.com
Vanguard is pretty good about it, but I had a few other mutual funds from companies that are not so good.  The prospectus is the LCD.

John Wiegley

unread,
Mar 15, 2012, 6:00:30 PM3/15/12
to ledge...@googlegroups.com
>>>>> Craig Earls <enderw88-Re5JQE...@public.gmane.org> writes:

> This doesn't explain why it IS working for me without the protection you
> mention.  "Allocation" is a top level account so I would think the
> paretn.total would puke on it, but it doesn't.

When I say top-level, I mean the invisible account which is the parent of
*all* accounts, not the visibly top-level accounts, which are actually at
--depth=1.

John

Craig Earls

unread,
Mar 15, 2012, 7:11:12 PM3/15/12
to ledge...@googlegroups.com
Docs have been updated

Jim Robinson

unread,
Oct 10, 2019, 9:28:26 AM10/10/19
to Ledger
I've been using the Asset Allocation reporting since Craig wrote it up for everyone, and have really liked it.  Now I'm wondering what the best way to break down asset allocation across account types (personal vs. retirement) might be.

Since it's a balance statement and keying off the Allocation "accounts"

$ ledger balance -X '$' --no-total --format "${fmt}" ^Allocation: ;

I thought one way could be to include extra guards on the 'expr' that looks at the account names to determine retirement or personal investment status, and then indicate in the fund names which ones belong to which groups, but that leads to a lot of duplication of information (2x the entries breaking down the allocations of a fund).

Has anyone come up with a simpler way to get such a breakdown?

Reply all
Reply to author
Forward
0 new messages