What is a function?

Give a calculation a name with def, feed it inputs, and get an answer back.

A function is a calculation with a name. You write it once with def, hand it inputs, and return hands an answer back.

In this lesson I build a return calculator, a pair of portfolio functions that call each other, and a profit function with a default argument.

Step 1. Name a calculation

def starts the function, the names in the brackets are the inputs, and the indented lines below are the body. return says what comes out.

def daily_return(prev, curr):
    """The simple return from prev to curr, as a decimal."""
    return (curr - prev) / prev


print(daily_return(100, 110))              # -> 0.1
print(daily_return(110, 99))               # -> -0.1
print(f"{daily_return(150, 168.75):.2%}")  # -> 12.50%
0.1
-0.1
12.50%

prev and curr are parameters. They are empty names until you call the function, then they hold what you passed in. The first call sets prev = 100 and curr = 110.

Running def on its own prints nothing. Only the call produces a value:

def daily_return(prev, curr):
    return (curr - prev) / prev          # defining it produces no output


r = daily_return(100, 110)               # calling it produces a value
print(r)                                 # -> 0.1
0.1

Step 2. Without return you get None

return sends the answer back. Drop it and the function runs and hands back None.

def gain(prev, curr):
    curr - prev                          # computed, then thrown away


print(gain(100, 110))                    # -> None


def gain(prev, curr):
    return curr - prev                   # now it comes back


print(gain(100, 110))                    # -> 10
None
10

The same thing happens on a book of shares.

prices = [185.40, 410.20, 128.75]
shares = [10, 4, 20]


def book_value(prices, shares):
    sum(p * n for p, n in zip(prices, shares))   # added up, then thrown away


print(book_value(prices, shares))                # -> None


def book_value(prices, shares):
    return sum(p * n for p, n in zip(prices, shares))


print(book_value(prices, shares))                # -> 6069.8
None
6069.8

Step 3. A function can call another function

Inside a function body you can call any other function. Here weights calls portfolio_value to get the total, then divides each position by it.

def portfolio_value(prices, shares):
    """Cash value of the whole book."""
    return sum(p * n for p, n in zip(prices, shares))


def weights(prices, shares):
    """Each position as a share of the total."""
    total = portfolio_value(prices, shares)            # calling the function above
    return [p * n / total for p, n in zip(prices, shares)]


prices = [185.40, 410.20, 128.75]
shares = [10, 4, 20]

print(round(portfolio_value(prices, shares), 2))       # -> 6069.8
print([round(w, 3) for w in weights(prices, shares)])  # -> [0.305, 0.27, 0.424]
6069.8
[0.305, 0.27, 0.424]

The three positions are worth 1854.00, 1640.80 and 2575.00, so the weights add to 1.

Step 4. Default arguments

Put = after a parameter and it gets a value to fall back on. Callers can skip it.

def pnl(buy, sell, shares, cost_bps=0):
    """Profit on a round trip, after cost_bps charged on both legs."""
    gross = (sell - buy) * shares
    cost  = (buy + sell) * shares * cost_bps / 10_000   # 1 bp = 0.01% = 1/10_000
    return gross - cost


print(pnl(150, 168.75, 40))                # -> 750.0     <- cost_bps falls back to 0
print(pnl(150, 168.75, 40, cost_bps=10))   # -> 737.25    <- 10 bp on 150 and on 168.75
750.0
737.25

Naming the argument at the call site, cost_bps=10, drops the need to remember the parameter order.

Your turn

Write net_return(prev, curr, cost_bps=0). It calls daily_return from Step 1 and subtracts the cost, expressed as a decimal. Check net_return(100, 110, cost_bps=20) gives 0.098.

def net_return(prev, curr, cost_bps=0):
    gross = daily_return(prev, curr)       # reuse the Step 1 function
    return gross - cost_bps / 10_000


print(round(net_return(100, 110), 4))                # -> 0.1
print(round(net_return(100, 110, cost_bps=20), 4))   # -> 0.098