More Functions

Plan Ahead

Functions have the potential to create immense utility for the end user. A very well constructed function can save you hours (if not days or weeks) of time down the road. However, to be useful, the function needs to be planned carefully. If the function is written well, then you can use it conveniently in the future without worrying about the details. However, if the function is written poorly, it could end up creating a huge headache for you when you try to use it later.

Keeping to the wacc() example, let’s consider some simple improvements to the function.

Imagine that we plan to use this wacc() function to value companies, using data on companies that will be obtained in some other piece of code. Thus, we collect all the requisite components to calculating a weighted average cost of capital, as well as information about the current free cash flow and an expected growth rate to the free cash flow. For simplicity, assume that free cash flow growth and the risk free rate of capital are constant. In this scenario, we can apply the Gordon growth formula to value the company.

\[ V = \frac{FCF}{r_{WACC}-g} \]
def wacc(E, D, rE, rD, tC):
    V = E + D
    cost_of_capital = E / V * rE + D / V * rD * (1-tC)
    return cost_of_capital, V
2/(wacc(E=100,D=30,rE=.08,rD=.04,tC=.35) - 0.01) # $2M in FCF, growing at 1%/year
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-0cbe3844b6b4> in <module>
----> 1 2/(wacc(E=100,D=30,rE=.08,rD=.04,tC=.35) - 0.01) # $2M in FCF, growing at 1%/year

TypeError: unsupported operand type(s) for -: 'tuple' and 'float'

A limitation of the wacc() function is that it assumes all inputs are sensible. Suppose an innocent typo places a negative sign in front of the value for rE. In this case, form value can end up a negative number!

2/(wacc(E=100,D=30,rE=-.08,rD=.04,tC=.35) - 0.01)
-30.516431924882625

One can look at this and see that there is a problem; firm value cannot be negative. A scarier example is one in which the accidental negative shows up in front of the value for rD. This latter case is worse because it’s not as obvious that there is an issue (firm value is still calculated to be positive).

2/(wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01)
43.91891891891892

These sorts of small problems can create huge issues for you or your company. In this working example, the typo over-values the firm by:

value_wrong = 2/(wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01)
value_right = 2/(wacc(E=100,D=30,rE=.08,rD=.04,tC=.35) - 0.01)
value_wrong / value_right - 1
0.2635135135135136

Therefore, when we write the wacc() function, we should take care to watch out for anticipable errors and deal with them.

One option is to simply print out a warning message if the function detects something fishy.

def better_wacc(E, D, rE, rD, tC):
    
    if rE <= 0:
        print('rE is not positive')
    if rD <= 0:
        print('rD is not positive')
    if tC <= 0:
        print('tC is not positive')
    
    V = E + D
    cost_of_capital = E / V * rE + D / V * rD * (1-tC)
    return cost_of_capital

2/(better_wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01)
Warning! rD is not positive
43.91891891891892

This at least generates a warning message, but warnings can only go so far. There is a very real possibility that someone using this function may not notice the warning message and consequently carry on with their analysis despite having an erroneously calculated firm value.

If you are certain something is absolutely not an acceptable input, stop it! Do not let the function continue operating on bad data. The way to do this is with an Exception.

def better_wacc(E, D, rE, rD, tC):
    
    if rE <= 0:
        raise Exception('rE is not positive')
    if rD <= 0:
        raise Exception('rD is not positive')
    if tC <= 0:
        raise Exception('tC is not positive')
    
    V = E + D
    cost_of_capital = E / V * rE + D / V * rD * (1-tC)
    return cost_of_capital

2/(better_wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01)
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-27-256c67bfb8c0> in <module>
     12     return cost_of_capital
     13 
---> 14 2/(better_wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01)

<ipython-input-27-256c67bfb8c0> in better_wacc(E, D, rE, rD, tC)
      4         raise Exception('Warning! rE is not positive')
      5     if rD <= 0:
----> 6         raise Exception('Warning! rD is not positive')
      7     if tC <= 0:
      8         raise Exception('Warning! tC is not positive')

Exception: Warning! rD is not positive

Exception messages spit out lots of text, so they can be somewhat intimidating to approach at first. Python will print out a traceback of what code ran that ultimately resulted in an error. The easiest way to get a quick picture of what happened is to follow the ---> symbols, reading from top to bottom. The first such symbol indicates that line 14 was called. The second such symbol tells us that line 6 was called. The actual error message is the final line of the printout. From this brief inspection, we can figure out what went wrong. Notice that line 6 can only be called if line 5 is true. Hence, the root of our problem is that line 5 is true, indicating that rD is entered as a negative number.

The if statements added to better_wacc() cause the funcion to “break” (raise an error message and stop the code from continuing) under certain scenarios. To see that the code completely terminantes when an exception is raised, try the following.

print( 2/(better_wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01) )
print('firm value is now calculated')
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-28-a502cbe6c27d> in <module>
----> 1 print( 2/(better_wacc(E=100,D=30,rE=.08,rD=-.04,tC=.35) - 0.01) )
      2 print('firm value is now calculated')

<ipython-input-27-256c67bfb8c0> in better_wacc(E, D, rE, rD, tC)
      4         raise Exception('Warning! rE is not positive')
      5     if rD <= 0:
----> 6         raise Exception('Warning! rD is not positive')
      7     if tC <= 0:
      8         raise Exception('Warning! tC is not positive')

Exception: Warning! rD is not positive

Note that the second print statement is not executed. Python halts all operations when an exception occurs.

Do not insert exceptions to your functions unnecessarily. Only rely on financial principles to make these judgements. It is obvious, for example, that costs of capital should not be negative. Moreover, the second Modigliani and Miller proposition tells us that rE should always be at least as high as rD, so we could raise an exception if rE <= rD. However, there is no strict rule, either by regulation or by principles of finance, that debt value cannot exceed equity value. For some smaller firms, it may very well be that leverage exceeds 50%. However, if you write the better_wacc() function and anticipate that it will only be used on large, S&P 500 companies, it may make sense to check whether E > D. In this case, raising an exception would not be appropriate. However, since we would generally expect that equity value should exceed debt value, it may make sense to print a warning message for the user.

def better_wacc(E, D, rE, rD, tC):
    
    if rE <= 0:
        raise Exception('rE is not positive')
    if rD <= 0:
        raise Exception('rD is not positive')
    if tC < 0:
        raise Exception('tC is negative')
    if rE <= rD:
        raise Exception('rE <= rD, M&M prop2 violated')
    if D > E:
        print('Warning, debt value exceeds equity value.  Did you accidentally flip these values?')
    
    V = E + D
    cost_of_capital = E / V * rE + D / V * rD * (1-tC)
    return cost_of_capital

Concept check: Use the better_wacc() function to estimate WACC given the following parameter values.

  • E = 10

  • D = 100

  • rE = .15

  • rD = .05

  • tC = .35

By the way, does the gap between rE and rD make senese to you? Hint: think about the levels of E and D.