Module: Xirr::Returns

Defined in:
lib/xirr/returns.rb

Overview

Performance and risk metrics: Returns.volatility, Returns.cagr, Returns.payback_period, Returns.discounted_payback_period, Returns.profitability_index, and Returns.twr.

The cash-flow functions follow the same convention as npv: the initial outlay sits at index 0 (undiscounted) and later flows fall at periods 1, 2, ….

Class Method Summary collapse

Class Method Details

.cagr(begin_value, end_value, years, precision: Xirr.config.precision) ⇒ Float

Compound annual growth rate — the constant yearly rate that grows begin_value into end_value over years.

Returns:

  • (Float)


27
28
29
30
31
32
33
# File 'lib/xirr/returns.rb', line 27

def cagr(begin_value, end_value, years, precision: Xirr.config.precision)
  if begin_value <= 0 || years <= 0 || end_value.to_f / begin_value < 0
    raise ArgumentError, 'undefined'
  end

  round_value((end_value.to_f / begin_value)**(1.0 / years) - 1, precision)
end

.discounted_payback_period(cash_flows, rate, precision: Xirr.config.precision) ⇒ Float

Like payback_period, but recovers the outlay from cash flows discounted at rate, so it accounts for the time value of money.

Returns:

  • (Float)


46
47
48
# File 'lib/xirr/returns.rb', line 46

def discounted_payback_period(cash_flows, rate, precision: Xirr.config.precision)
  recovery(discount_flows(cash_flows, rate), precision)
end

.payback_period(cash_flows, precision: Xirr.config.precision) ⇒ Float

Payback period — how many periods of cash flow it takes to recover the initial outlay, interpolating within the recovering period. The first amount is the outlay (negative), the rest are inflows.

Returns:

  • (Float)


39
40
41
# File 'lib/xirr/returns.rb', line 39

def payback_period(cash_flows, precision: Xirr.config.precision)
  recovery(cash_flows, precision)
end

.profitability_index(cash_flows, rate, precision: Xirr.config.precision) ⇒ Float

Profitability index — the present value of future inflows per unit of initial investment, discounted at rate. Above 1 means the project adds value. Equivalent to +1 + NPV / initial investment+.

Returns:

  • (Float)

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
# File 'lib/xirr/returns.rb', line 54

def profitability_index(cash_flows, rate, precision: Xirr.config.precision)
  raise ArgumentError, 'need at least one flow' if cash_flows.empty?
  raise ArgumentError, 'the first flow must be an outlay (negative)' if cash_flows.first >= 0

  npv = Xirr.npv(rate, cash_flows)
  round_value(1 + npv / -cash_flows.first, precision)
end

.twr(period_returns, periods_per_year: nil, precision: Xirr.config.precision) ⇒ Float

Time-weighted return — period returns linked geometrically, +∏(1 + rᵢ) − 1+. Immune to the timing of cash flows. Pass periods_per_year to annualise.

Returns:

  • (Float)

Raises:

  • (ArgumentError)


66
67
68
69
70
71
# File 'lib/xirr/returns.rb', line 66

def twr(period_returns, periods_per_year: nil, precision: Xirr.config.precision)
  raise ArgumentError, 'need at least one return' if period_returns.empty?
  raise ArgumentError, 'returns must be numbers' unless period_returns.all? { |r| r.is_a?(Numeric) }

  round_value(time_weighted(period_returns, periods_per_year), precision)
end

.volatility(prices, periods_per_year: 252, returns: :simple, precision: Xirr.config.precision) ⇒ Float

Annualised volatility of a price series — the sample standard deviation of its period-over-period returns, scaled up by √periods_per_year. Needs at least three positive prices, in time order.

Parameters:

  • returns (:simple, :log) (defaults to: :simple)

    how to measure each return: (b-a)/a or ln(b/a)

Returns:

  • (Float)

Raises:

  • (ArgumentError)


17
18
19
20
21
22
# File 'lib/xirr/returns.rb', line 17

def volatility(prices, periods_per_year: 252, returns: :simple, precision: Xirr.config.precision)
  rets = period_returns(prices, returns)
  raise ArgumentError, 'need at least three prices' if rets.length < 2

  round_value(annualise(rets, periods_per_year), precision)
end