Lab 4
# Start writing code here...
# math operations
from numpy import inf
# time operations
from datetime import timedelta
# for numerical analysis
import numpy as np
# to store and process data in dataframe
import pandas as pd
# basic visualization package
import matplotlib.pyplot as plt
# advanced ploting
import seaborn as sns
# interactive visualization
import plotly.express as px
import plotly.graph_objs as go
#from plotly.offline import plot, iplot, init_notebook_mode
#init_notebook_mode(connected=True)
# hide warnings
import warnings
warnings.filterwarnings("ignore")
# Import modules for API calls
import requests
import io
import pandas as pd
import requests
import json
from datetime import datetime
# Import module for plotting
import seaborn as sns
## JHU Vaccination Rates (Taken From: https://github.com/owid/covid-19-data/tree/master/public/data)
url = 'https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/owid-covid-data.csv'
download = requests.get(url).content
covid = pd.read_csv(io.StringIO(download.decode('utf-8')), parse_dates=['date'])
covid = covid.fillna(0)
covid = covid[covid["location"]=="Singapore"]
print(covid.info())
<class 'pandas.core.frame.DataFrame'>
Int64Index: 605 entries, 94462 to 95066
Data columns (total 62 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 iso_code 605 non-null object
1 continent 605 non-null object
2 location 605 non-null object
3 date 605 non-null datetime64[ns]
4 total_cases 605 non-null float64
5 new_cases 605 non-null float64
6 new_cases_smoothed 605 non-null float64
7 total_deaths 605 non-null float64
8 new_deaths 605 non-null float64
9 new_deaths_smoothed 605 non-null float64
10 total_cases_per_million 605 non-null float64
11 new_cases_per_million 605 non-null float64
12 new_cases_smoothed_per_million 605 non-null float64
13 total_deaths_per_million 605 non-null float64
14 new_deaths_per_million 605 non-null float64
15 new_deaths_smoothed_per_million 605 non-null float64
16 reproduction_rate 605 non-null float64
17 icu_patients 605 non-null float64
18 icu_patients_per_million 605 non-null float64
19 hosp_patients 605 non-null float64
20 hosp_patients_per_million 605 non-null float64
21 weekly_icu_admissions 605 non-null float64
22 weekly_icu_admissions_per_million 605 non-null float64
23 weekly_hosp_admissions 605 non-null float64
24 weekly_hosp_admissions_per_million 605 non-null float64
25 new_tests 605 non-null float64
26 total_tests 605 non-null float64
27 total_tests_per_thousand 605 non-null float64
28 new_tests_per_thousand 605 non-null float64
29 new_tests_smoothed 605 non-null float64
30 new_tests_smoothed_per_thousand 605 non-null float64
31 positive_rate 605 non-null float64
32 tests_per_case 605 non-null float64
33 tests_units 605 non-null object
34 total_vaccinations 605 non-null float64
35 people_vaccinated 605 non-null float64
36 people_fully_vaccinated 605 non-null float64
37 total_boosters 605 non-null float64
38 new_vaccinations 605 non-null float64
39 new_vaccinations_smoothed 605 non-null float64
40 total_vaccinations_per_hundred 605 non-null float64
41 people_vaccinated_per_hundred 605 non-null float64
42 people_fully_vaccinated_per_hundred 605 non-null float64
43 total_boosters_per_hundred 605 non-null float64
44 new_vaccinations_smoothed_per_million 605 non-null float64
45 stringency_index 605 non-null float64
46 population 605 non-null float64
47 population_density 605 non-null float64
48 median_age 605 non-null float64
49 aged_65_older 605 non-null float64
50 aged_70_older 605 non-null float64
51 gdp_per_capita 605 non-null float64
52 extreme_poverty 605 non-null float64
53 cardiovasc_death_rate 605 non-null float64
54 diabetes_prevalence 605 non-null float64
55 female_smokers 605 non-null float64
56 male_smokers 605 non-null float64
57 handwashing_facilities 605 non-null float64
58 hospital_beds_per_thousand 605 non-null float64
59 life_expectancy 605 non-null float64
60 human_development_index 605 non-null float64
61 excess_mortality 605 non-null float64
dtypes: datetime64[ns](1), float64(57), object(4)
memory usage: 297.8+ KB
None
1. Singapore's Covid19 situation
cnf, dth, rec, act = "#393e46", "#ff2e63", "#21bf73", "#fe9801"
temp = covid[["date","new_cases", "new_cases_smoothed"]]
temp.head()
# Plot a stack area graph with the three types of cases (i.e., recovered, deaths, and active)
fig = px.line(
temp,
x="date",
y=["new_cases", "new_cases_smoothed"],
height=600,
width=700,
title="Cases over time",
color_discrete_sequence=[rec, dth, act],
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
The new_case and new_case_smoothed (7-day) are used to plot the line graph above. It is able to illustrate the trend of new covid cases as well as the short term covid situation. If the new_case is below the new_case_smooth, we can conclude that the covid situation is getting better, vice versa. From the graph above, it is shown that after a period of case number stablisation between Sep 2020 and early July 2021, the new cases spiked up parabolically from mid-July and currently, new_case is above new_case_smoothed. We can conclude that the Covid sitation is getting worse recently based on the number of new cases.
cnf, dth, rec, act = "#393e46", "#ff2e63", "#21bf73", "#fe9801"
temp1 = covid[["date","reproduction_rate"]]
temp1["historical_average_r"] = temp1["reproduction_rate"].mean()
temp.head()
# Plot a stack area graph with the three types of cases (i.e., recovered, deaths, and active)
fig = px.line(
temp1,
x="date",
y=["reproduction_rate", "historical_average_r"],
height=600,
width=700,
title="R over time",
color_discrete_sequence=[rec, dth, act],
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
Based on the the head of the Multi-Ministry Covid Task Force, Lawrence Wong, the reproduction R is the rate of the spread of the Covid in Singapore and they are closely monitoring the number. Therefore, the average historical R is used to plot against the daily reproduction R. It is to see if the current R is above or below the the average. From the graph above, since mid August 2021, the R has risen above the average R sharply, therefore, we can conclude the Covid situation is worsened. On a side note, it is worth observing the level of R tolerant for the Singapore government in the future for them to step up its Covid measures.
2. Evaluate the potential hidden cases (e.g., case positivity rate) and deaths (e.g., estimated infection fatality rate, excess death)
cnf, dth, rec, act = "#393e46", "#ff2e63", "#21bf73", "#fe9801"
temp2 = covid[["date","positive_rate"]]
temp2.head()
# Plot a stack area graph with the three types of cases (i.e., recovered, deaths, and active)
fig = px.line(
temp2,
x="date",
y=["positive_rate"],
height=600,
width=700,
title="Case positivity over time",
color_discrete_sequence=[rec, dth, act],
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
The above line graph has shown almost 0 positivity rate since Oct 2020, it shows that Singapore government has done a good job in the efficiency of the Covid testing. Therefore, the probablity of hidden cases in Singapore is extremely low.
cnf, dth, rec, act = "#393e46", "#ff2e63", "#21bf73", "#fe9801"
temp2 = covid[["date","new_deaths","excess_mortality"]]
temp2.head()
# Plot a stack area graph with the three types of cases (i.e., recovered, deaths, and active)
fig = px.line(
temp2,
x="date",
y=["new_deaths","excess_mortality"],
height=600,
width=700,
title="New deaths over time",
color_discrete_sequence=[rec, dth, act],
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
3. Explore the relationship between the country's Covid19 cases and deaths and government health intervention policies (e.g., vaccination rate, closure), as well as Google community mobility reports.
mobility = pd.read_csv("/work/2020_SG_Region_Mobility_Report.csv")
mobility.drop_duplicates()
mobility.head()
mobility.tail()
sg_mobility_1 = mobility.iloc[:,8:]
sg_mobility_1["date"] = pd.to_datetime(sg_mobility_1["date"])
sg_mobility_1.tail()
sg_mobility_1 = sg_mobility_1.rename(columns={'retail_and_recreation_percent_change_from_baseline': 'Retail & Recreation', 'grocery_and_pharmacy_percent_change_from_baseline':'Grocery & Pharma', 'parks_percent_change_from_baseline':'Parks', 'transit_stations_percent_change_from_baseline':'Transit Stations','workplaces_percent_change_from_baseline':'Workplace','residential_percent_change_from_baseline':'Residential' })
sg_mobility_1.head()
cnf, dth, rec, act = "#ff0000", "#00ff00", "#0000ff", "#ffff00"
fig = px.line(
sg_mobility_1,
x="date",
y=["Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"],
height=600,
width=700,
title="Mobility Over Time",
color_discrete_sequence=[cnf, dth, rec, act]
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
sg_mobility_1['retail_SMA_7'] = sg_mobility_1.iloc[:,1].rolling(window=7).mean()
sg_mobility_1['grocery_SMA_7'] = sg_mobility_1.iloc[:,2].rolling(window=7).mean()
sg_mobility_1['parks_SMA_7'] = sg_mobility_1.iloc[:,3].rolling(window=7).mean()
sg_mobility_1['transit_stations_SMA_7'] = sg_mobility_1.iloc[:,4].rolling(window=7).mean()
sg_mobility_1['workplace_SMA_7'] = sg_mobility_1.iloc[:,5].rolling(window=7).mean()
sg_mobility_1['residential_SMA_7'] = sg_mobility_1.iloc[:,6].rolling(window=7).mean()
sg_mobility_1.tail(10)
cnf, dth, rec, act = "#ff0000", "#00ff00", "#0000ff", "#ffff00"
# Plot a stack area graph with the three types of cases (i.e., recovered, deaths, and active)
fig = px.line(
sg_mobility_1,
x="date",
y=["retail_SMA_7","grocery_SMA_7","transit_stations_SMA_7","workplace_SMA_7"],
labels={"value": "% change from baseline"},
height=600,
width=700,
title="Mobility in Singapore Over Time",
color_discrete_sequence=[cnf, dth, rec, act],
)
fig.update_layout(xaxis_rangeslider_visible=True)
fig.show()
Data from Google Mobility Reports indicate a recovery from a trough in mid-2020, however recent information show a slight downward trend as cases rise and people are more fearful to move about.
relationship = covid.merge(sg_mobility_1,on = "date", how = "right")[["total_cases","new_cases","new_deaths","Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"]]
sns.pairplot(relationship)
#sns.jointplot(x = 'Tests/1M pop', y = 'Tot Cases/1M pop', data = worldometer_data, kind='reg')
#print(relationship.head(10))
The above correlation chart shows that daily new cases correlate negatively with mobility in Singapore. This is due to heightened government restrictions implemented in response to higher daily new cases.
Responsiveness of government to daily new cases
a) Singapore
stringency = covid[["date","stringency_index"]]
stringency.head()
newcases_vs_stringency = covid[["new_cases","stringency_index"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(newcases_vs_stringency, annot=True, ax = ax)
b) Malaysia
covid_my = pd.read_csv(io.StringIO(download.decode('utf-8')), parse_dates=['date'])
covid_my = covid_my.fillna(0)
covid_my = covid_my.query('location == "Malaysia"')
covid_my.head()
newcases_vs_stringency_my = covid_my[["new_cases","stringency_index"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(newcases_vs_stringency_my, annot=True, ax = ax)
c) Australia
covid_aus = pd.read_csv(io.StringIO(download.decode('utf-8')), parse_dates=['date'])
covid_aus = covid_aus.fillna(0)
covid_aus = covid_aus.query('location == "Australia"')
covid_aus.head()
newcases_vs_stringency_aus = covid_aus[["new_cases","stringency_index"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(newcases_vs_stringency_aus, annot=True, ax = ax)
d) USA
covid_usa = pd.read_csv(io.StringIO(download.decode('utf-8')), parse_dates=['date'])
covid_usa = covid_usa.fillna(0)
covid_usa = covid_usa.query('location == "United States"')
covid_usa.head()
newcases_vs_stringency_usa = covid_usa[["new_cases","stringency_index"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(newcases_vs_stringency_usa, annot=True, ax = ax)
Conclusively, among the 4 countries measured (Singapore, Malaysia, Australia, USA), Singapore's government is the most responsive to rises in daily new cases, with a 0.4 correlation coefficient between stringency and new cases, with the figure being -0.58 for Malaysia, 0.076 for Australia and 0.32 for the USA.
Level of compliance of citizens to government restrictions
Metric of compliance: The closer to -1 the correlation between mobility and stringency, the more compliant the citizens to government restrictions (pay attention to dark-coloured boxes)
a) Singapore
relationship1 = sg_mobility_1.merge(stringency,on = "date", how = "right")[["Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace","stringency_index"]]
relationship1_corr = relationship1[["stringency_index", "Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(relationship1_corr, annot=True, ax = ax)
b) Malaysia
mobility_my = pd.read_csv("/work/2020_MY_Region_Mobility_Report.csv")
mobility_my.drop_duplicates()
mobility_my["date"] = pd.to_datetime(mobility_my["date"])
mobility_my = mobility_my.rename(columns={'retail_and_recreation_percent_change_from_baseline': 'Retail & Recreation', 'grocery_and_pharmacy_percent_change_from_baseline':'Grocery & Pharma', 'parks_percent_change_from_baseline':'Parks', 'transit_stations_percent_change_from_baseline':'Transit Stations','workplaces_percent_change_from_baseline':'Workplace','residential_percent_change_from_baseline':'Residential' })
mobility_my.tail()
stringency_my = covid_my[["date","stringency_index"]]
relationship2 = mobility_my.merge(stringency_my,on = "date", how = "right")[["Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace","stringency_index"]]
relationship2_corr = relationship2[["stringency_index", "Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(relationship2_corr, annot=True, ax = ax)
c) Australia
mobility_aus = pd.read_csv("/work/2020_AU_Region_Mobility_Report.csv")
mobility_aus.drop_duplicates()
mobility_aus["date"] = pd.to_datetime(mobility_aus["date"])
mobility_aus = mobility_aus.rename(columns={'retail_and_recreation_percent_change_from_baseline': 'Retail & Recreation', 'grocery_and_pharmacy_percent_change_from_baseline':'Grocery & Pharma', 'parks_percent_change_from_baseline':'Parks', 'transit_stations_percent_change_from_baseline':'Transit Stations','workplaces_percent_change_from_baseline':'Workplace','residential_percent_change_from_baseline':'Residential' })
mobility_aus.tail()
stringency_aus = covid_aus[["date","stringency_index"]]
relationship3 = mobility_aus.merge(stringency_aus,on = "date", how = "right")[["Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace","stringency_index"]]
relationship3_corr = relationship3[["stringency_index", "Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(relationship3_corr, annot=True, ax = ax)
d) USA
mobility_us = pd.read_csv("/work/2020_US_Region_Mobility_Report.csv")
mobility_us.drop_duplicates()
mobility_us["date"] = pd.to_datetime(mobility_aus["date"])
mobility_us = mobility_us.rename(columns={'retail_and_recreation_percent_change_from_baseline': 'Retail & Recreation', 'grocery_and_pharmacy_percent_change_from_baseline':'Grocery & Pharma', 'parks_percent_change_from_baseline':'Parks', 'transit_stations_percent_change_from_baseline':'Transit Stations','workplaces_percent_change_from_baseline':'Workplace','residential_percent_change_from_baseline':'Residential' })
mobility_us.tail()
stringency_us = covid_aus[["date","stringency_index"]]
relationship4 = mobility_us.merge(stringency_us,on = "date", how = "right")[["Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace","stringency_index"]]
relationship4_corr = relationship4[["stringency_index", "Retail & Recreation","Grocery & Pharma","Transit Stations","Workplace"]].dropna().corr()
fig, ax = plt.subplots(figsize=(10,5))
sns.heatmap(relationship4_corr, annot=True, ax = ax)
Conclusion: Singapore has the highest level of compliance to government restrictions among the selected countries, followed by Australia, USA and Malaysia (in this order).
4. Relationship between STI, selected Singaporean stocks and pandemic
!pip install yfinance --upgrade --no-cache-dir
Collecting yfinance
Downloading yfinance-0.1.63.tar.gz (26 kB)
Requirement already satisfied: pandas>=0.24 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from yfinance) (1.2.5)
Requirement already satisfied: numpy>=1.15 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from yfinance) (1.19.5)
Requirement already satisfied: requests>=2.20 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from yfinance) (2.26.0)
Collecting multitasking>=0.0.7
Downloading multitasking-0.0.9.tar.gz (8.1 kB)
Requirement already satisfied: lxml>=4.5.1 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from yfinance) (4.6.3)
Requirement already satisfied: pytz>=2017.3 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from pandas>=0.24->yfinance) (2021.1)
Requirement already satisfied: python-dateutil>=2.7.3 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from pandas>=0.24->yfinance) (2.8.2)
Requirement already satisfied: six>=1.5 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from python-dateutil>=2.7.3->pandas>=0.24->yfinance) (1.16.0)
Requirement already satisfied: charset-normalizer~=2.0.0 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from requests>=2.20->yfinance) (2.0.4)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from requests>=2.20->yfinance) (1.26.6)
Requirement already satisfied: certifi>=2017.4.17 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from requests>=2.20->yfinance) (2021.5.30)
Requirement already satisfied: idna<4,>=2.5 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from requests>=2.20->yfinance) (3.2)
Building wheels for collected packages: yfinance, multitasking
Building wheel for yfinance (setup.py) ... done
Created wheel for yfinance: filename=yfinance-0.1.63-py2.py3-none-any.whl size=23918 sha256=d0a075c0c2696c0304d0172c608d8438cbdd168c843d3d11d7a3e94b15cef7ef
Stored in directory: /tmp/pip-ephem-wheel-cache-n2t6knr6/wheels/fe/87/8b/7ec24486e001d3926537f5f7801f57a74d181be25b11157983
Building wheel for multitasking (setup.py) ... done
Created wheel for multitasking: filename=multitasking-0.0.9-py3-none-any.whl size=8375 sha256=3032d39e99f7078532da3b11d9709b8ee5d2f4d03c001c7d542d2da054345b42
Stored in directory: /tmp/pip-ephem-wheel-cache-n2t6knr6/wheels/ae/25/47/4d68431a7ec1b6c4b5233365934b74c1d4e665bf5f968d363a
Successfully built yfinance multitasking
Installing collected packages: multitasking, yfinance
Successfully installed multitasking-0.0.9 yfinance-0.1.63
WARNING: You are using pip version 21.2.3; however, version 21.2.4 is available.
You should consider upgrading via the '/root/venv/bin/python -m pip install --upgrade pip' command.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import datetime as dt
import yfinance as yf
symbols_list1 = ['^STI','OV8.SI','1337.HK','SE']
start1 = dt.datetime(2020,1,1)
end1 = dt.datetime(2021,9,15)
data1 = yf.download(symbols_list1, start=start1, end=end1)
[*********************100%***********************] 4 of 4 completed
data1 = data1["Adj Close"]
data1.head()
#Calculate daily return
data1['STI_ret'] =data1['^STI'].pct_change()[1:]
data1['OV8.SI_ret'] =data1['OV8.SI'].pct_change()[1:]
data1['1337.HK_ret'] =data1['1337.HK'].pct_change()[1:]
data1['SE_ret'] =data1['SE'].pct_change()[1:]
data1 = data1.ffill()
data1 = data1.reset_index()
data1 = data1.rename(columns={"Date":"date"})
data1.head()
data2 = data1.merge(stringency, on='date',how='right')
data2.head()
data2 = data2.merge(sg_mobility_1, on='date',how='right')
data2.head()
data2 = pd.merge(data2,covid[['date','new_cases']],on='date', how='left')
data2.head()
data2_corr = data2[['STI_ret','OV8.SI_ret','1337.HK_ret','SE_ret','Retail & Recreation','Workplace','new_cases','stringency_index']].dropna().corr()
fig, ax = plt.subplots(figsize=(20,10))
sns.heatmap(data2_corr, annot=True, ax = ax)
The above graphic shows correlations between Singaporean equities, mobility, daily new cases, and stringency of movement restrictions. Based on it, the STI is mostly uncorrelated with reductions in mobility, and increases in daily new cases and stringency. The two stocks with the highest correlation of the 4 selected are Sheng Siong (OV8.SI) and Sea Group (SE) - with each having correlations of 0.1 and 0.11 with the stringency index respectively.
Lab 5
1. Based on your investment strategy (i.e., chosen stocks, industries, asset classes), identify the maximum Sharpe ratio and minimum volatility portfolios.
Our strategy is to invest in the following stocks: Amazon (AMZN), Sheng Siong (OV8.SI), Moderna (MRNA), Novavax (NVAX), and Sea Group (SE)
# find the symbol (i.e., google the instrument + 'yahoo finance') to any data series you are interested at
# e.g., market/sector index ETF for your chosen country and various asset classes (e.g., Comex Gold's symbol is 'GC=F')
# e.g., SPY (https://finance.yahoo.com/quote/SPY/)
symbols_list = ['AMZN', 'OV8.SI', 'MRNA','NVAX','SE']
start = dt.datetime(2019,12,1)
end = dt.datetime(2021,9,15)
data = yf.download(symbols_list, start=start, end=end)
[*********************100%***********************] 5 of 5 completed
# filter column adjusted close
df = data['Adj Close']
df = df.ffill()
df.head()
df["OV8.SI_USD"] = df["OV8.SI"] * 0.74
df = df.drop("OV8.SI", axis=1)
df.head()
!pip install PyPortfolioOpt==1.2.1
Collecting PyPortfolioOpt==1.2.1
Downloading PyPortfolioOpt-1.2.1-py3-none-any.whl (49 kB)
|████████████████████████████████| 49 kB 8.3 MB/s
Collecting cvxpy<2.0.0,>=1.0.28
Downloading cvxpy-1.1.15-cp37-cp37m-manylinux_2_24_x86_64.whl (2.7 MB)
|████████████████████████████████| 2.7 MB 36.5 MB/s
Requirement already satisfied: scipy in /shared-libs/python3.7/py/lib/python3.7/site-packages (from PyPortfolioOpt==1.2.1) (1.7.1)
Collecting pandas<0.26.0,>=0.25.3
Downloading pandas-0.25.3-cp37-cp37m-manylinux1_x86_64.whl (10.4 MB)
|████████████████████████████████| 10.4 MB 48.2 MB/s
Requirement already satisfied: numpy in /shared-libs/python3.7/py/lib/python3.7/site-packages (from PyPortfolioOpt==1.2.1) (1.19.5)
Collecting scs>=1.1.6
Downloading scs-2.1.4.tar.gz (6.6 MB)
|████████████████████████████████| 6.6 MB 22.7 MB/s
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing wheel metadata ... done
Collecting osqp>=0.4.1
Downloading osqp-0.6.2.post0-cp37-cp37m-manylinux2014_x86_64.whl (212 kB)
|████████████████████████████████| 212 kB 76.3 MB/s
Collecting ecos>=2
Downloading ecos-2.0.7.post1-cp37-cp37m-manylinux1_x86_64.whl (147 kB)
|████████████████████████████████| 147 kB 71.1 MB/s
Collecting qdldl
Downloading qdldl-0.1.5.post0-cp37-cp37m-manylinux2014_x86_64.whl (941 kB)
|████████████████████████████████| 941 kB 67.3 MB/s
Requirement already satisfied: python-dateutil>=2.6.1 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from pandas<0.26.0,>=0.25.3->PyPortfolioOpt==1.2.1) (2.8.2)
Requirement already satisfied: pytz>=2017.2 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from pandas<0.26.0,>=0.25.3->PyPortfolioOpt==1.2.1) (2021.1)
Requirement already satisfied: six>=1.5 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from python-dateutil>=2.6.1->pandas<0.26.0,>=0.25.3->PyPortfolioOpt==1.2.1) (1.16.0)
Building wheels for collected packages: scs
Building wheel for scs (PEP 517) ... done
Created wheel for scs: filename=scs-2.1.4-cp37-cp37m-linux_x86_64.whl size=113462 sha256=bb42cb3c3ced5c2c953622e83f7546fedf3b4dd93463025050dd17f8c847e12c
Stored in directory: /root/.cache/pip/wheels/d6/58/0b/a55ff6d4e2da4c9776e363a778ea13e4068037a7be82eb85e0
Successfully built scs
Installing collected packages: qdldl, scs, osqp, ecos, pandas, cvxpy, PyPortfolioOpt
Attempting uninstall: pandas
Found existing installation: pandas 1.2.5
Not uninstalling pandas at /shared-libs/python3.7/py/lib/python3.7/site-packages, outside environment /root/venv
Can't uninstall 'pandas'. No files were found to uninstall.
Successfully installed PyPortfolioOpt-1.2.1 cvxpy-1.1.15 ecos-2.0.7.post1 osqp-0.6.2.post0 pandas-0.25.3 qdldl-0.1.5.post0 scs-2.1.4
WARNING: You are using pip version 21.2.3; however, version 21.2.4 is available.
You should consider upgrading via the '/root/venv/bin/python -m pip install --upgrade pip' command.
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
from pypfopt import cla
from pypfopt.plotting import Plotting
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
from matplotlib.ticker import FuncFormatter
import seaborn as sns
# Check NaN values in the data
nullin_df = pd.DataFrame(df,columns=symbols_list)
print(nullin_df.isnull().sum())
# Calculate portfolio mean return
mu = expected_returns.mean_historical_return(df)
print(mu)
# Calculate portfolio return variance
sigma = risk_models.sample_cov(df)
print(sigma)
AMZN 0
OV8.SI 461
MRNA 0
NVAX 0
SE 0
dtype: int64
AMZN 0.414608
MRNA 2.073933
NVAX 3.060995
SE 1.380613
OV8.SI_USD 0.175837
dtype: float64
AMZN MRNA NVAX SE OV8.SI_USD
AMZN 0.105037 0.008802 0.070541 0.091127 0.005925
MRNA 0.008802 0.790117 0.368148 0.068074 0.007288
NVAX 0.070541 0.368148 2.190706 0.147731 0.014819
SE 0.091127 0.068074 0.147731 0.334761 0.021558
OV8.SI_USD 0.005925 0.007288 0.014819 0.021558 0.061633
2. Maximum Sharpe Portfolio
# Note max sharpe ratio is the tangency portfolio
# weight bounds in negative allows shorting of stocks
ef = EfficientFrontier(mu, sigma, weight_bounds=(-1,1))
# optional constraints possible, read pypfopt documentation.
sharpe_portfolio=ef.max_sharpe(risk_free_rate=0.008)
sharpe_portfolio_wt=ef.clean_weights()
print(sharpe_portfolio_wt)
Plotting.plot_weights(sharpe_portfolio_wt)
{'AMZN': 0.04432, 'MRNA': 0.26087, 'NVAX': 0.1125, 'SE': 0.4273, 'OV8.SI_USD': 0.15502}
latest_prices = get_latest_prices(df)
# Allocate Portfolio Value in $ as required to show number of shares/stocks to buy,
# also bounds for shorting will affect allocation
# Maximum Sharpe Portfolio Allocation $1000000
da = DiscreteAllocation(sharpe_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print(allocation)
print("Leftover Fund value for the maximum Sharpe portfolio is ${:.2f}".format(leftover))
{'SE': 1281, 'MRNA': 609, 'OV8.SI_USD': 140594, 'NVAX': 480, 'AMZN': 12}
Leftover Fund value for the maximum Sharpe portfolio is $3368.94
a) If you have USD 1 million now, how much should you invest in each.
{'SE': 1281, 'MRNA': 609, 'OV8.SI_USD': 140594, 'NVAX': 480, 'AMZN': 12} Leftover Fund value for the maximum Sharpe portfolio is $3368.94
max_sharpe_cla = cla.CLA(mu, sigma)
max_sharpe_cla.max_sharpe()
Plotting.plot_efficient_frontier(max_sharpe_cla, show_assets="True")
sharpe_portfolio_wt_list = list(sharpe_portfolio_wt.values())
ret_data = df.pct_change()[1:]
weighted_returns = (sharpe_portfolio_wt_list * ret_data)
portfolio_ret = pd.DataFrame(weighted_returns.sum(axis=1))
ret_data = ret_data.merge(portfolio_ret, on="Date", how="left")
ret_data = ret_data.rename(columns={0: "portfolio_ret"})
ret_data.head()
ret_data['cumulative_portfolio_ret'] = (ret_data['portfolio_ret'] + 1).cumprod()
ret_data['cumulative_spy_ret'] = (ret_data['AMZN'] + 1).cumprod()
ret_data.head()
b) Plot a graph showing how your portfolios (maximum Sharpe and minimum volatility portfolios) perform relative to the market (i.e., SPY).
sns.scatterplot('Date', 'cumulative_portfolio_ret', data=ret_data)
sns.scatterplot('Date', 'cumulative_spy_ret', data=ret_data)
plt.legend(labels=["Our Portfolio","S&P 500"])
def cdf(data):
n = len(data)
print("length of numpy array: ", n)
x = np.sort(data)
print("x contains: ", x)
y = np.arange(1, n+1) / n
print("y contains: ", y)
return x, y
mean = np.mean(ret_data['cumulative_portfolio_ret'])
# cumulative distribution
x,y = cdf(ret_data['cumulative_portfolio_ret'])
# random sample from an exponential distribution calibrated with sample mean (parameter for exponential distribution)
samples_exp =np.random.exponential(mean,size=10000)
# cumulative distribution from the random sample drawn from an exponential distribution
x_theor_exp,y_theor_exp = cdf(samples_exp)
length of numpy array: 460
x contains: [ 0.96212275 0.96215201 0.96422574 0.96535558 0.96775028 0.96782285
0.97031269 0.97357523 0.98431623 0.99313036 0.99504903 0.99710969
0.99938085 0.99938085 0.99941975 1.00305934 1.00384369 1.00524742
1.00617543 1.0074493 1.01109719 1.01241359 1.01366613 1.01669021
1.01874464 1.02096297 1.02124475 1.02436606 1.0249088 1.02776744
1.04199276 1.06811061 1.09028547 1.09161794 1.180247 1.18558287
1.18615745 1.19051976 1.19445218 1.19635792 1.19853918 1.20112985
1.20815968 1.21154522 1.21204455 1.21278916 1.21304259 1.23188459
1.2326993 1.23311189 1.23586741 1.23769927 1.23769927 1.23931237
1.25132283 1.25185092 1.25232073 1.25442106 1.25557286 1.25872608
1.27458089 1.28110526 1.30037203 1.30161187 1.31533618 1.34574328
1.35268401 1.37506266 1.37840675 1.38361578 1.39639377 1.3974763
1.39887269 1.40728243 1.42321437 1.42899382 1.45618686 1.46968917
1.4877111 1.48828545 1.48853354 1.5057231 1.50813684 1.51779572
1.52678186 1.53754895 1.56082288 1.56556358 1.58838384 1.6205112
1.62093008 1.63122579 1.67455164 1.75876966 1.78213544 1.91202023
1.99354834 2.02603894 2.03237534 2.03310899 2.03387706 2.03827692
2.04175265 2.06590165 2.07374173 2.07845939 2.09603277 2.09756283
2.11637737 2.15310799 2.19701124 2.31043456 2.48320444 2.6051695
2.60546074 2.63503558 2.67348844 2.81181517 2.8274529 2.91808508
2.98121857 2.99611768 3.01150036 3.0186935 3.03694391 3.04689156
3.04842341 3.07490225 3.08043724 3.09671451 3.10082644 3.1029236
3.11533274 3.11563961 3.1594567 3.19172462 3.36060382 3.40433111
3.49888739 3.54609671 3.60716633 3.62358264 3.65716681 3.65889407
3.70180378 3.71293356 3.72679785 3.75198327 3.7763808 3.79814995
3.80545142 3.80545142 3.91504941 4.03174361 4.07284701 4.1080737
4.12748929 4.17238166 4.18712686 4.19844994 4.25476181 4.26041208
4.265647 4.31724291 4.34796054 4.37041264 4.42434252 4.45980201
4.48113927 4.49798072 4.50073142 4.50728421 4.51034251 4.51425516
4.5164519 4.51874666 4.53531866 4.54033711 4.55501848 4.57937548
4.59650869 4.60501527 4.65786381 4.67226469 4.67269135 4.68632042
4.71679402 4.72900739 4.73625329 4.73792469 4.73948642 4.73995832
4.75428461 4.76130434 4.77224778 4.77341879 4.77636659 4.7825778
4.78507352 4.79361784 4.79515217 4.80218722 4.81654258 4.82448459
4.8280072 4.83358593 4.83939848 4.84308671 4.84399465 4.86076621
4.86374122 4.86460744 4.8682864 4.88493833 4.90516573 4.90685594
4.91119571 4.91629034 4.92121068 4.92894861 4.93735483 4.95048826
4.95165068 4.95258967 4.96486819 4.96736097 4.98308427 4.99759951
5.00210926 5.01867886 5.05574474 5.07772844 5.08299755 5.09904565
5.1292205 5.14706551 5.15028757 5.15040291 5.16545028 5.18364312
5.18724436 5.23390116 5.23420328 5.25742149 5.31431353 5.37756409
5.37910945 5.39284203 5.4975104 5.54458268 5.57698888 5.57975367
5.68208683 5.78647524 5.80372568 6.04854434 6.16558884 6.2235995
6.25344749 6.26104965 6.27907857 6.34534743 6.36203602 6.38982712
6.44270371 6.48714274 6.49527838 6.51685127 6.53937818 6.55236284
6.55427327 6.5612497 6.61813813 6.62240814 6.65460714 6.66597523
6.73946578 6.76694386 6.77463404 6.80825157 6.80894858 6.83587292
6.84452553 6.88135725 6.89025148 6.95031485 7.01676451 7.01676451
7.03058385 7.03290787 7.05507221 7.09036954 7.09104071 7.11927839
7.1682088 7.17072743 7.20596282 7.20960466 7.22726088 7.2531362
7.25575965 7.27253371 7.35655631 7.37226201 7.4155597 7.44297142
7.4955584 7.51957928 7.53482091 7.5726243 7.59438378 7.64827981
7.67124461 7.73690209 7.76992944 7.81683922 7.83742571 7.86122713
7.87703067 7.87737386 7.8798765 7.89380928 7.91165778 7.91312678
7.93805402 7.96070641 7.96308048 7.96452284 7.99635017 8.02597605
8.04014906 8.0520041 8.09263372 8.11130329 8.14653078 8.20351969
8.22531276 8.23290044 8.25670364 8.28313632 8.30418148 8.32733934
8.33240403 8.36612863 8.39187511 8.40377297 8.41655419 8.46622531
8.47453708 8.47933766 8.50308865 8.50379252 8.52108792 8.53117029
8.56458584 8.5783962 8.59765609 8.63180951 8.63887958 8.67166459
8.73214487 8.75486771 8.75486771 8.79872443 8.81507652 8.82149925
8.84810829 8.89077751 8.98158507 8.99678974 9.03456063 9.05360389
9.05986976 9.07499444 9.07826486 9.20118266 9.22494456 9.25943572
9.2863759 9.29865807 9.33639926 9.34494064 9.38510431 9.41157165
9.45409305 9.45798287 9.45798287 9.46034317 9.47244539 9.50435509
9.52042551 9.57253574 9.5736016 9.66729216 9.67051858 9.70190825
9.75503346 9.76670679 9.77389022 9.84346382 9.88378281 9.92431318
9.94945079 9.95530365 9.98467306 10.01404556 10.06400856 10.08833342
10.09150536 10.09929206 10.10952649 10.12657597 10.13044979 10.13044979
10.145067 10.15135215 10.28553186 10.43863195 10.8568909 10.95180879
10.99520486 11.1286446 11.15086842 11.18425898 11.26922699 11.35256284
11.39424042 11.40835414 11.57073176 11.66788321 11.99629088 12.16147423
12.21242826 12.23894889 12.4029611 12.44528965 12.48426299 12.49074405
12.54069202 12.55530914 12.58887916 12.60746996 12.67040322 12.69623712
12.72959765 12.7746683 12.78303323 12.89371065 12.91763669 12.9468385
13.02680161 13.1486046 13.23667956 13.2604596 13.38130831 13.46584277
13.57337571 13.58611211 13.58741784 13.82359723]
y contains: [0.00217391 0.00434783 0.00652174 0.00869565 0.01086957 0.01304348
0.01521739 0.0173913 0.01956522 0.02173913 0.02391304 0.02608696
0.02826087 0.03043478 0.0326087 0.03478261 0.03695652 0.03913043
0.04130435 0.04347826 0.04565217 0.04782609 0.05 0.05217391
0.05434783 0.05652174 0.05869565 0.06086957 0.06304348 0.06521739
0.0673913 0.06956522 0.07173913 0.07391304 0.07608696 0.07826087
0.08043478 0.0826087 0.08478261 0.08695652 0.08913043 0.09130435
0.09347826 0.09565217 0.09782609 0.1 0.10217391 0.10434783
0.10652174 0.10869565 0.11086957 0.11304348 0.11521739 0.1173913
0.11956522 0.12173913 0.12391304 0.12608696 0.12826087 0.13043478
0.1326087 0.13478261 0.13695652 0.13913043 0.14130435 0.14347826
0.14565217 0.14782609 0.15 0.15217391 0.15434783 0.15652174
0.15869565 0.16086957 0.16304348 0.16521739 0.1673913 0.16956522
0.17173913 0.17391304 0.17608696 0.17826087 0.18043478 0.1826087
0.18478261 0.18695652 0.18913043 0.19130435 0.19347826 0.19565217
0.19782609 0.2 0.20217391 0.20434783 0.20652174 0.20869565
0.21086957 0.21304348 0.21521739 0.2173913 0.21956522 0.22173913
0.22391304 0.22608696 0.22826087 0.23043478 0.2326087 0.23478261
0.23695652 0.23913043 0.24130435 0.24347826 0.24565217 0.24782609
0.25 0.25217391 0.25434783 0.25652174 0.25869565 0.26086957
0.26304348 0.26521739 0.2673913 0.26956522 0.27173913 0.27391304
0.27608696 0.27826087 0.28043478 0.2826087 0.28478261 0.28695652
0.28913043 0.29130435 0.29347826 0.29565217 0.29782609 0.3
0.30217391 0.30434783 0.30652174 0.30869565 0.31086957 0.31304348
0.31521739 0.3173913 0.31956522 0.32173913 0.32391304 0.32608696
0.32826087 0.33043478 0.3326087 0.33478261 0.33695652 0.33913043
0.34130435 0.34347826 0.34565217 0.34782609 0.35 0.35217391
0.35434783 0.35652174 0.35869565 0.36086957 0.36304348 0.36521739
0.3673913 0.36956522 0.37173913 0.37391304 0.37608696 0.37826087
0.38043478 0.3826087 0.38478261 0.38695652 0.38913043 0.39130435
0.39347826 0.39565217 0.39782609 0.4 0.40217391 0.40434783
0.40652174 0.40869565 0.41086957 0.41304348 0.41521739 0.4173913
0.41956522 0.42173913 0.42391304 0.42608696 0.42826087 0.43043478
0.4326087 0.43478261 0.43695652 0.43913043 0.44130435 0.44347826
0.44565217 0.44782609 0.45 0.45217391 0.45434783 0.45652174
0.45869565 0.46086957 0.46304348 0.46521739 0.4673913 0.46956522
0.47173913 0.47391304 0.47608696 0.47826087 0.48043478 0.4826087
0.48478261 0.48695652 0.48913043 0.49130435 0.49347826 0.49565217
0.49782609 0.5 0.50217391 0.50434783 0.50652174 0.50869565
0.51086957 0.51304348 0.51521739 0.5173913 0.51956522 0.52173913
0.52391304 0.52608696 0.52826087 0.53043478 0.5326087 0.53478261
0.53695652 0.53913043 0.54130435 0.54347826 0.54565217 0.54782609
0.55 0.55217391 0.55434783 0.55652174 0.55869565 0.56086957
0.56304348 0.56521739 0.5673913 0.56956522 0.57173913 0.57391304
0.57608696 0.57826087 0.58043478 0.5826087 0.58478261 0.58695652
0.58913043 0.59130435 0.59347826 0.59565217 0.59782609 0.6
0.60217391 0.60434783 0.60652174 0.60869565 0.61086957 0.61304348
0.61521739 0.6173913 0.61956522 0.62173913 0.62391304 0.62608696
0.62826087 0.63043478 0.6326087 0.63478261 0.63695652 0.63913043
0.64130435 0.64347826 0.64565217 0.64782609 0.65 0.65217391
0.65434783 0.65652174 0.65869565 0.66086957 0.66304348 0.66521739
0.6673913 0.66956522 0.67173913 0.67391304 0.67608696 0.67826087
0.68043478 0.6826087 0.68478261 0.68695652 0.68913043 0.69130435
0.69347826 0.69565217 0.69782609 0.7 0.70217391 0.70434783
0.70652174 0.70869565 0.71086957 0.71304348 0.71521739 0.7173913
0.71956522 0.72173913 0.72391304 0.72608696 0.72826087 0.73043478
0.7326087 0.73478261 0.73695652 0.73913043 0.74130435 0.74347826
0.74565217 0.74782609 0.75 0.75217391 0.75434783 0.75652174
0.75869565 0.76086957 0.76304348 0.76521739 0.7673913 0.76956522
0.77173913 0.77391304 0.77608696 0.77826087 0.78043478 0.7826087
0.78478261 0.78695652 0.78913043 0.79130435 0.79347826 0.79565217
0.79782609 0.8 0.80217391 0.80434783 0.80652174 0.80869565
0.81086957 0.81304348 0.81521739 0.8173913 0.81956522 0.82173913
0.82391304 0.82608696 0.82826087 0.83043478 0.8326087 0.83478261
0.83695652 0.83913043 0.84130435 0.84347826 0.84565217 0.84782609
0.85 0.85217391 0.85434783 0.85652174 0.85869565 0.86086957
0.86304348 0.86521739 0.8673913 0.86956522 0.87173913 0.87391304
0.87608696 0.87826087 0.88043478 0.8826087 0.88478261 0.88695652
0.88913043 0.89130435 0.89347826 0.89565217 0.89782609 0.9
0.90217391 0.90434783 0.90652174 0.90869565 0.91086957 0.91304348
0.91521739 0.9173913 0.91956522 0.92173913 0.92391304 0.92608696
0.92826087 0.93043478 0.9326087 0.93478261 0.93695652 0.93913043
0.94130435 0.94347826 0.94565217 0.94782609 0.95 0.95217391
0.95434783 0.95652174 0.95869565 0.96086957 0.96304348 0.96521739
0.9673913 0.96956522 0.97173913 0.97391304 0.97608696 0.97826087
0.98043478 0.9826087 0.98478261 0.98695652 0.98913043 0.99130435
0.99347826 0.99565217 0.99782609 1. ]
length of numpy array: 10000
x contains: [4.50779549e-04 1.05938703e-03 1.57546335e-03 ... 4.76682022e+01
4.83772654e+01 4.90753422e+01]
y contains: [1.000e-04 2.000e-04 3.000e-04 ... 9.998e-01 9.999e-01 1.000e+00]
c) Bonus - Histogram and confidence intervals of portfolio returns
# plot histogram of random sample
plt.hist(samples_exp, bins = 50, density = True, histtype = 'step', color = 'red')
plt.xlabel('portfolio return')
plt.ylabel('f(x)')
plt.title('Probability Density Function')
# note: unlike drawing from the population, bootstrapping draws random sample (with replacement) from the actual data
def bootstrap_replicate_1d(data, func):
# bootstrap from actual data
# draw random sample from actual data with replacement
# sample size is the number of observations of the actual data
bs_sample = np.random.choice(data, len(data))
# return the mean of a draw
return func(bs_sample)
# draw bootstrap replicates
def draw_bs_reps(data, func, size=1):
# initialize a numpy array of the dimension size
bs_replicates = np.empty(size)
# call function bootstrap_replicate_1d
for i in range(size):
bs_replicates[i] = bootstrap_replicate_1d(data,func)
# return a numpy array of the means of draws
return bs_replicates
# bootstrap from actual samples
# note that this is not sample randomly drawn from a theoretical population
bs_replicates = draw_bs_reps(ret_data['cumulative_portfolio_ret'], np.mean, size=10000)
plt.hist(bs_replicates, bins=50, density=True, alpha = .8, color = 'red')
plt.xlabel('portfolio return')
plt.ylabel('P(T = t)')
plt.title('Probability Density Function')
# Bootstrapped confidence intervals
conf_int = np.percentile(bs_replicates,[2.5, 97.5])
print('95% bootstrapped confidence interval =', conf_int, '%')
# Theoretical confidence intervals
conf_int_actual_upper = ret_data['cumulative_portfolio_ret'].mean() + ret_data['cumulative_portfolio_ret'].std()/np.sqrt(ret_data['cumulative_portfolio_ret'].count())*1.96
conf_int_actual_lower = ret_data['cumulative_portfolio_ret'].mean() - ret_data['cumulative_portfolio_ret'].std()/np.sqrt(ret_data['cumulative_portfolio_ret'].count())*1.96
conf_int_actual = [conf_int_actual_lower, conf_int_actual_upper]
print('-'*120)
print('95% theoretical confidence interval =', conf_int_actual, '%')
95% bootstrapped confidence interval = [5.41101065 6.05582014] %
------------------------------------------------------------------------------------------------------------------------
95% theoretical confidence interval = [5.4124659008719584, 6.052354374268256] %
3. Minimum Volatility Portfolio
#May use add objective to ensure minimum zero weighting to individual stocks
min_vol_portfolio=ef.min_volatility()
min_vol_portfolio_wt=ef.clean_weights()
print(min_vol_portfolio_wt)
Plotting.plot_weights(min_vol_portfolio_wt)
{'AMZN': 0.18115, 'MRNA': 0.16974, 'NVAX': 0.0636, 'SE': 0.23508, 'OV8.SI_USD': 0.35042}
# Allocate Portfolio Value in $ as required to show number of shares/stocks to buy,
# also bounds for shorting will affect allocation
# Maximum Sharpe Portfolio Allocation $1000000
da = DiscreteAllocation(min_vol_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print(allocation)
print("Leftover Fund value for the minimum volatility portfolio is ${:.2f}".format(leftover))
{'OV8.SI_USD': 317812, 'SE': 704, 'AMZN': 52, 'MRNA': 396, 'NVAX': 271}
Leftover Fund value for the minimum volatility portfolio is $2495.55
a) If you have USD 1 million now, how much should you invest in each.
{'OV8.SI_USD': 317812, 'SE': 704, 'AMZN': 52, 'MRNA': 396, 'NVAX': 271} Leftover Fund value for the minimum volatility portfolio is $2495.55
min_vol_cla = cla.CLA(mu, sigma)
min_vol_cla.min_volatility()
Plotting.plot_efficient_frontier(min_vol_cla, show_assets="True")
ret_data1 = df.pct_change()[1:]
min_vol_portfolio_wt_list = list(min_vol_portfolio_wt.values())
weighted_returns1 = (min_vol_portfolio_wt_list * ret_data1)
portfolio_ret1 = pd.DataFrame(weighted_returns1.sum(axis=1))
ret_data1 = ret_data1.merge(portfolio_ret1, on="Date", how="left")
ret_data1 = ret_data1.rename(columns={0: "portfolio_ret"})
ret_data1.head()
ret_data1['cumulative_portfolio_ret'] = (ret_data1['portfolio_ret'] + 1).cumprod()
ret_data1['cumulative_spy_ret'] = (ret_data1['AMZN'] + 1).cumprod()
ret_data1.head()
b) Plot a graph showing how your portfolios (minimum volatility portfolios) perform relative to the market (i.e., SPY).
sns.scatterplot('Date', 'cumulative_portfolio_ret', data=ret_data1)
sns.scatterplot('Date', 'cumulative_spy_ret', data=ret_data1)
plt.legend(labels=["Our Portfolio","S&P 500"])
mean = np.mean(ret_data1['cumulative_portfolio_ret'])
x,y = cdf(ret_data1['cumulative_portfolio_ret'])
# random sample from an exponential distribution calibrated with sample mean (parameter for exponential distribution)
samples_exp =np.random.exponential(mean,size=10000)
# cumulative distribution from the random sample drawn from an exponential distribution
x_theor_exp,y_theor_exp = cdf(samples_exp)
length of numpy array: 460
x contains: [0.97462323 0.97663285 0.97769458 0.97856241 0.97874328 0.98043019
0.98136745 0.98466686 0.98757579 0.99461418 0.99599701 0.99612354
0.99612354 0.99728765 1.00216893 1.0034352 1.01050221 1.01176944
1.01256621 1.0134332 1.01359924 1.01584507 1.01775749 1.0194469
1.02219953 1.02462807 1.02755642 1.02778477 1.02884423 1.03563111
1.03809246 1.05282911 1.06353624 1.06647891 1.09074957 1.1115017
1.11315374 1.11530275 1.11570263 1.11738561 1.12143339 1.12621992
1.12654215 1.12712371 1.12789209 1.13055719 1.1319122 1.13277633
1.135272 1.13791023 1.14368967 1.15541273 1.1554305 1.15718251
1.16036313 1.16303934 1.16971767 1.17005493 1.17656007 1.17656007
1.17949784 1.18050639 1.18479648 1.18523989 1.19098662 1.19425838
1.20092716 1.20312988 1.20368004 1.20494471 1.20765561 1.22529623
1.22640779 1.2361494 1.24862129 1.24978884 1.25525759 1.26647905
1.27337748 1.27681411 1.28899429 1.29177686 1.29281442 1.30060421
1.30456081 1.31016255 1.32793509 1.33083253 1.36740014 1.37523099
1.40026912 1.40033917 1.44079214 1.50039171 1.5167404 1.61242454
1.66546599 1.67914664 1.68067253 1.68074651 1.69576799 1.69689037
1.70245136 1.70282011 1.70521098 1.71540864 1.72005395 1.72010745
1.72607447 1.75619657 1.78197758 1.84881323 1.94523297 1.9866555
1.99865199 2.00909692 2.02903973 2.07120646 2.08604596 2.14063533
2.16942443 2.17150703 2.18064561 2.19001694 2.19085916 2.19161958
2.19925914 2.20227293 2.21064918 2.21631468 2.21965484 2.22109564
2.22113682 2.23085043 2.2533766 2.26010678 2.32763017 2.36047336
2.39542999 2.4169719 2.44048741 2.47957472 2.49660447 2.5058391
2.5068449 2.52520435 2.55671372 2.5608347 2.58033827 2.58148684
2.58148684 2.60298555 2.64740826 2.71760216 2.71994323 2.7483877
2.75099482 2.75804791 2.77180785 2.79083187 2.79147917 2.79942303
2.80428523 2.84705214 2.8695024 2.86977044 2.88858876 2.8889153
2.89066472 2.91122749 2.91217179 2.93549609 2.94479599 2.94742585
2.95547323 2.95766832 2.9593583 2.97845708 2.98196932 2.98434708
2.98983478 2.99056957 2.9933386 2.99681058 2.99788802 2.99895645
3.00236031 3.00942841 3.01007445 3.01131113 3.02406564 3.03038537
3.03511752 3.03518775 3.03914617 3.04194054 3.04733489 3.05234379
3.05440239 3.05442074 3.05713576 3.06081623 3.07631321 3.08013664
3.09003803 3.09291263 3.09443502 3.09544283 3.10190123 3.10342443
3.10343308 3.10399118 3.10522708 3.11352929 3.11566394 3.11913438
3.12149076 3.12199562 3.12383662 3.12464176 3.13073066 3.13168048
3.1332474 3.1381497 3.14503195 3.14637179 3.15910718 3.15983205
3.16081375 3.16814321 3.17530829 3.17567927 3.17588848 3.17803839
3.18898893 3.19317059 3.19443606 3.21121207 3.21261037 3.21445639
3.22118595 3.22248301 3.22880524 3.23754007 3.24010049 3.24588221
3.25324026 3.27972278 3.28500427 3.29786516 3.30554736 3.30950808
3.34225252 3.38772418 3.41055361 3.50733769 3.52240784 3.55672977
3.56623457 3.56880876 3.57037369 3.60120079 3.60625534 3.60819864
3.61963971 3.62055013 3.64479801 3.64711019 3.64830074 3.65514886
3.659346 3.66657337 3.66879495 3.68659447 3.69339734 3.69537006
3.72160877 3.72609982 3.72647645 3.7298313 3.73375676 3.74913245
3.75509635 3.75906939 3.75964113 3.77891462 3.78557779 3.79212533
3.79212533 3.79586618 3.81978053 3.82327688 3.82506478 3.83674033
3.84653032 3.85069709 3.86032916 3.88266404 3.88479132 3.89646223
3.89972285 3.91604091 3.91825896 3.92394046 3.94289873 3.94950617
3.96446774 3.97580771 3.9770765 3.98792588 3.99078849 4.0014559
4.00841975 4.01985485 4.04859165 4.05392049 4.05409498 4.06105904
4.06176491 4.06256669 4.06450243 4.07602466 4.09147679 4.10985332
4.12541664 4.13554417 4.13808318 4.14741817 4.15584225 4.1721664
4.17845693 4.1822275 4.20635889 4.21301394 4.21381779 4.21692763
4.24215391 4.24382 4.24383013 4.26133326 4.26825066 4.27342662
4.27657256 4.29078609 4.29549372 4.29638799 4.30598623 4.31258424
4.33199603 4.33267086 4.33749876 4.34136613 4.34982124 4.36449056
4.36872358 4.37154804 4.38846238 4.39196703 4.40351897 4.41652536
4.42559535 4.43420876 4.4414733 4.44462157 4.45113781 4.45113781
4.46211278 4.47526312 4.47626226 4.49628837 4.50637041 4.5162498
4.51872103 4.52188713 4.52460331 4.52764962 4.53309863 4.54828072
4.55281807 4.56151621 4.57572263 4.58880649 4.59158584 4.59158584
4.59652671 4.59855701 4.59912004 4.60247361 4.67286597 4.68313978
4.69101433 4.69256082 4.72370763 4.72397825 4.75687971 4.75776623
4.76018588 4.76940246 4.80781902 4.8261911 4.83736013 4.84404622
4.85379432 4.85817615 4.87870448 4.88459911 4.89773768 4.90367918
4.91215717 4.91215717 4.91750376 4.93083518 4.93869732 4.93987335
4.94101599 4.94496832 4.9568674 5.03720566 5.1727776 5.18145468
5.19496249 5.1987684 5.21265874 5.27739434 5.28191446 5.31319478
5.33443285 5.33902055 5.34256888 5.42440716 5.43084412 5.45929011
5.47549618 5.48348985 5.50274358 5.50629841 5.54059588 5.5489683
5.55005433 5.55781269 5.58220332 5.58310777 5.5938703 5.6140102
5.62242034 5.62867367 5.62911756 5.66154435 5.67808249 5.67989947
5.71155059 5.74366609 5.75882943 5.76397387 5.83046029 5.83506197
5.84717635 5.84872536 5.86444624 5.91044325]
y contains: [0.00217391 0.00434783 0.00652174 0.00869565 0.01086957 0.01304348
0.01521739 0.0173913 0.01956522 0.02173913 0.02391304 0.02608696
0.02826087 0.03043478 0.0326087 0.03478261 0.03695652 0.03913043
0.04130435 0.04347826 0.04565217 0.04782609 0.05 0.05217391
0.05434783 0.05652174 0.05869565 0.06086957 0.06304348 0.06521739
0.0673913 0.06956522 0.07173913 0.07391304 0.07608696 0.07826087
0.08043478 0.0826087 0.08478261 0.08695652 0.08913043 0.09130435
0.09347826 0.09565217 0.09782609 0.1 0.10217391 0.10434783
0.10652174 0.10869565 0.11086957 0.11304348 0.11521739 0.1173913
0.11956522 0.12173913 0.12391304 0.12608696 0.12826087 0.13043478
0.1326087 0.13478261 0.13695652 0.13913043 0.14130435 0.14347826
0.14565217 0.14782609 0.15 0.15217391 0.15434783 0.15652174
0.15869565 0.16086957 0.16304348 0.16521739 0.1673913 0.16956522
0.17173913 0.17391304 0.17608696 0.17826087 0.18043478 0.1826087
0.18478261 0.18695652 0.18913043 0.19130435 0.19347826 0.19565217
0.19782609 0.2 0.20217391 0.20434783 0.20652174 0.20869565
0.21086957 0.21304348 0.21521739 0.2173913 0.21956522 0.22173913
0.22391304 0.22608696 0.22826087 0.23043478 0.2326087 0.23478261
0.23695652 0.23913043 0.24130435 0.24347826 0.24565217 0.24782609
0.25 0.25217391 0.25434783 0.25652174 0.25869565 0.26086957
0.26304348 0.26521739 0.2673913 0.26956522 0.27173913 0.27391304
0.27608696 0.27826087 0.28043478 0.2826087 0.28478261 0.28695652
0.28913043 0.29130435 0.29347826 0.29565217 0.29782609 0.3
0.30217391 0.30434783 0.30652174 0.30869565 0.31086957 0.31304348
0.31521739 0.3173913 0.31956522 0.32173913 0.32391304 0.32608696
0.32826087 0.33043478 0.3326087 0.33478261 0.33695652 0.33913043
0.34130435 0.34347826 0.34565217 0.34782609 0.35 0.35217391
0.35434783 0.35652174 0.35869565 0.36086957 0.36304348 0.36521739
0.3673913 0.36956522 0.37173913 0.37391304 0.37608696 0.37826087
0.38043478 0.3826087 0.38478261 0.38695652 0.38913043 0.39130435
0.39347826 0.39565217 0.39782609 0.4 0.40217391 0.40434783
0.40652174 0.40869565 0.41086957 0.41304348 0.41521739 0.4173913
0.41956522 0.42173913 0.42391304 0.42608696 0.42826087 0.43043478
0.4326087 0.43478261 0.43695652 0.43913043 0.44130435 0.44347826
0.44565217 0.44782609 0.45 0.45217391 0.45434783 0.45652174
0.45869565 0.46086957 0.46304348 0.46521739 0.4673913 0.46956522
0.47173913 0.47391304 0.47608696 0.47826087 0.48043478 0.4826087
0.48478261 0.48695652 0.48913043 0.49130435 0.49347826 0.49565217
0.49782609 0.5 0.50217391 0.50434783 0.50652174 0.50869565
0.51086957 0.51304348 0.51521739 0.5173913 0.51956522 0.52173913
0.52391304 0.52608696 0.52826087 0.53043478 0.5326087 0.53478261
0.53695652 0.53913043 0.54130435 0.54347826 0.54565217 0.54782609
0.55 0.55217391 0.55434783 0.55652174 0.55869565 0.56086957
0.56304348 0.56521739 0.5673913 0.56956522 0.57173913 0.57391304
0.57608696 0.57826087 0.58043478 0.5826087 0.58478261 0.58695652
0.58913043 0.59130435 0.59347826 0.59565217 0.59782609 0.6
0.60217391 0.60434783 0.60652174 0.60869565 0.61086957 0.61304348
0.61521739 0.6173913 0.61956522 0.62173913 0.62391304 0.62608696
0.62826087 0.63043478 0.6326087 0.63478261 0.63695652 0.63913043
0.64130435 0.64347826 0.64565217 0.64782609 0.65 0.65217391
0.65434783 0.65652174 0.65869565 0.66086957 0.66304348 0.66521739
0.6673913 0.66956522 0.67173913 0.67391304 0.67608696 0.67826087
0.68043478 0.6826087 0.68478261 0.68695652 0.68913043 0.69130435
0.69347826 0.69565217 0.69782609 0.7 0.70217391 0.70434783
0.70652174 0.70869565 0.71086957 0.71304348 0.71521739 0.7173913
0.71956522 0.72173913 0.72391304 0.72608696 0.72826087 0.73043478
0.7326087 0.73478261 0.73695652 0.73913043 0.74130435 0.74347826
0.74565217 0.74782609 0.75 0.75217391 0.75434783 0.75652174
0.75869565 0.76086957 0.76304348 0.76521739 0.7673913 0.76956522
0.77173913 0.77391304 0.77608696 0.77826087 0.78043478 0.7826087
0.78478261 0.78695652 0.78913043 0.79130435 0.79347826 0.79565217
0.79782609 0.8 0.80217391 0.80434783 0.80652174 0.80869565
0.81086957 0.81304348 0.81521739 0.8173913 0.81956522 0.82173913
0.82391304 0.82608696 0.82826087 0.83043478 0.8326087 0.83478261
0.83695652 0.83913043 0.84130435 0.84347826 0.84565217 0.84782609
0.85 0.85217391 0.85434783 0.85652174 0.85869565 0.86086957
0.86304348 0.86521739 0.8673913 0.86956522 0.87173913 0.87391304
0.87608696 0.87826087 0.88043478 0.8826087 0.88478261 0.88695652
0.88913043 0.89130435 0.89347826 0.89565217 0.89782609 0.9
0.90217391 0.90434783 0.90652174 0.90869565 0.91086957 0.91304348
0.91521739 0.9173913 0.91956522 0.92173913 0.92391304 0.92608696
0.92826087 0.93043478 0.9326087 0.93478261 0.93695652 0.93913043
0.94130435 0.94347826 0.94565217 0.94782609 0.95 0.95217391
0.95434783 0.95652174 0.95869565 0.96086957 0.96304348 0.96521739
0.9673913 0.96956522 0.97173913 0.97391304 0.97608696 0.97826087
0.98043478 0.9826087 0.98478261 0.98695652 0.98913043 0.99130435
0.99347826 0.99565217 0.99782609 1. ]
length of numpy array: 10000
x contains: [3.87700059e-04 5.09440911e-04 1.26853156e-03 ... 2.52743582e+01
2.73453044e+01 3.37201914e+01]
y contains: [1.000e-04 2.000e-04 3.000e-04 ... 9.998e-01 9.999e-01 1.000e+00]
c) Bonus: Histogram and confidence intervals of portfolio returns
# plot histogram of random sample
plt.hist(samples_exp, bins = 50, density = True, histtype = 'step', color = 'red')
plt.xlabel('portfolio return')
plt.ylabel('f(x)')
plt.title('Probability Density Function')
# note: unlike drawing from the population, bootstrapping draws random sample (with replacement) from the actual data
def bootstrap_replicate_1d(data, func):
# bootstrap from actual data
# draw random sample from actual data with replacement
# sample size is the number of observations of the actual data
bs_sample = np.random.choice(data, len(data))
# return the mean of a draw
return func(bs_sample)
# draw bootstrap replicates
def draw_bs_reps(data, func, size=1):
# initialize a numpy array of the dimension size
bs_replicates = np.empty(size)
# call function bootstrap_replicate_1d
for i in range(size):
bs_replicates[i] = bootstrap_replicate_1d(data,func)
# return a numpy array of the means of draws
return bs_replicates
# bootstrap from actual samples
# note that this is not sample randomly drawn from a theoretical population
bs_replicates1 = draw_bs_reps(ret_data1['cumulative_portfolio_ret'], np.mean, size=10000)
plt.hist(bs_replicates1, bins=50, density=True, alpha = .8, color = 'red')
plt.xlabel('portfolio return')
plt.ylabel('P(T = t)')
plt.title('Probability Density Function')
# Bootstrapped confidence intervals
conf_int1 = np.percentile(bs_replicates1,[2.5, 97.5])
print('95% bootstrapped confidence interval =', conf_int1, '%')
# Theoretical confidence intervals
conf_int_actual_upper1 = ret_data1['cumulative_portfolio_ret'].mean() + ret_data1['cumulative_portfolio_ret'].std()/np.sqrt(ret_data1['cumulative_portfolio_ret'].count())*1.96
conf_int_actual_lower1 = ret_data1['cumulative_portfolio_ret'].mean() - ret_data1['cumulative_portfolio_ret'].std()/np.sqrt(ret_data1['cumulative_portfolio_ret'].count())*1.96
conf_int_actual1 = [conf_int_actual_lower1, conf_int_actual_upper1]
print('-'*120)
print('95% theoretical confidence interval =', conf_int_actual1, '%')
95% bootstrapped confidence interval = [3.05870556 3.31280338] %
------------------------------------------------------------------------------------------------------------------------
95% theoretical confidence interval = [3.058450759241934, 3.3160417579349355] %
df = df.stack()
df.head()
df = df.reset_index()
df = df.rename(columns={"level_1": "ticker", 0: "price"})
df.head()
df = df.set_index('Date')
df['ret'] = df.groupby('ticker').pct_change()
df = df.dropna()
df.head()
sns.scatterplot('Date', 'price', data=df, hue='ticker')