I am trying to calculate the breakeven price for a given IRR and getting stuck in scope's root solver. A simple example:
n = 10. # no of periods
years = np.arange(n) + 1
initial_investment = [-1000]
quantity_sold = np.full(n, 20)
price = np.full(n, 20)
revenue = quantity_sold * price
expense = np.full(n, 50)
cash_flow = np.concatenate([initial_investment, revenue - expense])
With this we can calculate the IRR and verify that we get NPV of 0 using numpy_financial
:
npf.irr(cash_flow), npf.npv(0.32975, cash_flow)
# (0.32975, 0.008)
What I want to do is the opposite: say, given an IRR of 40%, what is the cash_flow needed for break-even price (NPV = 0).
I am using scipy.optimize.root
but getting lost as how to structure that. I can invert npf.npv
to get IRR:
optimize.root(npf.npv, args=cash_flow, x0=0.5)
but cash_flow is an array, even allowing for the values of that array to be constant. How would I solve for cash_flow
given an IRR?
It's certainly possible, but it's not very meaningful unless you further constrain the problem (by, for example, fixing the initial investment). Otherwise, the system is underdetermined:
import numpy as np
import scipy.optimize
n = 10 # no of periods
years = np.arange(1, 1 + n)
initial_investment = -1000
quantity_sold = np.full(shape=n, fill_value=20)
price = np.full(shape=n, fill_value=20.)
revenue = quantity_sold * price
expense = np.full(shape=n, fill_value=50.)
cash_flow = np.concatenate(((initial_investment,), revenue - expense))
# Forward calculation of IRR, shown without use of `npf`
def npv(rate: float) -> float:
return (cash_flow / (1 + rate)**np.arange(cash_flow.size)).sum()
result = scipy.optimize.root_scalar(f=npv, x0=0.5, bracket=(0, 1))
assert result.converged
irr = result.root
assert np.isclose(0.32975, irr, atol=0, rtol=1e-5)
np.isclose(0, npv(irr), rtol=0, atol=1e-12)
print('irr =', irr)
# Back-calculation of cash flow, again without `npf`
def npv_from_series(cash_flow: np.ndarray) -> float:
return (cash_flow / (1 + irr)**np.arange(cash_flow.size)).sum()
result = scipy.optimize.least_squares(
fun=npv_from_series,
x0=np.concatenate(((-1,), np.ones(n))),
bounds=scipy.optimize.Bounds(
lb=np.concatenate(((-np.inf,), np.zeros(n))),
ub=np.concatenate(((0,), np.full(shape=n, fill_value=np.inf))),
),
)
assert result.success
cash_flow = result.x
print(f'Cash flow for IRR of {irr:.1%}:')
print(cash_flow)
print(f'NPV error: {result.fun[0]:.2e}')
irr = 0.3297531334357468
Cash flow for IRR of 33.0%:
[-3.32043506 0.99765753 1.06177051 1.11807742 1.17345293 1.23855932
1.33771217 1.5628038 3.13235102 0.0914474 0.674785 ]
NPV error: 1.80e-16
If you do fix the initial investment, it's still underdetermined, but at least tends to have a sensible scale:
# Back-calculation of cash flow, again without `npf`
def npv_from_series(profit: np.ndarray) -> float:
cash_flow = np.concatenate(((initial_investment,), profit))
return (cash_flow / (1 + irr)**np.arange(cash_flow.size)).sum()
result = scipy.optimize.least_squares(
fun=npv_from_series,
x0=np.ones(n),
bounds=scipy.optimize.Bounds(lb=np.zeros(n)),
)
assert result.success
profit = result.x
print(f'Profit series for IRR of {irr:.1%} and investment of ${-initial_investment:.2f}:')
print(profit)
print(f'NPV error: {result.fun[0]:.2e}')
irr = 0.3297531334357468
Profit series for IRR of 33.0% and investment of $1000.00:
[577.58634499 435.70855853 328.53386135 247.66502124 186.69665115
140.76042784 106.16648717 80.12334345 60.5226813 45.77384425]
NPV error: -2.49e-14
If you aggressively constrain the problem by assuming constant profit, the profit can be found analytically:
constant_profit = -initial_investment/(
(1 + irr)**np.arange(-1, -1-n, -1)
).sum()
print(f'For IRR of {irr:.1%} and investment of ${-initial_investment:.2f}, '
f'assumed-constant profit of ${constant_profit:.2f} over {n} years')
For IRR of 33.0% and investment of $1000.00, assumed-constant profit of $350.00 over 10 years
Yet another constrained solution is to fix the NPV terms to a constant value. This makes the profit a geometric series whose compound growth equals the IRR:
profit = -initial_investment/n*(1 + irr)**np.arange(1, 1+n)
print(f'For IRR of {irr:.1%} and investment of ${-initial_investment:.2f}, '
f'constant-term profit over {n} years is')
cash_flow = np.concatenate(((initial_investment,), profit))
print(profit)
print(f'NPV error: {npv(irr):.2e}')
For IRR of 33.0% and investment of $1000.00, constant-term profit over 10 years is
[ 132.97531334 176.82433959 235.13271964 312.66847071 415.77187865
552.87395843 735.18587862 977.61572575 1299.98757461 1728.66255077]
NPV error: 0.00e+00