-
Geographical Analysis 6
-
Lecture1.1
-
Lecture1.2
-
Lecture1.3
-
Lecture1.4
-
Lecture1.5
-
Lecture1.6
-
-
Cap Table 3
-
Lecture2.1
-
Lecture2.2
-
Lecture2.3
-
-
Simulation 6
-
Lecture3.1
-
Lecture3.2
-
Lecture3.3
-
Lecture3.4
-
Lecture3.5
-
Lecture3.6
-
-
Search Index 8
-
Lecture4.1
-
Lecture4.2
-
Lecture4.3
-
Lecture4.4
-
Lecture4.5
-
Lecture4.6
-
Lecture4.7
-
Lecture4.8
-
-
Fund Distributions 5
-
Lecture5.1
-
Lecture5.2
-
Lecture5.3
-
Lecture5.4
-
Lecture5.5
-
IRR Performance
Benchmark IRR Performance Fee¶
Another way to view define the performance fee is one where the fund will only get a performance fee on an IRR above a given benchmark. This ensures that the general partners have made at least a solid return before they have to pay for the performance fee. Let’s begin with a basic example and see how this might work. Let’s say the following are the capital calls (negative values) in the first three years, then the next two years are the total profits/distributions that need to be split.
cash_flows = pd.Series([-400, -300, -300, 800, 1600])
print(cash_flows)
0 -400
1 -300
2 -300
3 800
4 1600
dtype: int64
Before any performance fees, this IRR looks pretty good!
IRR = minimize(sq_distance, .05, args=(cash_flows.index, cash_flows), method="Nelder-Mead")['x'][0]
print(IRR)
0.36175781250000116
Now, we also know that up until year 4, even if the performance fee has no benchmark to beat, it won't be collected because the total earned is still less than the capital called down. In the fifth year, we can get that performacne fee, so let's make a new version of net cash flows and update for the 20% of (2400-1000) that the fund gets.
cash_flows_net = cash_flows.copy()
cash_flows_net.loc[4] -= 1400 * .2
print(cash_flows_net)
0 -400.0
1 -300.0
2 -300.0
3 800.0
4 1320.0
dtype: float64
What about the IRR after this performance fee?
IRR = minimize(sq_distance, .05, args=(cash_flows.index, cash_flows_net), method="Nelder-Mead")['x'][0]
print(IRR)
0.3101464843750009
Finding the NPV¶
The first thing we are going to need to do for figuring out if a performance fee would be collected given a hurdle rate is to see what the NPV is. Let's say that our hurdle rate IRR is 10%. In this case, we know that the NPV using r=10% must be positive to collected a performance fee.
NPV = (cash_flows / (1.1) ** cash_flows.index).sum()
print(NPV)
773.2122122805815
Becuase it is positive, we know that we will collect a fee! Now, if that fee is collected in the last year, we can't use the present value for figuring out what is left over to be collected on, we must use the future value. The future value (at year 4) of the NPV is....
FV = NPV * 1.1 ** 4
print(FV)
1132.0599999999997
So this means that $1132.05 in terms of future value, was earned over the expected benchmark return. We can create a series of data that takes out this future value to confirm that the cashflows less this future value lead to an IRR of 10%.
cash_flows_bench = cash_flows.copy()
cash_flows_bench.loc[4] -= FV
print(cash_flows_bench)
0 -400.00
1 -300.00
2 -300.00
3 800.00
4 467.94
dtype: float64
IRR = minimize(sq_distance, .05, args=(cash_flows_bench.index, cash_flows_bench), method="Nelder-Mead")['x'][0]
print(IRR)
0.10000000000000017
So the net earnings are going to be the cashflows that get the investor to a 10% IRR followed plus 80% of the residual profits.
cash_flows_net = cash_flows_bench.copy()
cash_flows_net.loc[4] += FV * .8
print(cash_flows_net)
0 -400.000
1 -300.000
2 -300.000
3 800.000
4 1373.588
dtype: float64
IRR = minimize(sq_distance, .05, args=(cash_flows_net.index, cash_flows_net), method="Nelder-Mead")['x'][0]
print(IRR)
0.3204736328125011
Catch Up¶
With a hurdle rate, there are sometimes provisions that the fund gets to catch up once the hurdle rate has been passed. What this means is that there are three areas for the returns:
- While the returns are below the benchmark returns, all profits are given to the limited partners.
- After the benchmark return has been reached, all profits are given to the general partners until they have "caught up" to what their proportional performance fee would have been.
- Any further profits are split based on the performance fee.
The catch up will take the following form assuming that the distributions are greater than step 1 plus the catch up:
$ C = \frac{D_{1} * P}{1-P}$
where
$ C = \text{Catch-up Value}$
$ D_{1} = \text{Distribution from step 1}$
$ P = \text{Performance Fee}$
In the case that the total distributions are possibly less than the catch up we can modify it to say that it is the minimum of this value or whatever is left over after the hurdle rate.
$ C = min(\frac{D_{1} * P}{1-P}, D_{T} - D_{1})$
where we add in
$ D_{T} = \text{Total distributions}$
Let's work through the example again to show how this might differ.
Start with the benchmark returns once again.
print("Total Cash Flows:")
print(cash_flows)
print()
NPV = (cash_flows / (1.1) ** cash_flows.index).sum()
FV = NPV * 1.1 ** 4
cash_flows_bench = cash_flows.copy()
cash_flows_bench.loc[4] -= FV
print("Cash Flows Allocated for Benchmark Return:")
print(cash_flows_bench)
print()
cash_flow_residual = cash_flows - cash_flows_bench
print("Residual Cash Flows:")
print(cash_flow_residual)
Total Cash Flows:
0 -400
1 -300
2 -300
3 800
4 1600
dtype: int64
Cash Flows Allocated for Benchmark Return:
0 -400.00
1 -300.00
2 -300.00
3 800.00
4 467.94
dtype: float64
Residual Cash Flows:
0 0.00
1 0.00
2 0.00
3 0.00
4 1132.06
dtype: float64
Now, let's see what the catch up amount needs to be.
d1 = cash_flows_bench.sum()
print("Step 1 Distributions:")
print(d1)
print()
c = (d1 * .2) / .8
print("Catch up:")
print(c)
print()
cash_flow_residual2 = cash_flow_residual.copy()
cash_flow_residual2.loc[4] -= c
print("Residual Cashflows After Catch Up:")
print(cash_flow_residual2)
Step 1 Distributions:
267.9400000000003
Catch up:
66.98500000000007
Residual Cashflows After Catch Up:
0 0.000
1 0.000
2 0.000
3 0.000
4 1065.075
dtype: float64
For the final step, the residual cash flows are split between the two investors based on the 20% performance fee. So we can construct the cash flows from this for both parties.
cash_flows_LP = cash_flows_bench + cash_flow_residual2 * .8
cash_flows_GP = cash_flow_residual2 * .2
cash_flows_GP.loc[4] += c
print("Limited Partners Cashflows:")
print(cash_flows_LP)
print()
print("General Partners Cashflows:")
print(cash_flows_GP)
print()
Limited Partners Cashflows:
0 -400.0
1 -300.0
2 -300.0
3 800.0
4 1320.0
dtype: float64
General Partners Cashflows:
0 0.0
1 0.0
2 0.0
3 0.0
4 280.0
dtype: float64
And maybe we want to be checking to make sure that the cashflows are split 20%/80% at this point. In that case, the following checks will prove that.
print(cash_flows_LP.sum())
print(cash_flows_GP.sum())
print(cash_flows_GP.sum() / (cash_flows_LP.sum() + cash_flows_GP.sum()))
1120.0
280.0
0.2