How conditional nodes work

I have a complicated formula for intrinsic value that takes into account whether a stock is in the financial sector or not. If it's in the financial sector it uses cost of equity, and if it's not it uses cost of capital. Then I create a conditional node with the condition being "sector = financial." The True node is $intrinsicvalue/mktcap and the False node is $intrinsicvalue/ev.

When I run this, to my surprise, the top-ranked stocks are mostly in the financial sector and the bottom-ranked stocks are mostly not. I had thought that with conditional nodes the stocks are ranked entirely separately, which would evenly space all the financials among the non-financials, but that's clearly not the case. It's not a matter of N/A handling as only about 2% of the stocks are N/A.

If instead of a conditional node I just use Eval(Sector=Financial, $intrinsicvalue/mktcap, $intrinsicvalue/ev), I get quite different results and the top-ranked stocks are more evenly spread between sectors. But you can't really compare the two ratios, so the Eval formula is incorrect.

So I ran a screen to see exactly what was happening and this is what I found.

Each of the sub-nodes ranks ALL stocks according to the rule in that subnode. The final ranking then consists of the rank in the subnode that pertains to the condition.

So, for example, Passage Bio, a biotech, ranks highest in the non-financials. But there are bunch of financial stocks that rank higher than Passage Bio in the $intrinsicvalue/ev ratio. That fact should be irrelevant, since you don't use EV numbers when looking at financial companies, and it's very clearly not what I specified.

So the conditional ranking takes into account a huge amount of entirely irrelevant data.

There's no easy fix for this, as far as I know. I tried this: in the True node, use Eval(Sector=Financial, $intrinsicvalue/mktcap, NA); in the false node, use Eval(Sector=Financial, NA, $intrinsicvalue/EV), and use NAs neutral. But then all the financial stocks are near the top and bottom of the rankings, which are 50% financial and 50% non-financial, and the middling stocks have no financials at all. If you rank by sector, you get the right distribution of financials and non-financials, but outside of the financial/non-financial distinction, I'd rather favor high intrinsic value to EV stocks regardless of sector. Using FRank, even with #exclna, makes no difference.

If anyone knows of a workaround, I'd appreciate hearing from you.

2 Likes

I came up with a workaround, which should be good for replacing conditional nodes: Eval (Sector = Financial, FRank ("Eval (Sector = Financial, $intrinsic / mktcap, NA)", #all, #desc, #exclna), FRank ("Eval (Sector = Financial, NA, $intrinsic / ev)", #all, #desc, #exclna)). This spaces out the financials in equal measures throughout the ranking, and considers information pertinent only to each stock.

2 Likes

Alternately, instead of using a conditional node, one can just use a composite node made up of two mutually exclusive nodes with Eval and NA to get the same result (if you're ranking NAs neutral). One node would be Eval (Sector = Financial, $intrinsic / mktcap, NA) and the other would be Eval (Sector = Financial, NA, $intrinsic / ev). This solution is faster to calculate and won't brush up against the limit on cross-sectional nodes. The key thing to note is that a single formula node spaces out all non-NAs evenly while a node that is part of a conditional node will have NAs occupy significant space in the ranking. For example, let's say you have 100 stocks in your universe and 90 of them are NA. If you use a simple node (or part of a composite node), the ten non-NA stocks will rank 90, 80, 70, 60, 50, 40, 30, 20, 10, and 0. If you use a conditional node, the ten non-NA stocks will rank 99, 98, 97, 96, 95, 4, 3, 2, 1, 0.

3 Likes

Thanks. This is very clever and useful.

The formula can be simplified a bit:
Eval(Sector = Financial, FRank("($intrinsicvalue / mktcap) / (Sector=Financial)", #all, #desc, #exclna), FRank("($intrinsicvalue / ev) / (Sector!=Financial)", #all, #desc, #exclna))

"($intrinsicvalue / mktcap) / (Sector=Financial)": For a non-financial stock, this becomes (yield) / 0, and evaluates to NA (if #exclna is used)

As a future enhancement, I would like to see a more user-friendly way to apply two or more separate Ranking Systems to separate universes.

The method below uses one combined universe (UTIL and NONCYCLICAL) and the rule Sector = UTIL. It does not work as intended, because it does not select an equal number of stocks from both sectors. To make this work, I need to apply a rule in the strategy like this:
SecWeight < 60. But this has still some issues.

1 Like

You should use Dynamic Weight in the Rebalancing Module. If you then have a buy rule SecCount<=10 you should get 10 stocks from each of the two sectors in your universe.

Very clever too! Thanks!

1 Like