# Value-at-Risk for Stocks: Boudoukh-Richardson-Whitelaw (BRW) Approach

### Lecture Notes by Jakov Ivan S. Dumbrique (jdumbrique@ateneo.edu)

MATH 100.2: Topics in Financial Mathematics II \
First Semester, S.Y. 2021-2022 \
Ateneo de Manila University


```python
import numpy as np # Numerical Computing
import pandas as pd # Data wrangling
```

## Introduction to BRW Approach
The historical simulation approach assumes that historical returns are uniformly distributed and so quantiles are estimated based on the empirical distribution of returns.

Limitations of the historical simulation approach:

1. Extreme percentiles of the distribution are difficult to estimate with a small amount of data.
2. Returns are assumed to be i.i.d. and hence does not allow for time-varying volatility.

In an effort to address these limitations, we may extend the historical window to include more observations. However, longer historical windows diminish the weight assigned to more recent market phenomenon.

The BRW/Hybrid approach entails the use of exponentially-declining weights on historical data to estimate percentiles, with bigger weights assigned to more recent data.

The empirical distribution of historical returns now rely on the assigned weights. Quantiles are then calculated based on the cumulative weights after ordering historical returns in increasing order.

## Step 1: Assignment of Weights 

Let $\{R_0,R_1,\dots,R_{M-1}\}$ denote the observed one-day log-returns on the stock or portfolio, where $R_i$ is the one-day log-return observed $i$ days ago.
	
Let $0<\lambda<1$ be constant and let $w_i$ be the weight assigned to $R_i$, $i=0,1,\dots,M-1$. The weights decay exponentially, so we assume that $w_{i+1}=\lambda w_i$. To ensure that the weights sum to 1, we have
	\begin{equation}
	w_i = \left(\frac{1-\lambda}{1-\lambda^M}\right)\lambda^i, \qquad i=0,1,\dots,M-1.
	\end{equation}

Consider the single-asset case. Note that each historical return can be mapped to a scenario for the change in portfolio value $$\Delta P^{(i)} \approx NS_0 R_i,$$ where $N$ is the number of shares of the stock, $S_0$ is the current price of the stock per share, and $R_i$ is the one-day log-return observed $i$ days ago.
	
The weights $w_0, w_1,\dots w_{M-1}$ therefore also correspond to the collection $\Delta P^{(0)}, \Delta P^{(1)},\dots\Delta P^{(M-1)}$, the scenario space for the random variable $\Delta P$.



```python
# These are from our programming session on HS
def get_return(df, d):
    """
    df is the original df
    
    appends returns series to df
    """
    df["previous"] = df["close"].shift(-d)
    df["return"] = np.log(df["close"]/df["previous"])
    return df 
```


```python
def get_change_in_value_df(df, N):
    """
    df is the output of get_returns_df 
    
    appends change in portfolio value series to df
    """
    S0 = df.loc[0, "close"]
    df["change_in_value"] = N * S0 * df["return"]
    return df
```


```python
def get_kth_percentile_discrete(df, d, alpha):
    """
    this returns the (1-alpha)th percentile of the ordered array of historical changes in portfolio values
    """
    M = len(df)-d
    k = int(np.floor((1-alpha)*M))
    var = abs(df["change_in_value"].sort_values(ignore_index=True)[k-1])
    
    return var
```

\begin{equation}
	w_i = \left(\frac{1-\lambda}{1-\lambda^M}\right)\lambda^i, \qquad i=0,1,\dots,M-1.
	\end{equation}


```python
# This is from our programming session on EWMA 
# with a minor edit on ewma_par (now decay_par) and weight_lst formula
def get_weights_df(df, d, decay_par):
    """ 
    appends weights series to df
    """
    count_returns = len(df["close"])-d # M
    weight_lst = [(1-decay_par)*(decay_par**j)/(1-decay_par**count_returns) for j in range(count_returns)]
    df["weight"] = pd.Series(weight_lst)
    return df
```

<h3 style='color:green'> Example on Single-Asset Portfolio</h3> 
Today is February 23, 2018. You are a portfolio risk manager who is assigned to analyze the market risk of a portfolio of 700 PLDT (TEL) shares. Determine the portfolio's one-day 99% VaR using BRW approach with decay parameter of 0.76.
 



```python
df = pd.read_csv("https://raw.githubusercontent.com/ateneomathdept/math100.2_2021Sem1/main/data/lectures/TEL_2018.csv")
```


```python
df
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>dt</th>
      <th>close</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2/23/18</td>
      <td>1488.74</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2/22/18</td>
      <td>1510.86</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2/21/18</td>
      <td>1513.72</td>
    </tr>
    <tr>
      <th>3</th>
      <td>2/20/18</td>
      <td>1536.65</td>
    </tr>
    <tr>
      <th>4</th>
      <td>2/16/18</td>
      <td>1476.37</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>243</th>
      <td>3/2/17</td>
      <td>1430.29</td>
    </tr>
    <tr>
      <th>244</th>
      <td>3/1/17</td>
      <td>1408.94</td>
    </tr>
    <tr>
      <th>245</th>
      <td>2/28/17</td>
      <td>1394.35</td>
    </tr>
    <tr>
      <th>246</th>
      <td>2/27/17</td>
      <td>1374.93</td>
    </tr>
    <tr>
      <th>247</th>
      <td>2/24/17</td>
      <td>1367.68</td>
    </tr>
  </tbody>
</table>
<p>248 rows × 2 columns</p>
</div>




```python
d = 1
N = 700
# step 1.1: generate your historical returns
df = get_return(df, d)
# Step 1.2: get your historical changes in portfolio values
df = get_change_in_value_df(df, N)
df
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>dt</th>
      <th>close</th>
      <th>previous</th>
      <th>return</th>
      <th>change_in_value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2/23/18</td>
      <td>1488.74</td>
      <td>1510.86</td>
      <td>-0.014749</td>
      <td>-15370.094698</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2/22/18</td>
      <td>1510.86</td>
      <td>1513.72</td>
      <td>-0.001891</td>
      <td>-1970.824622</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2/21/18</td>
      <td>1513.72</td>
      <td>1536.65</td>
      <td>-0.015035</td>
      <td>-15667.749059</td>
    </tr>
    <tr>
      <th>3</th>
      <td>2/20/18</td>
      <td>1536.65</td>
      <td>1476.37</td>
      <td>0.040018</td>
      <td>41703.842890</td>
    </tr>
    <tr>
      <th>4</th>
      <td>2/16/18</td>
      <td>1476.37</td>
      <td>1475.71</td>
      <td>0.000447</td>
      <td>465.975103</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>243</th>
      <td>3/2/17</td>
      <td>1430.29</td>
      <td>1408.94</td>
      <td>0.015040</td>
      <td>15673.009003</td>
    </tr>
    <tr>
      <th>244</th>
      <td>3/1/17</td>
      <td>1408.94</td>
      <td>1394.35</td>
      <td>0.010409</td>
      <td>10847.710303</td>
    </tr>
    <tr>
      <th>245</th>
      <td>2/28/17</td>
      <td>1394.35</td>
      <td>1374.93</td>
      <td>0.014026</td>
      <td>14616.263663</td>
    </tr>
    <tr>
      <th>246</th>
      <td>2/27/17</td>
      <td>1374.93</td>
      <td>1367.68</td>
      <td>0.005287</td>
      <td>5509.622658</td>
    </tr>
    <tr>
      <th>247</th>
      <td>2/24/17</td>
      <td>1367.68</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
<p>248 rows × 5 columns</p>
</div>




```python
decay_par = 0.76
# Step 1.3: generate the weights
df = get_weights_df(df, d, decay_par)
df
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>dt</th>
      <th>close</th>
      <th>previous</th>
      <th>return</th>
      <th>change_in_value</th>
      <th>weight</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2/23/18</td>
      <td>1488.74</td>
      <td>1510.86</td>
      <td>-0.014749</td>
      <td>-15370.094698</td>
      <td>2.400000e-01</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2/22/18</td>
      <td>1510.86</td>
      <td>1513.72</td>
      <td>-0.001891</td>
      <td>-1970.824622</td>
      <td>1.824000e-01</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2/21/18</td>
      <td>1513.72</td>
      <td>1536.65</td>
      <td>-0.015035</td>
      <td>-15667.749059</td>
      <td>1.386240e-01</td>
    </tr>
    <tr>
      <th>3</th>
      <td>2/20/18</td>
      <td>1536.65</td>
      <td>1476.37</td>
      <td>0.040018</td>
      <td>41703.842890</td>
      <td>1.053542e-01</td>
    </tr>
    <tr>
      <th>4</th>
      <td>2/16/18</td>
      <td>1476.37</td>
      <td>1475.71</td>
      <td>0.000447</td>
      <td>465.975103</td>
      <td>8.006922e-02</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>243</th>
      <td>3/2/17</td>
      <td>1430.29</td>
      <td>1408.94</td>
      <td>0.015040</td>
      <td>15673.009003</td>
      <td>2.617666e-30</td>
    </tr>
    <tr>
      <th>244</th>
      <td>3/1/17</td>
      <td>1408.94</td>
      <td>1394.35</td>
      <td>0.010409</td>
      <td>10847.710303</td>
      <td>1.989426e-30</td>
    </tr>
    <tr>
      <th>245</th>
      <td>2/28/17</td>
      <td>1394.35</td>
      <td>1374.93</td>
      <td>0.014026</td>
      <td>14616.263663</td>
      <td>1.511964e-30</td>
    </tr>
    <tr>
      <th>246</th>
      <td>2/27/17</td>
      <td>1374.93</td>
      <td>1367.68</td>
      <td>0.005287</td>
      <td>5509.622658</td>
      <td>1.149092e-30</td>
    </tr>
    <tr>
      <th>247</th>
      <td>2/24/17</td>
      <td>1367.68</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
<p>248 rows × 6 columns</p>
</div>



## Step 2: Constructing the Empirical Distribution

Arrange the $\Delta P$ scenario space in increasing order. 
	
The empirical cumulative distribution at $\Delta\tilde{P}_{(j)}$ is determined by the accumulation of weights $\psi_j$ defined as
	\begin{equation}
	\psi_j = \sum_{i=0}^j w_{(i)}, \qquad j=0,1,\dots,M-1.
	\end{equation}


```python
df.sort_values(by="change_in_value")
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>dt</th>
      <th>close</th>
      <th>previous</th>
      <th>return</th>
      <th>change_in_value</th>
      <th>weight</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>68</th>
      <td>11/13/17</td>
      <td>1612.93</td>
      <td>1740.89</td>
      <td>-0.076344</td>
      <td>-79559.535694</td>
      <td>1.885973e-09</td>
    </tr>
    <tr>
      <th>43</th>
      <td>12/19/17</td>
      <td>1477.31</td>
      <td>1565.96</td>
      <td>-0.058276</td>
      <td>-60730.664517</td>
      <td>1.799682e-06</td>
    </tr>
    <tr>
      <th>10</th>
      <td>2/8/18</td>
      <td>1442.38</td>
      <td>1516.47</td>
      <td>-0.050091</td>
      <td>-52200.460251</td>
      <td>1.542933e-02</td>
    </tr>
    <tr>
      <th>161</th>
      <td>6/29/17</td>
      <td>1713.94</td>
      <td>1798.89</td>
      <td>-0.048375</td>
      <td>-50412.452299</td>
      <td>1.553101e-20</td>
    </tr>
    <tr>
      <th>49</th>
      <td>12/11/17</td>
      <td>1442.18</td>
      <td>1509.60</td>
      <td>-0.045689</td>
      <td>-47613.180665</td>
      <td>3.467985e-07</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>185</th>
      <td>5/25/17</td>
      <td>1666.95</td>
      <td>1590.54</td>
      <td>0.046922</td>
      <td>48898.290082</td>
      <td>2.141538e-23</td>
    </tr>
    <tr>
      <th>198</th>
      <td>5/5/17</td>
      <td>1741.91</td>
      <td>1661.55</td>
      <td>0.047231</td>
      <td>49220.599037</td>
      <td>6.043695e-25</td>
    </tr>
    <tr>
      <th>239</th>
      <td>3/8/17</td>
      <td>1532.88</td>
      <td>1433.24</td>
      <td>0.067211</td>
      <td>70041.483709</td>
      <td>7.846208e-30</td>
    </tr>
    <tr>
      <th>19</th>
      <td>1/26/18</td>
      <td>1614.12</td>
      <td>1489.54</td>
      <td>0.080323</td>
      <td>83705.595030</td>
      <td>1.305177e-03</td>
    </tr>
    <tr>
      <th>247</th>
      <td>2/24/17</td>
      <td>1367.68</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
<p>248 rows × 6 columns</p>
</div>




```python
def get_ecdf(df):
    """
        assumes the input df have already passed through get_change_in_value_df and get_weights_df
    """
    df = df.sort_values(by="change_in_value")
    df["ecdf"] = df["weight"].cumsum()
    
    return df
```


```python
df = get_ecdf(df)
df
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>dt</th>
      <th>close</th>
      <th>previous</th>
      <th>return</th>
      <th>change_in_value</th>
      <th>weight</th>
      <th>ecdf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>68</th>
      <td>11/13/17</td>
      <td>1612.93</td>
      <td>1740.89</td>
      <td>-0.076344</td>
      <td>-79559.535694</td>
      <td>1.885973e-09</td>
      <td>1.885973e-09</td>
    </tr>
    <tr>
      <th>43</th>
      <td>12/19/17</td>
      <td>1477.31</td>
      <td>1565.96</td>
      <td>-0.058276</td>
      <td>-60730.664517</td>
      <td>1.799682e-06</td>
      <td>1.801568e-06</td>
    </tr>
    <tr>
      <th>10</th>
      <td>2/8/18</td>
      <td>1442.38</td>
      <td>1516.47</td>
      <td>-0.050091</td>
      <td>-52200.460251</td>
      <td>1.542933e-02</td>
      <td>1.543114e-02</td>
    </tr>
    <tr>
      <th>161</th>
      <td>6/29/17</td>
      <td>1713.94</td>
      <td>1798.89</td>
      <td>-0.048375</td>
      <td>-50412.452299</td>
      <td>1.553101e-20</td>
      <td>1.543114e-02</td>
    </tr>
    <tr>
      <th>49</th>
      <td>12/11/17</td>
      <td>1442.18</td>
      <td>1509.60</td>
      <td>-0.045689</td>
      <td>-47613.180665</td>
      <td>3.467985e-07</td>
      <td>1.543148e-02</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>185</th>
      <td>5/25/17</td>
      <td>1666.95</td>
      <td>1590.54</td>
      <td>0.046922</td>
      <td>48898.290082</td>
      <td>2.141538e-23</td>
      <td>9.986948e-01</td>
    </tr>
    <tr>
      <th>198</th>
      <td>5/5/17</td>
      <td>1741.91</td>
      <td>1661.55</td>
      <td>0.047231</td>
      <td>49220.599037</td>
      <td>6.043695e-25</td>
      <td>9.986948e-01</td>
    </tr>
    <tr>
      <th>239</th>
      <td>3/8/17</td>
      <td>1532.88</td>
      <td>1433.24</td>
      <td>0.067211</td>
      <td>70041.483709</td>
      <td>7.846208e-30</td>
      <td>9.986948e-01</td>
    </tr>
    <tr>
      <th>19</th>
      <td>1/26/18</td>
      <td>1614.12</td>
      <td>1489.54</td>
      <td>0.080323</td>
      <td>83705.595030</td>
      <td>1.305177e-03</td>
      <td>1.000000e+00</td>
    </tr>
    <tr>
      <th>247</th>
      <td>2/24/17</td>
      <td>1367.68</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
<p>248 rows × 7 columns</p>
</div>



## Step 3: Calculating the VaR

The $100\alpha\%$ value-at-risk is obtained by first identifying successive ordered pairs $(\Delta\tilde{P}^{(k)},\psi_k)$ and $(\Delta\tilde{P}^{(k+1)},\psi_{k+1})$ such that
	\begin{equation}
	\psi_k < 1-\alpha < \psi_{k+1}.
	\end{equation}
	(This is because $1-\alpha$ may not be a value assumed in the sequence $\psi_0,\psi_1,\dots,\psi_{M-1}$.)
	
	
Let $V$ be the linearly interpolated value of $\Delta P$ from the ordered pairs $(\Delta\tilde{P}^{(k)},\psi_k)$ and $(\Delta\tilde{P}^{(k+1)},\psi_{k+1})$ corresponding to the value $\psi=1-\alpha$. **The $100\alpha\%$ value-at-risk is therefore given by $|V|$.**

Step 1: Identify the successive ordered pairs $(\Delta\tilde{P}^{(k)},\psi_k)$ and $(\Delta\tilde{P}^{(k+1)},\psi_{k+1})$ such that
	\begin{equation}
	\psi_k < 1-\alpha < \psi_{k+1}.
	\end{equation}
Step 2: implement linear interpolation on $\Delta P$ from the ordered pairs $(\Delta\tilde{P}^{(k)},\psi_k)$ and $(\Delta\tilde{P}^{(k+1)},\psi_{k+1})$ corresponding to the value $\psi=1-\alpha$


Tip: use [np.interp()](https://numpy.org/doc/stable/reference/generated/numpy.interp.html) 


```python
alpha = 0.99
var = abs(np.interp(1-alpha, df["ecdf"], df["change_in_value"]))
var
```




    55203.09747955038




```python
def d_day_alpha_percent_VaR_single_stock_brw(
    df, N, d, alpha, decay_par
):
    """Returns the d-day 100*alpha% VaR of a single stock using BRW Approach.
    
    Parameters
    ----------
    df : pandas.DataFrame
        has two columns: (1) dt [str] and (2) closing price [float]
        assumes the dates are arranged from newest to oldest, and the date today is the date on the first row  
    N : int
        number of shares for the sole stock
    d : int
        the value to be used in calculating the d-day VaR (e.g. 1-day, 5-day)
    alpha : float
        the value to be used in calculting the 100(alpha)% VaR (e.g. 0.99, 0.95)
    decay_par : float
        the value of the BRW decay parameter
    
    Returns
    -------
    float (2 decimal places)
       d-day 100*alpha% VaR of a single stock using BRW Approach.
    """
    # STEP 1: ASSIGNMENT OF WEIGHTS
    # step 1.1: generate your historical returns
    df = get_return(df, d)
    # Step 1.2: get your historical changes in portfolio values
    df = get_change_in_value_df(df, N)
    # Step 1.3: generate the weights
    df = get_weights_df(df, d, decay_par)
    
    # STEP 2: CONSTRUCT THE ECDF
    df = get_ecdf(df)
    
    # STEP 3: CALCULATE THE VAR
    var = abs(np.interp(1-alpha, df["ecdf"], df["change_in_value"]))
    
    return round(var, 2)
```


```python
TEL_df = pd.read_csv("https://raw.githubusercontent.com/ateneomathdept/math100.2_2021Sem1/main/data/lectures/TEL_2018.csv")
```


```python
d_day_alpha_percent_VaR_single_stock_brw(df=TEL_df, N=700, d=1, alpha=0.99, decay_par=0.76)
```




    55203.1



## The BRW Approach: N-Asset Case

The BRW approach can be extended to the $N$-asset case by taking the $R_i$'s as the one-day log-return on the **entire portfolio**. To $R_i$ we assign the weight $w_i$ (as defined before).

If $P_0$ is the current value of the portfolio, then scenarios for the future one-day change in portfolio value $\Delta P$ can be obtained using the equation
	\begin{equation}
	\Delta P_i = P_0(e^{R_i}-1).
	\end{equation}

We then resume the BRW approach at Step 2 (Constructing the Empirical Distribution) using $\{\Delta P_i\}$ as the $\Delta P$ scenarios.



```python

```

1f541fd56e9a7d2fc6f8b55dd981369e04f69905
ipynb
Jupyter Notebook
lectures/1Stocks/20210928_VaRforStocks_BRW.ipynb
ateneomathdept/math100.2_2021Sem1
ac51a30e25d57a50ef8aa25fcfc0db64da0641b9
MIT
2021-12-12T04:44:09.000Z
2021-12-12T04:44:09.000Z
lectures/1Stocks/20210928_VaRforStocks_BRW.ipynb
ateneomathdept/math100.2_2021Sem1
ac51a30e25d57a50ef8aa25fcfc0db64da0641b9
MIT
lectures/1Stocks/20210928_VaRforStocks_BRW.ipynb
ateneomathdept/math100.2_2021Sem1
ac51a30e25d57a50ef8aa25fcfc0db64da0641b9
MIT
2021-10-04T04:16:18.000Z
2021-10-04T04:16:18.000Z
Qwen/Qwen-72B
1. YES
2. YES