公示是写的这段代码:{
"cells": [
{
"cell_type": "markdown",
"id": "267c9a27-fcec-4e35-9920-b28d1b244879",
"metadata": {},
"source": [
"# Problem Set 1\n",
"\n",
"This is the first homework assignment, which accounts for $30\\%$ of your final grade. There are two questions, and their weights are:\n",
"* Q1: $75\\%$ ($25\\% \\times 3$),\n",
"* Q2: $25\\%$.\n",
"\n",
"You may work with other students. The maximum number of students per group is two. However, you can work on your own. Be sure to indicate with whom you have worked in your submission.\n",
"\n",
"### Deadline: Nov 14, 2025 (5 PM HK Time). \n",
"\n",
"There is a penalty for late submissions: $5\\%$ will be subtracted from the total mark for every additional day after the deadline. "
]
},
{
"cell_type": "markdown",
"id": "99230c5e-5ce4-47c4-8030-3da66f532fe9",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cba9bc45-4c53-47aa-b714-a4d25fef1d7c",
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import sqlite3\n",
"plt.rc('figure', figsize=(8, 4))\n",
"PREVIOUS_MAX_ROWS = pd.options.display.max_rows\n",
"pd.options.display.max_rows = 15\n",
"np.set_printoptions(precision=4, suppress=True)"
]
},
{
"cell_type": "markdown",
"id": "2a0f53f5-7626-46f3-86af-95d4804c4b7b",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "a06dbcb0-18fe-4128-bf43-9c60e6824236",
"metadata": {},
"source": [
"## Q1. Modelling Stock Prices via Generalized Random Walks"
]
},
{
"cell_type": "markdown",
"id": "031c9fc5-22fd-43e7-8c91-8b6e975da963",
"metadata": {},
"source": [
"In the in-class exercise of lecture 5, we twist the simple walk to model stock returns/prices. Details can be found in the lecture notes. The function ```stock_price_simulations``` and the related model parameters are given below:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1615be14-9440-40d6-9c18-0d905828d66e",
"metadata": {},
"outputs": [],
"source": [
"def stock_price_simulations(P0, T, delta_t, mu, sigma, nwalks):\n",
" \"\"\"\n",
" P0: the current stock price;\n",
" T: time-to-maturity;\n",
" delta_t: the time step, double numeric (we choose delta_t such that T/delta_t is an integer);\n",
" mu, sigma: mean and standard deviation of the stock return, double numeric;\n",
" nwalks: the number of stock return random walks, integer;\n",
" Return: a two-dimensional np.array, with each row storing one draw of stock price\n",
" random walk with nsteps. \n",
" \"\"\"\n",
" \n",
" nsteps = int(T/delta_t) # the number of steps in each stock return random walk, integer;\n",
" draws = np.random.randint(0, 2, size=(nwalks, nsteps))\n",
" steps = np.where(draws > 0, 1, -1)\n",
" returns = steps*sigma*np.sqrt(delta_t) + mu*delta_t\n",
" P1 = P0 * (1+returns).cumprod(axis=1)\n",
" return P1\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34cb62f0-7182-4148-8450-db071a89ee1d",
"metadata": {},
"outputs": [],
"source": [
"### Please don't change the parameter values\n",
"T = 1 # time to maturity\n",
"mu = 0.2 # annualised average return\n",
"sigma = 0.2 # annualised volatility\n",
"r = 0.03 # annualised risk-free rate\n",
"P0 = 50 # current stock price\n",
"delta_t = 1 / 2520\n",
"nwalks = 10000 # the number of random walks"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "173eb2cb-3cb5-4bca-9302-29531f246bf8",
"metadata": {},
"outputs": [],
"source": [
"np.random.seed(12345)\n",
"P1_sim = stock_price_simulations(P0, T, delta_t, mu, sigma, nwalks)\n",
"print(P1_sim.shape)"
]
},
{
"cell_type": "markdown",
"id": "832247f3-2497-4c58-9b3c-8f7e855dd779",
"metadata": {},
"source": [
"### Q1.1"
]
},
{
"cell_type": "markdown",
"id": "069fec17-f217-4db5-8750-4ca2c117ba2f",
"metadata": {},
"source": [
"There are 10,000 simulation paths in ```P1_sim```, each of which has 2520 steps. Based on the simulated stock prices in ```P1_sim```, what is the probability of the terminal stock price being less than 60 but larger than 40? (Hint: You need to count the percentages of stock prices in the final trading period being less than 60 but larger than 40).\n",
"\n",
"Second, how does the probability of the terminal stock price being less than 60 but larger than 40 depend on the values of ```mu``` and ```sigma```? To answer this question, you need to consider a variety of ```mu``` and ```sigma```, as follows:\n",
"```python\n",
"mu_seq = np.arange(-0.20, 0.22, 0.02)\n",
"sigma_seq = np.arange(0.10, 0.32, 0.02)\n",
"```\n",
"\n",
"For each pair of ```mu``` and ```sigma``` in ```mu_seq``` and ```sigma_seq```, you need to estimate the probability of the terminal stock prices being less than 60 but larger than 40. Based on your estimates, please briefly describe how the values of ```mu``` and ```sigma``` determine the probability that the stock price is less than 60 but larger than 40 in the final trading period. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fb8c11d-22d6-40c3-9d64-4b7211c64769",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "998972d8-00e4-4a01-bf0f-b9ccb74be31c",
"metadata": {},
"source": [
"### Q1.2 Newton-Raphson Algorithm to Find the implied volatility\n",
"\n",
"The Black-Scholes-Merton formula of the European call option is given as follows: <br>\n",
"<br>\n",
"\\begin{equation}\n",
"c(P_t, K, \\sigma, r, T-t) = P_t \\cdot N(d_1) - e^{-r (T-t)} K N(d_2)\n",
"\\label{eq:euro_call_BS} \\tag{1}\n",
"\\end{equation} \n",
"<br>\n",
"where \n",
"- $P_t$ is the stock price at time $t$,\n",
"- $K$ is the strike price of the European call option,\n",
"- $\\sigma$ is the volatility of the stock return,\n",
"- $r$ is the risk-free rate,\n",
"- $T-t$ is the time-to-maturity, and \n",
"- $N(\\cdot)$ is the cumulative distribution function of the standard normal ditribution, \n",
"\n",
"\\begin{equation}\n",
"d_1 = \\frac{\\ln(\\frac{P_t}{K}) + (r + \\frac{1}{2} \\sigma^2)(T-t)}{\\sigma \\sqrt{T-t}}, \\ \n",
"d_2 = d_1 - \\sigma \\sqrt{T-t}.\n",
"\\label{eq:euro_call_BS_d} \\tag{2}\n",
"\\end{equation}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7055b1bc-995d-4cad-9cab-664402733f97",
"metadata": {},
"outputs": [],
"source": [
"from scipy.stats import norm\n",
"\n",
"def euro_call_BS(P0, r, T, sigma, K):\n",
" \"\"\"\n",
" P0: current asset price;\n",
" r: (annualized) risk-free rate;\n",
" T: time to maturity;\n",
" sigma: volatility of asset returns;\n",
" K: strike price;\n",
" \n",
" Return the price of European call option implied by Black-Scholes Formula.\n",
" \"\"\"\n",
" \n",
" d1 = (np.log(P0/K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))\n",
" d2 = d1 - sigma * np.sqrt(T)\n",
" return(P0*norm.cdf(d1) - np.exp(-r*T)*K*norm.cdf(d2))\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4af8ffc-c2c7-4b3e-a1a4-1db570435d64",
"metadata": {},
"outputs": [],
"source": [
"K = 50\n",
"price_bs = euro_call_BS(P0, r, T, sigma, K)\n",
"print(\"Black-Scholes price of European call option is\", \"%.4f\" % price_bs)"
]
},
{
"cell_type": "markdown",
"id": "7a3742bf-03e1-46d2-be65-8d658a77ce62",
"metadata": {},
"source": [
"In lecture 5, we learned how to use the function ```euro_call_BS``` given above to compute the fair value of a European call option, given the parameter values of ```(P0, r, T, sigma, K)```. However, in financial markets, we often observe ```(P0, r, T, K)``` and the option price, and the stock volatility is reverse-engineered using the Black-Scholes-Merton formula. Note that the Black-Scholes-Merton formula is a nonlinear function of ```sigma```, and no close-form solution is provided. In this subquestion, we rely on the ***Newton-Raphson*** algorithm to back out ```sigma``` (Details about the Newton-Raphson algorithm can be found in ***Problem Set 0, question 4***).\n",
"\n",
"You may need to use the options greeks: https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model#The_Options_Greeks. "
]
},
{
"cell_type": "markdown",
"id": "8b19647b-904e-44e1-8298-84ebec389fb4",
"metadata": {},
"source": [
"In particular, you need to create the following function to estimate the volatility parameter ```sigma``` given the call option price and other model parameters:\n",
"```python\n",
"def implied_vol_newton(call_price, P0, r, T, K, epsilon, max_steps=1000):\n",
" \"\"\"\n",
" call_price: the market price of a European call option; \n",
" P0: current asset price;\n",
" r: (annualized) risk-free rate;\n",
" T: time to maturity;\n",
" K: strike price;\n",
" max_steps int, epsilon > 0. \n",
" Returns the implied volatility of a European call option based on the Newton-Raphson algorithm.\n",
" If such a float does not exist (the number of loops is more than max_steps), it returns None. \n",
" \"\"\"\n",
" pass\n",
"```\n",
"\n",
"After you create the above function, please execute the following codes to find out the implied volatility:\n",
"```python\n",
"call_price = 4.7067\n",
"implied_vol_newton(call_price, P0, r, T, K, epsilon=0.001, max_steps=1000)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "48fa9994-56fc-4a06-be61-8d25383a63c0",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "ddf0c0d0-ba2a-4e27-a93e-b2c4a4daa5af",
"metadata": {},
"source": [
"### Q1.3 Market price of an exotic option in the Black-Scholes-Merton economy\n",
"\n",
"In this question, you are asked to price an exotic option. The payoff structure of this option is as follows: When the market price at the maturity date (time $T$) is between 40 and 60, the payoff is equal to one. However, if the market price is higher than 60 or lower than 40 at the maturity date, the payoff is zero. What is the fair market value of this exotic option in the Black-Scholes-Merton economy? \n",
"\n",
"Remember that in the risk-neutral pricing, the fair price of this exotic option with strike price $K$ is equal to \n",
"\n",
"$$\n",
"C(t, S_t) = \\exp \\{ - r (T-t) \\} \\cdot \\mathbb{E}^Q_t [ 1_{K_1 < P_T < K_2} ], \n",
"$$ where $P_T$ is equal to $P_T = P_t \\cdot R_{t \\to T}$, and $1_{K_1 < P_T < K_2}$ is an indicator function that equals one if the terminal stock price $P_T$ is between $K_1$ and $K_2$ and zero otherwise. \n",
"\n",
"To approximate $\\mathbb{E}^Q_t [ 1_{K_1 < P_T < K_2} ]$, we need to draw a huge amount of simulation paths of terminal stock prices. We denote the simulated outcomes of stock price as $\\{P_T (\\omega_j) \\}_{j=1}^{M}$, then\n",
"$$\n",
"\\mathbb{E}^Q_t [ \\max\\{ P_T - K, 0 \\} ] \\approx \\frac{1}{M} \\sum_{j=1}^M 1_{K_1 < P_T (\\omega_j) < K_2}.\n",
"$$\n"
]
},
{
"cell_type": "markdown",
"id": "d404b9f6-5e93-4cc1-acfa-f7313a0b420a",
"metadata": {},
"source": [
"You need to create the function ```exotic_call_MC``` to estimate the fair value of the exotic option based on Monte-Carlo simulations:\n",
"```python\n",
"def exotic_call_MC(P0, K1, K2, T, delta_t, mu, sigma, nwalks):\n",
" \"\"\"\n",
" P0: the current stock price;\n",
" K1, K2: the strike prices;\n",
" T: time-to-maturity;\n",
" delta_t: the time step, double numeric;\n",
" mu, sigma: mean and standard deviation of the stock return, double numeric;\n",
" nwalks: the number of stock price random walks, integer. \n",
" The payoff of this exotic option is 1 when terminal stock price is between K1 and K2 and zero otherwise.\n",
" Return: the approximated price of the exotic option based on Monte-Carlo simulations.\n",
" \"\"\"\n",
" pass\n",
"```\n",
"\n",
"After creating the function ```exotic_call_MC```, please execute the following codes:\n",
"```python\n",
"K1 = 40\n",
"K2 = 60\n",
"print(\"Option price based on Monte-Carlo simulations is\", \n",
" \"%.4f\" % exotic_call_MC(P0, K1, K2, T, delta_t, mu, sigma, nwalks))\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "464b006b-5d8e-4c76-8c12-1bcaef8ed79a",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "80348e9c-d6e7-460e-9e49-6a7fa2519489",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "d25d61d2-3986-4662-a4c8-c194c13b6306",
"metadata": {},
"source": [
"## Q2. Using SQL ```SELECT``` Statement\n",
"\n",
"In this question, you will use the **SQL** (more precisely, ```SQLite3```) statements to explore the database. "
]
},
{
"cell_type": "markdown",
"id": "dedc98f7-cdc5-41ce-8ad0-759f5230bc83",
"metadata": {},
"source": [
"### Q2.1\n",
"\n",
"First, you need to download ```\"ps1_stocks.db\"```, using the following Dropbox link: https://www.dropbox.com/scl/fi/ujplikyo63wddn717xkgr/ps1_stocks.db?rlkey=kfh251d49i5mbbfoxlp8brwnv&st=rgig6w8n&dl=0.\n",
"\n",
"There are three tables in this dataset. \n",
"* Table 'stock_returns': contains four variables, ```['key', 'id', 'eom', 'ret_exc_lead1m']```;\n",
"* Table 'st_reversal_signals': contains nine variables ```['key', 'id', 'eom', 'iskew_capm_21d', 'iskew_ff3_21d', 'iskew_hxz4_21d', 'ret_1_0', 'rmax5_rvol_21d', 'rskew_21d']```;\n",
"* Table 'quality_signal': contains eight variables ```['key', 'id', 'eom', 'at_turnover', 'cop_at', 'cop_atl1', 'dgp_dsale', 'gp_at']```, \n",
"\n",
"where the variable ```'key'``` is the primary key of the dataset. "
]
},
{
"cell_type": "markdown",
"id": "230e4d63-c596-40d0-a873-df0ebf32eeff",
"metadata": {},
"source": [
"After you create the link to ```\"ps1_stocks.db\"``` database, you are asked to execute SQL statements to get the table information as follows:\n",
"```python\n",
"Information about table stock_returns:\n",
"\n",
"[(0, 'key', 'INTEGER', 0, None, 0),\n",
" (1, 'id', 'REAL', 0, None, 0),\n",
" (2, 'eom', 'TIMESTAMP', 0, None, 0),\n",
" (3, 'ret_exc_lead1m', 'REAL', 0, None, 0)]\n",
"\n",
"Information about table st_reversal_signals:\n",
"[(0, 'key', 'INTEGER', 0, None, 0),\n",
" (1, 'id', 'REAL', 0, None, 0),\n",
" (2, 'eom', 'TIMESTAMP', 0, None, 0),\n",
" (3, 'iskew_capm_21d', 'REAL', 0, None, 0),\n",
" (4, 'iskew_ff3_21d', 'REAL', 0, None, 0),\n",
" (5, 'iskew_hxz4_21d', 'REAL', 0, None, 0),\n",
" (6, 'ret_1_0', 'REAL', 0, None, 0),\n",
" (7, 'rmax5_rvol_21d', 'REAL', 0, None, 0),\n",
" (8, 'rskew_21d', 'REAL', 0, None, 0)]\n",
"\n",
"Information about table quality_signals:\n",
"[(0, 'key', 'INTEGER', 0, None, 0),\n",
" (1, 'id', 'REAL', 0, None, 0),\n",
" (2, 'eom', 'TIMESTAMP', 0, None, 0),\n",
" (3, 'at_turnover', 'REAL', 0, None, 0),\n",
" (4, 'cop_at', 'REAL', 0, None, 0),\n",
" (5, 'cop_atl1', 'REAL', 0, None, 0),\n",
" (6, 'dgp_dsale', 'REAL', 0, None, 0),\n",
" (7, 'gp_at', 'REAL', 0, None, 0)]\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72e8f643-1689-49de-903f-d7576b2439f2",
"metadata": {},
"outputs": [],
"source": [
"conn = sqlite3.connect('data/ps1_stocks.db')\n",
"c = conn.cursor()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a8ecd34-f591-417a-bec0-3196d16c183e",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "63a421e9-a530-4b1e-a9d2-bd31b4143e5d",
"metadata": {},
"source": [
"### Q2.2\n",
"\n",
"Select the stock-month observations from the ```'stock_returns'``` table that satisfy the following requirements:\n",
"* Select ```'id'```, ```'eom'```, and ```'ret_exc_lead1m'```\n",
"* The stock return should range from $-0.03$ to $0.03$ and is NOT missing\n",
"* The stock id contains ```222```\n",
"* Sort the data by ```eom``` in the descending order and by ```id``` in the ascending order\n",
"* Display the first 5 rows of the sorted data. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ade1739f-3f66-4c25-8bd8-eb787993b7fb",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "8d8180ae-7024-43f5-84a0-99dbfda56db2",
"metadata": {},
"source": [
"### Q2.3\n",
"\n",
"Compute the average, maximal, and minimal asset turnovers (```'at_turnover'```) for the stocks that satisfy the following requirements: \n",
"* Report ```id``` and create three new variables (```mean_at_turnover```, ```max_at_turnover```, and ```min_at_turnover```) to denote the average, maximal, and minimal returns for selected stocks\n",
"* The asset turnover should NOT be missing\n",
"* The number of time-series observations is greater than 360."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "76b8c0fd-eae7-4491-9ed0-5d816aa42c9f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "db46d4d2-2dfb-460b-873c-ddef5f20f372",
"metadata": {},
"source": [
"### Q2.4\n",
"\n",
"In this question, you are asked to extract \n",
"* ```iskew_capm_21d``` and ```iskew_ff3_21d``` in table ```st_reversal_signals```,\n",
"* ```at_turnover``` and ```gp_at``` in table ```quality_signals```,\n",
"* ```ret_exc_lead1m``` in the ```stock_returns``` table,\n",
"* and ```key```, ```id```, ```eom``` in all three tables. \n",
"\n",
"Moreover, we require that there is no missing value in ```ret_exc_lead1m```, ```at_turnover```, ```gp_at```, ```iskew_capm_21d```, and ```iskew_ff3_21d```."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7138ac4-dacf-4e64-9978-61fa55f5d192",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "c9049f98-ed59-49e6-bd2b-ffa1328ba6b7",
"metadata": {},
"source": [
"### Q2.5\n",
"\n",
"Compute the average returns per period (grouped by ```eom```) that satisfy the following requirements: \n",
"* Report ```eom``` and create a new variable ```mean_ret``` to denote the average returns for selected observations\n",
"* The observations should satisfy ```iskew_capm_21d > 0.5``` and ```at_turnover > 0.1```\n",
"* Sorted by ```eom``` in the descending order."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "73c7b813-298d-482d-b9f6-ddb2942884ac",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd130036-a94b-4c64-8f8a-9a3ea4ba06e4",
"metadata": {},
"outputs": [],
"source": [
"conn.close()"
]
},
{
"cell_type": "markdown",
"id": "916f359e-55e5-4166-b36f-410874a0a50b",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "0fa62f21-71e5-461e-9378-a12ed14986f9",
"metadata": {},
"source": [
"# END"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}