# Start writing code here...e.g., WTF stock # how hidden cases correlated to the industry chosen (if situation is expected to be worsen, choose firm with high correlation to hidden cases)
!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: 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: pytz>=2017.3 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from pandas>=0.24->yfinance) (2021.1)
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: idna<4,>=2.5 in /shared-libs/python3.7/py-core/lib/python3.7/site-packages (from requests>=2.20->yfinance) (3.2)
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: 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: charset-normalizer~=2.0.0 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from requests>=2.20->yfinance) (2.0.4)
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=adb1365942853c81885693716dc4539e298e3481aa5ceef7f083a812c1dd9ab4
Stored in directory: /tmp/pip-ephem-wheel-cache-29banwtc/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=4eb6ef4fe33abed0d94b1a389cd4f9c022f279afe99ed0fc5239df32d369fb6b
Stored in directory: /tmp/pip-ephem-wheel-cache-29banwtc/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
# 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/)
#Medical and health ETF, Zoom (remote working), Paypal (digital wallet), Bitcoin (digital currency), US bond, Bilibili (Video App)
symbols_list = ['PYPL', 'VHT','BTC-USD', 'BILI', 'ZM', 'SPY', 'FB', 'AMZN'] #, '^IRX'
start = dt.datetime(2020,3,1)
end = dt.datetime(2021,9,1)
data = yf.download(symbols_list, start=start, end=end)
[*********************100%***********************] 8 of 8 completed
# filter column adjusted close
# do not use close price as the price might be discountinous due to corporate actions
df = data['Adj Close']
# Unique names (e.g., crypto trade on weekends as well but stock do not)
df = df.ffill()
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 9.0 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 42.0 MB/s
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 46.0 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)
Requirement already satisfied: scipy in /shared-libs/python3.7/py/lib/python3.7/site-packages (from PyPortfolioOpt==1.2.1) (1.7.1)
Collecting scs>=1.1.6
Downloading scs-2.1.4.tar.gz (6.6 MB)
|████████████████████████████████| 6.6 MB 11.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 19.6 MB/s
Collecting ecos>=2
Downloading ecos-2.0.7.post1-cp37-cp37m-manylinux1_x86_64.whl (147 kB)
|████████████████████████████████| 147 kB 52.7 MB/s
Collecting qdldl
Downloading qdldl-0.1.5.post0-cp37-cp37m-manylinux2014_x86_64.whl (941 kB)
|████████████████████████████████| 941 kB 34.3 MB/s
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: 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: 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=fe5f3ca92d2c59f34879bebe908e07a1f03567cdf5b66c15ca9e0d9aa5744a4c
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
from pypfopt import objective_functions
# 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)
PYPL 1
VHT 1
BTC-USD 0
BILI 1
ZM 1
SPY 1
FB 1
AMZN 1
dtype: int64
AMZN 0.303953
BILI 0.675574
BTC-USD 1.032789
FB 0.360631
PYPL 0.511885
SPY 0.212407
VHT 0.197386
ZM 0.625015
dtype: float64
AMZN BILI BTC-USD FB PYPL SPY VHT \
AMZN 0.079474 0.048702 0.044908 0.065383 0.070624 0.038410 0.030785
BILI 0.048702 0.359640 0.054330 0.053274 0.094018 0.042297 0.036689
BTC-USD 0.044908 0.054330 0.446912 0.057130 0.086791 0.049342 0.037791
FB 0.065383 0.053274 0.057130 0.115597 0.091328 0.055625 0.044595
PYPL 0.070624 0.094018 0.086791 0.091328 0.160349 0.065683 0.056558
SPY 0.038410 0.042297 0.049342 0.055625 0.065683 0.052630 0.044760
VHT 0.030785 0.036689 0.037791 0.044595 0.056558 0.044760 0.045776
ZM 0.051069 0.087098 0.046584 0.018863 0.052674 -0.002991 -0.002056
ZM
AMZN 0.051069
BILI 0.087098
BTC-USD 0.046584
FB 0.018863
PYPL 0.052674
SPY -0.002991
VHT -0.002056
ZM 0.400372
# Note max sharpe ratio is the tangency portfolio
# weight bounds in negative allows shorting of stocks
ef = EfficientFrontier(mu, sigma, weight_bounds=(-1,1))
ef.add_objective(objective_functions.L2_reg, gamma=2)
# ef.add_constraint(lambda x : x <= 0.4)
# ef.add_constraint(lambda x : x >= -0.3)
# optional constraints possible, read pypfopt documentation.
sharpe_portfolio=ef.max_sharpe(risk_free_rate=0.00078) # US bond
sharpe_portfolio_wt=ef.clean_weights()
print(sharpe_portfolio_wt)
Plotting.plot_weights(sharpe_portfolio_wt)
{'AMZN': 0.07631, 'BILI': 0.17167, 'BTC-USD': 0.26975, 'FB': 0.0922, 'PYPL': 0.12928, 'SPY': 0.05111, 'VHT': 0.04954, 'ZM': 0.16014}
/root/venv/lib/python3.7/site-packages/pypfopt/efficient_frontier.py:183: UserWarning: max_sharpe transforms the optimisation problem so additional objectives may not work as expected.
"max_sharpe transforms the optimisation problem so additional objectives may not work as expected."
/root/venv/lib/python3.7/site-packages/cvxpy/expressions/expression.py:556: UserWarning:
This use of ``*`` has resulted in matrix multiplication.
Using ``*`` for matrix multiplication has been deprecated since CVXPY 1.1.
Use ``*`` for matrix-scalar and vector-scalar multiplication.
Use ``@`` for matrix-matrix and matrix-vector multiplication.
Use ``multiply`` for elementwise multiplication.
This code path has been hit 1 times so far.
warnings.warn(msg, UserWarning)
#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.04466, 'BILI': 0.29805, 'BTC-USD': 0.61203, 'FB': 0.01546, 'PYPL': 0.1547, 'SPY': -0.13495, 'VHT': -0.14662, 'ZM': 0.24597}
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(sharpe_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print("Discrete allocation:", allocation)
print("Funds remaining: ${:.2f}".format(leftover))
Discrete allocation: {'BTC-USD': 5, 'BILI': 2139, 'ZM': 553, 'PYPL': 447, 'FB': 243, 'AMZN': 22, 'SPY': 113, 'VHT': 188}
Funds remaining: $26107.24
max_sharpe_cla = cla.CLA(mu, sigma)
max_sharpe_cla.max_sharpe()
Plotting.plot_efficient_frontier(max_sharpe_cla, show_assets="True")
max_s = pd.DataFrame.from_dict(allocation,orient='index')
max_s = max_s.rename(columns={0:"no_of_stock"})
max_s["price"] = round(latest_prices,2)
max_s["value"]=round(max_s["no_of_stock"] * max_s["price"],2)
max_s
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(min_vol_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print("Discrete allocation:", allocation)
print("Funds remaining: ${:.2f}".format(leftover))
Discrete allocation: {'BTC-USD': 9, 'BILI': 2801, 'ZM': 640, 'PYPL': 404, 'FB': 31, 'VHT': -513, 'SPY': -274, 'AMZN': -11}
Funds remaining: $25429.60
min_v = pd.DataFrame.from_dict(allocation,orient='index')
min_v = min_v.rename(columns={0:"no_of_stock"})
min_v["price"] = round(latest_prices,2)
min_v["value"]=round(min_v["no_of_stock"] * min_v["price"],2)
min_v
min_vol_cla = cla.CLA(mu, sigma)
min_vol_cla.min_volatility()
Plotting.plot_efficient_frontier(min_vol_cla, show_assets="True")
sharpe_portfolio_wt_list = list(sharpe_portfolio_wt.values())
ret_data_s = df.pct_change()[1:]
weighted_returns_s = (sharpe_portfolio_wt_list * ret_data_s)
portfolio_ret_s = pd.DataFrame(weighted_returns_s.sum(axis=1))
ret_data_s = ret_data_s.merge(portfolio_ret_s, on="Date", how="left")
ret_data_s = ret_data_s.rename(columns={0: "portfolio_ret"})
ret_data_s.head()
ret_data_s['cumulative_portfolio_ret'] = (ret_data_s['portfolio_ret'] + 1).cumprod()
ret_data_s['cumulative_spy_ret'] = (ret_data_s['SPY'] + 1).cumprod()
ret_data_s.tail()
sns.scatterplot('Date', 'cumulative_portfolio_ret', data=ret_data_s)
sns.scatterplot('Date', 'cumulative_spy_ret', data=ret_data_s)
plt.legend(['cumulative_max_sharpe_portfolio_ret','cumulative_spy_ret'])
/shared-libs/python3.7/py/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
FutureWarning
/shared-libs/python3.7/py/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
FutureWarning
min_vol_portfolio_wt_list = list(min_vol_portfolio_wt.values())
ret_data_v = df.pct_change()[1:]
weighted_returns_v = (min_vol_portfolio_wt_list * ret_data_v)
portfolio_ret_v = pd.DataFrame(weighted_returns_v.sum(axis=1))
ret_data_v = ret_data_v.merge(portfolio_ret_v, on="Date", how="left")
ret_data_v = ret_data_v.rename(columns={0: "portfolio_ret"})
ret_data_v.head()
ret_data_v['cumulative_portfolio_ret_v'] = (ret_data_v['portfolio_ret'] + 1).cumprod()
ret_data_v['cumulative_spy_ret_v'] = (ret_data_v['SPY'] + 1).cumprod()
ret_data_v.tail()
sns.scatterplot('Date', 'cumulative_portfolio_ret_v', data=ret_data_v)
sns.scatterplot('Date', 'cumulative_spy_ret_v', data=ret_data_v)
plt.legend(['cumulative_min_vol_portfolio_ret','cumulative_spy_ret'])
/shared-libs/python3.7/py/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
FutureWarning
/shared-libs/python3.7/py/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
FutureWarning
import matplotlib.pyplot as plt
plt.hist(ret_data_s["portfolio_ret"], bins=50, alpha = 0.5)
plt.hist(ret_data_v["portfolio_ret"], bins=50, alpha = 0.5)
plt.legend(["Max_Sharpe", "Min_Vol"])
plt.show()
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_s["portfolio_ret"], np.mean, size=10000)
plt.hist(bs_replicates, bins=50, density=True, color = 'grey')
plt.xlabel('Max_Sharpe portfolio return')
plt.ylabel('X=x')
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_s["portfolio_ret"].mean() + ret_data_s["portfolio_ret"].std()/np.sqrt(ret_data_s["portfolio_ret"].count())*1.96
conf_int_actual_lower = ret_data_s["portfolio_ret"].mean() - ret_data_s["portfolio_ret"].std()/np.sqrt(ret_data_s["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 = [0.00081038 0.00421403] %
------------------------------------------------------------------------------------------------------------------------
95% theoretical confidence interval = [0.0008232653120819201, 0.004234370176426008] %
# bootstrap from actual samples
# note that this is not sample randomly drawn from a theoretical population
bs_replicates = draw_bs_reps(ret_data_v["portfolio_ret"], np.mean, size=10000)
plt.hist(bs_replicates, bins=50, density=True, color = 'grey')
plt.xlabel('Min_volatility porfolio return')
plt.ylabel('X = x')
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_v["portfolio_ret"].mean() + ret_data_v["portfolio_ret"].std()/np.sqrt(ret_data_v["portfolio_ret"].count())*1.96
conf_int_actual_lower = ret_data_v["portfolio_ret"].mean() - ret_data_v["portfolio_ret"].std()/np.sqrt(ret_data_v["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 = [0.00117685 0.00672639]
------------------------------------------------------------------------------------------------------------------------
95% theoretical confidence interval = [0.0011954742834613522, 0.006741831753413308]