{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# Week 1 Functions and NumPy Arrays\n\nPostgraduate Financial Econometrics Python Bootcamp 2026 27\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Week 0 used individual values and Python lists. This week introduces functions for reusable calculations and NumPy arrays for numerical data. The array ideas developed here lead directly to pandas tables in Week 2.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Functions\n\nA function is a named block of code that performs one task. It is defined once and can then be called with different inputs.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "def square(x):\n    result = x ** 2\n    return result\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Running the definition creates the function but does not run its body. The body runs when the function is called. In the call below, `5` is the argument and `x` is the parameter that receives it.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "answer = square(5)\nanswer\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "A function can accept several inputs. The returned value can be stored and used in another calculation.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "def position_profit(shares, purchase_price, current_price):\n    amount_invested = shares * purchase_price\n    current_value = shares * current_price\n    return current_value - amount_invested\n\nprofit = position_profit(80, 395.00, 418.00)\nprofit\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "`return` sends a result back to the caller. `print()` only displays a value. Functions that calculate values should normally return them.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "def classify_profit(profit):\n    if profit > 0:\n        return \"profit\"\n    elif profit == 0:\n        return \"break even\"\n    else:\n        return \"loss\"\n\nclassify_profit(profit)\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Exercise 6 Write a simple-return function\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The simple return between two prices is calculated as `end_price / start_price - 1`.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a)** Define a function called `simple_return(start_price, end_price)` that returns the simple return.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b)** Use the function to calculate the return when a price rises from 185.64 to 188.63. Store the result as `aapl_return`.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c)** Use the same function to calculate the return when a price falls from 172.27 to 170.94. Store the result as `jpm_return`.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Solution\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a) Define the function**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "def simple_return(start_price, end_price):\n    return end_price / start_price - 1\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b) Calculate the positive return**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "aapl_return = simple_return(185.64, 188.63)\naapl_return\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c) Calculate the negative return**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "jpm_return = simple_return(172.27, 170.94)\njpm_return\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Packages, installation, and importing\n\nPython includes a standard library. Packages such as NumPy and pandas are installed separately. Installation adds a package to the Python environment and normally needs to be done only once. Importing makes an installed package available in the current notebook and is done in each new notebook session.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "If NumPy is missing, run `%pip install numpy` in its own notebook code cell. If the import below works, no installation is needed. The name `np` is the standard short name used for NumPy.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## NumPy arrays\n\nA Python list is a general container. A NumPy array stores values of a common numerical type and applies calculations across the complete array. pandas uses the same ideas of shape, data type, and elementwise operations.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "import numpy as np\n\nprices = np.array([185.64, 187.15, 184.86, 188.63, 191.56])\nprices\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The most useful structural attributes are `ndim`, `shape`, `size`, and `dtype`. A shape of `(5,)` means one dimension containing five values.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "prices.ndim, prices.shape, prices.size, prices.dtype\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "`np.arange()` creates an evenly stepped sequence whose stop value is excluded. `np.linspace()` creates a requested number of evenly spaced values and includes both endpoints.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "integers = np.arange(1, 6)\ngrid = np.linspace(0, 1, 5)\n\nintegers, grid\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Vectorised operations\n\nArithmetic on an array is performed element by element. A single value is applied to every element; this simple case is called broadcasting.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "number_list = [1, 2, 3]\nnumber_array = np.array([1, 2, 3])\n\nnumber_list * 2, number_array * 2\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "purchase_prices = np.array([185.64, 370.87, 172.27])\ncurrent_prices = np.array([188.63, 374.69, 170.94])\nshares = np.array([10, 5, 20])\n\nprofits = shares * (current_prices - purchase_prices)\nprofits\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Quiz 5 Lists and arrays do different things\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Predict the two lines printed:\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "x = [1, 2, 3]\ny = np.array(x)\n\nx = x + [4]\ny = y + 4\n\nprint(x)\nprint(y)\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Quiz answer\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The code prints:\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "```text\n[1, 2, 3, 4]\n[5 6 7]\n```\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Adding two lists concatenates them. Adding the scalar 4 to a NumPy array broadcasts the scalar and adds it to each element.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Indexing, slicing, and Boolean selection\n\nOne-dimensional arrays use the same zero-based indexing and stop-before-the-end slicing rules as Python lists.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "prices[0], prices[-1], prices[1:4]\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Comparing an array with a value creates one Boolean value for each element. Using that Boolean array inside square brackets keeps the matching values.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "mask = (prices >= 185) & (prices <= 190)\nselected_prices = prices[mask]\n\nmask, selected_prices\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "For array conditions, use `&` for elementwise ‘and’ and place each comparison in parentheses. pandas uses the same Boolean-mask pattern to filter rows.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Numerical summaries and mathematical functions\n\nNumPy provides numerical functions that operate directly on an array. Common examples include `mean`, `std`, `sum`, `min`, and `max`.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "mean_price = np.mean(prices)\nstandard_deviation = np.std(prices)\nlowest_price = np.min(prices)\nhighest_price = np.max(prices)\ntotal = np.sum(prices)\n\nmean_price, standard_deviation, lowest_price, highest_price, total\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Vectorisation also combines slices. The two slices below have the same length, so subtraction calculates every consecutive price change at once.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "price_changes = prices[1:] - prices[:-1]\nprice_changes\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "A price change depends on the price level. A simple return measures the change relative to the starting price. A return of `0.01` means 1 per cent.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "simple_returns = prices[1:] / prices[:-1] - 1\nsimple_returns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The same NumPy functions and conditions used for prices can summarise and select returns.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "average_return = np.mean(simple_returns)\nreturn_volatility = np.std(simple_returns)\npositive_returns = simple_returns[simple_returns > 0]\n\naverage_return, return_volatility, positive_returns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Exercise 7 Analyse consecutive returns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The following array contains six consecutive closing prices:\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "closing_prices = np.array([100.0, 102.0, 101.0, 104.0, 103.0, 106.0])\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a)** Create an array called `returns` containing the five simple returns between consecutive closing prices.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b)** Calculate the average return and the standard deviation of the returns.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c)** Create an array called `positive_returns` containing only the positive returns.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Solution\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a) Calculate consecutive returns**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "closing_prices = np.array([100.0, 102.0, 101.0, 104.0, 103.0, 106.0])\nreturns = closing_prices[1:] / closing_prices[:-1] - 1\n\nreturns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b) Summarise the returns**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "average_return = np.mean(returns)\nstandard_deviation = np.std(returns)\n\n(average_return, standard_deviation)\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c) Keep the positive returns**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "positive_returns = returns[returns > 0]\npositive_returns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Exercise 8 Calculate portfolio profit with arrays\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "The three entries in each array refer to the same three investments:\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "shares = np.array([10, 5, 20])\npurchase_prices = np.array([185.64, 370.87, 172.27])\ncurrent_prices = np.array([188.63, 374.69, 170.94])\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a)** Create an array called `position_profits` containing the profit or loss on each investment.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b)** Create an array called `position_returns` containing the simple return on each investment.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c)** Calculate the total profit or loss on the portfolio.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Solution\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(a) Calculate each position**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "shares = np.array([10, 5, 20])\npurchase_prices = np.array([185.64, 370.87, 172.27])\ncurrent_prices = np.array([188.63, 374.69, 170.94])\n\nposition_profits = shares * (current_prices - purchase_prices)\nposition_profits\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(b) Calculate each position's return**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "position_returns = current_prices / purchase_prices - 1\nposition_returns\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**(c) Calculate the portfolio total**\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "total_profit = np.sum(position_profits)\ntotal_profit\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Week 1 takeaways\n\nYou can define and call a function, install and import a package, create and inspect NumPy arrays, apply vectorised arithmetic, calculate simple returns, select values by position or condition, and use NumPy's main numerical functions.\n"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3"
  },
  "financial_econometrics": {
   "academic_year": "2026/27",
   "source": "postgraduate_financial_econometrics.tex",
   "week": 1,
   "solutions_included": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
