Skip to content

Commit

Permalink
PE: docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Anexen committed Nov 28, 2023
1 parent 313d338 commit 64e077c
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include = [
[lib]
name = "pyxirr"
crate-type = ["rlib", "cdylib"]
doctest = false

[dependencies]
pyo3 = "0.20"
Expand Down
9 changes: 9 additions & 0 deletions docs/_inline/pe/direct_alpha.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The concept of direct alpha is closely related to the KS PME as it uses the
same method to calculate an IRR which is then compared to the fund’s IRR.
However, the difference between KS PME and direct alpha method is that in
direct alpha the under or out performance is quantified by calculating the
compounded cash flows plus the fund’s NAV.

See also:
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Direct_Alpha>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
14 changes: 11 additions & 3 deletions docs/_inline/pe/dpi.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Calculates the DPI (Distributions to Paid-In) for a set of cash flows. This is
just the total distributions divided by the total contributions, with
contributions coerced to be positive.
Distributed to Paid-In Capital (DPI) is a term used to measure the total
capital that a private equity fund has returned thus far to its investors. It
is also referred to as the realisation multiple. The DPI value is the
cumulative value of all investor distributions expressed as a multiple of all
the capital paid into the fund up to that time.

Formula: DPI = Cumulative Distributions / Paid-In Capital

See also:
- <https://www.moonfare.com/glossary/distributed-to-paid-in-capital-dpi>
- <https://www.investopedia.com/terms/r/realization_multiple.asp>
10 changes: 10 additions & 0 deletions docs/_inline/pe/ks_pme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
KS PME represents a market adjusted equivalent of the Total Value to
Paid-In-Capital (TVPI). KS PME is calculated by finding the future value of
each contribution and distribution using the stock market index returns.

Formula: FV(Distributions) + NAV / FV(Contributions)

See also:
- `ks_pme_flows` and `tvpi` functions
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Kaplan_Schoar_PME>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
14 changes: 10 additions & 4 deletions docs/_inline/pe/ks_pme_flows.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Use the Kaplan-Schoar method to re-scale the private equity flows to match the
public market equivalents (PME) for comparison. This method works as follows,
for each period, re-scale the amount as: amount * (pme_price_final_period /
pme_price_current_period). Basically you are future valuing the amount to the
final period based on the returns of the PME.
public market equivalents (PME) for comparison.

This method works as follows, for each period, re-scale the amount as:
`amount * (index[final_period] / index[current_period])`.
Basically you are future-valuing the amount to the final period based on the
returns of the PME.

See also:
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Kaplan_Schoar_PME>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
9 changes: 9 additions & 0 deletions docs/_inline/pe/ln_pme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The basic idea of Long Nickels method is that the cash flows of a VC fund i.e.
contributions and distributions are invested in a stock market index and to
generate a net asset value (NAV) at the end of each period. The last NAV is
used to calculate the IRR and this IRR is the Long Nickels PME.

See also:
- `ln_pme_nav` function
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Long-Nickels_PME>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
9 changes: 7 additions & 2 deletions docs/_inline/pe/ln_pme_nav.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ the nav. Instead of relying on the given nav, it is calculated as the future
valued contributions less the future valued distributions.

This will look like (for two periods with a contribution and distribution in each):
```
nav = c[1] * index[-1]/index[1] + c[2] * index[-1]/index[2]
- d[1] * index[-1]/index[1] - d[2] * index[-1]/index[2]
```

nav = c1 * px_final/px_1 + c2 * px_final/px_2
- d1 * px_final/px_1 - d2 * px_final/px_2
See also:
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Long-Nickels_PME>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
8 changes: 8 additions & 0 deletions docs/_inline/pe/m_pme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mPME is similar to PME+ in the sense that it uses a scaling factor. However,
mPME uses different scaling factors for cash flows at different time intervals.
Thus, it attempts to improve the limitations of PME+ where a single coefficient
λ is used to scale all distributions.

See also:
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#Modified_PME>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
12 changes: 12 additions & 0 deletions docs/_inline/pe/moic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
The Multiple on Invested Capital (MOIC) measures the performance of an
investment today relative to the initial investment.

Formula: (Realized investment + Unrealized investment) / Initial Investment

Unrealised value, also referred to as residual value (or NAV), is the total
value of the remaining portfolio’s active investments that have not yet been
liquidated.

See also:
- <https://www.moonfare.com/glossary/multiple-on-invested-capital-moic>
- <https://financestu.com/tvpi-vs-moic/>
8 changes: 8 additions & 0 deletions docs/_inline/pe/pme_plus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PME+ discount every distribution by a factor computed so that the NAV of the
index investment matches the NAV of the fund. The PME+ returns an IRR value of
discounted distributions.

See also:
- `pme_plus_flows` and `pme_plus_lambda` functions
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#PME+>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
14 changes: 9 additions & 5 deletions docs/_inline/pe/pme_plus_flows.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
Use the PME+ method to re-scale the private equity flows to match the public
market equivalents (PME) for comparison. This method works as follows: create
an equation that sets the nav equal to the contributions future valued based on
an equation that sets the NAV equal to the contributions future valued based on
the PME returns, minus the distributions multiplied by a scalar (lambda) future
valued based on the PME returns.

This will look like (for two periods with a contribution and distribution in each):

nav = c1 * px_final/px_1 + c2 * px_final/px_2
- d1 * lambda * px_final/px_1 - d2 * lambda * px_final/px_2

```
nav = c[1] * index[-1]/index[1] + c[2] * index[-1]/index[2]
- d[1] * λ * index[-1]/index[1] - d[2] * λ * index[-1]/index[2]
```
Solve for lambda so that the two sides of the equation are equal. Then multiply
all the distributions by lambda to re-scale them.

See also:
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#PME+>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
11 changes: 11 additions & 0 deletions docs/_inline/pe/pme_plus_lambda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Find λ used in PME+ method.

Formula: λ = (Scaled Distributions - NAV) / Scaled Contributions
Where:
- Scaled Distributions = sum(distributions * index[last] / index[current])
- Scaled Contributions = sum(contributions * index[last] / index[current])

See also:
- `pme_plus_flows` function
- <https://en.wikipedia.org/wiki/Public_Market_Equivalent#PME+>
- <https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/>
14 changes: 11 additions & 3 deletions docs/_inline/pe/rvpi.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Calculates RVPI (Residual Value to Paid-In) for a set of cash flows. This is
the total residual value (NAV) divided by the total contributions, with
contributions coerced to be positive.
Residual Value to Paid-In Capital (RVPI) is a term used to measure the residual
value (NAV) of a private equity fund as a multiple of the capital paid in by the
investors. The residual value is the current fair value of all assets held by
the fund and the paid-in capital by the investors is the total of all
contributed capital up to that time.

Formula: Residual Value / Paid-In Capital

See also:
- <https://www.moonfare.com/glossary/residual-value-to-paid-in-capital-rvpi>
- <https://financestu.com/rvpi/>
17 changes: 12 additions & 5 deletions docs/_inline/pe/tvpi.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Total Value to Paid-In Capital is a measure of the performance of a private
equity fund. It represents the total value of a fund relative to the amount of
capital paid into the fund to date. The total value of a fund is the sum of
realised value (all distributions made to investors to date) plus the
unrealised value (residual value of investments) still held by the fund.
Total Value to Paid-In Capital (also known as the 'Investment Multiple') is a
measure of the performance of a private equity fund. It represents the total
value of a fund relative to the amount of capital paid into the fund to date.
The total value of a fund is the sum of realised value (all distributions made
to investors to date) plus the unrealised value (residual value of investments)
still held by the fund.

Formula: (Distributed Capital + residual Value) / Paid-In Capital

See also:
- <https://www.moonfare.com/glossary/total-value-to-paid-in-capital-tvpi>
- <https://financestu.com/tvpi-vs-moic/>
14 changes: 14 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,18 @@ Rust-powered collection of financial functions.
- [IRR](functions.md#irr)
- [MIRR](functions.md#mirr)
- [XIRR](functions.md#xirr)
- [Private Equity](private_equity.md)
- [DPI](private_equity.md#dpi)
- [RVPI](private_equity.md#rvpi)
- [TVPI](private_equity.md#tvpi)
- [MOIC](private_equity.md#moic)
- [LN-PME NAV](private_equity.md#ln_pme_nav)
- [LN-PME](private_equity.md#ln_pme)
- [KS-PME Flows](private_equity.md#ks_pme_flows)
- [KS-PME](private_equity.md#ks_pme)
- [mPME](private_equity.md#m_pme)
- [PME+ Flows](private_equity.md#pme_plus_flows)
- [PME+ Lambda](private_equity.md#pme_plus_lambda)
- [PME+](private_equity.md#pme_plus)
- [Direct Alpha](private_equity.md#direct_alpha)
- [Benchmarks](bench/index.html)
54 changes: 54 additions & 0 deletions docs/private_equity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% include head.html %}

## DPI

{% include_relative ./_inline/pe/dpi.md %}

## RVPI

{% include_relative ./_inline/pe/rvpi.md %}

## TVPI

{% include_relative ./_inline/pe/tvpi.md %}

## MOIC

{% include_relative ./_inline/pe/moic.md %}

## LN-PME

{% include_relative ./_inline/pe/ln_pme.md %}

## LN-PME NAV

{% include_relative ./_inline/pe/ln_pme_nav.md %}

## KS-PME Flows

{% include_relative ./_inline/pe/ks_pme_flows.md %}

## KS-PME

{% include_relative ./_inline/pe/ks_pme.md %}

## mPME

{% include_relative ./_inline/pe/m_pme.md %}

## PME+ Flows

{% include_relative ./_inline/pe/pme_plus_flows.md %}

## PME+ Lambda

{% include_relative ./_inline/pe/pme_plus_lambda.md %}

## PME+

{% include_relative ./_inline/pe/pme_plus.md %}

## Direct Alpha

{% include_relative ./_inline/pe/direct_alpha.md %}

16 changes: 15 additions & 1 deletion src/core/private_equity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub fn tvpi_2(contributions: &[f64], distributions: &[f64], nav: f64) -> Result<
Ok((ds + nav) / cs)
}

#[doc = include_str!("../../docs/_inline/pe/moic.md")]
pub fn moic(amounts: &[f64], nav: f64) -> Result<f64> {
// MOIC divides the total value of the investment or fund by the total invested capital,
// whereas TVPI divides it by the paid-in capital (meaning, the capital that investors have
Expand All @@ -52,6 +53,7 @@ pub fn moic(amounts: &[f64], nav: f64) -> Result<f64> {
tvpi(amounts, nav)
}

#[doc = include_str!("../../docs/_inline/pe/moic.md")]
pub fn moic_2(contributions: &[f64], distributions: &[f64], nav: f64) -> Result<f64> {
tvpi_2(contributions, distributions, nav)
}
Expand Down Expand Up @@ -79,10 +81,12 @@ pub fn ks_pme_flows_2(
Ok((c, d))
}

#[doc = include_str!("../../docs/_inline/pe/ks_pme.md")]
pub fn ks_pme(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
ks_pme_flows(amounts, index).and_then(|a| tvpi(&a, nav))
}

#[doc = include_str!("../../docs/_inline/pe/ks_pme.md")]
pub fn ks_pme_2(
contributions: &[f64],
distributions: &[f64],
Expand All @@ -92,11 +96,13 @@ pub fn ks_pme_2(
ks_pme_flows_2(contributions, distributions, index).and_then(|(c, d)| tvpi_2(&c, &d, nav))
}

#[doc = include_str!("../../docs/_inline/pe/m_pme.md")]
pub fn m_pme(amounts: &[f64], index: &[f64], nav: &[f64]) -> Result<f64> {
let (contributions, distributions) = split_amounts(amounts);
m_pme_2(&contributions, &distributions, index, nav)
}

#[doc = include_str!("../../docs/_inline/pe/m_pme.md")]
pub fn m_pme_2(
contributions: &[f64],
distributions: &[f64],
Expand Down Expand Up @@ -151,13 +157,15 @@ pub fn pme_plus_flows_2(
Ok(scale(distributions, lambda))
}

#[doc = include_str!("../../docs/_inline/pe/pme_plus_lambda.md")]
pub fn pme_plus_lambda(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
check_input_len(amounts, index)?;

let (contributions, distributions) = split_amounts(amounts);
pme_plus_lambda_2(&contributions, &distributions, index, nav)
}

#[doc = include_str!("../../docs/_inline/pe/pme_plus_lambda.md")]
pub fn pme_plus_lambda_2(
contributions: &[f64],
distributions: &[f64],
Expand All @@ -174,6 +182,7 @@ pub fn pme_plus_lambda_2(
Ok((cs - nav) / ds)
}

#[doc = include_str!("../../docs/_inline/pe/pme_plus.md")]
pub fn pme_plus(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
let mut cf = pme_plus_flows(amounts, index, nav)?;

Expand All @@ -184,6 +193,7 @@ pub fn pme_plus(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
super::irr(&cf, None)
}

#[doc = include_str!("../../docs/_inline/pe/pme_plus.md")]
pub fn pme_plus_2(
contributions: &[f64],
distributions: &[f64],
Expand Down Expand Up @@ -214,6 +224,7 @@ pub fn ln_pme_nav_2(contributions: &[f64], distributions: &[f64], index: &[f64])
ln_pme_nav(&amounts, index)
}

#[doc = include_str!("../../docs/_inline/pe/ln_pme.md")]
pub fn ln_pme(amounts: &[f64], index: &[f64]) -> Result<f64> {
let pme_nav = ln_pme_nav(amounts, index)?;
let mut cf = amounts.to_owned();
Expand All @@ -223,6 +234,7 @@ pub fn ln_pme(amounts: &[f64], index: &[f64]) -> Result<f64> {
super::irr(&cf, None)
}

#[doc = include_str!("../../docs/_inline/pe/ln_pme.md")]
pub fn ln_pme_2(contributions: &[f64], distributions: &[f64], index: &[f64]) -> Result<f64> {
let mut amounts = combine_amounts(contributions, distributions);
let pme_nav = ln_pme_nav(&amounts, index)?;
Expand All @@ -232,6 +244,7 @@ pub fn ln_pme_2(contributions: &[f64], distributions: &[f64], index: &[f64]) ->
super::irr(&amounts, None)
}

#[doc = include_str!("../../docs/_inline/pe/direct_alpha.md")]
pub fn direct_alpha(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
let mut cf = ks_pme_flows(amounts, index)?;
if let Some(last) = cf.last_mut() {
Expand All @@ -240,6 +253,7 @@ pub fn direct_alpha(amounts: &[f64], index: &[f64], nav: f64) -> Result<f64> {
super::irr(&cf, None)
}

#[doc = include_str!("../../docs/_inline/pe/direct_alpha.md")]
pub fn direct_alpha_2(
contributions: &[f64],
distributions: &[f64],
Expand Down Expand Up @@ -467,7 +481,7 @@ mod tests {
#[rstest]
#[case(&[-25., 15., 0.], &[100., 115., 130.], 20., 0.0875)]
// example from https://en.wikipedia.org/wiki/Public_Market_Equivalent#Direct_Alpha
#[case(&[-100., -50., 60., 10., 0.], &[100., 105., 115., 117., 120.], 110., 0.0108)]
#[case(&[-100., -50., 60., 10., 0.], &[100., 105., 115., 117., 120.], 110., 0.0109)]
// example from https://blog.edda.co/advanced-fund-performance-methods-pme-direct-alpha/
#[case(&[-80., -140., 0., 70., 140., 85.], &[890.35, 1144.98, 1271.5, 1289.09,1466.47, 1842.37], 70., 0.028)]
// example from https://directalphamethod.info/
Expand Down
Loading

0 comments on commit 64e077c

Please sign in to comment.