Bear vs. Bull Portfolio Risk/Return Optimization QC Analysis

Based on the Portfolio Optimization Algorithm (POA) discussed earlier and the related portfolio management, let’s run the Bear vs. Bull QC test of the portfolio P=[MSFT, AAPL, NDAQ] in terms of the Risk/Return Ratio (RRR).

Content:

  • Stock prices are rising in a bull market and declining in a bear market. The stock market under bullish conditions is consistently gaining value, even with some brief market corrections. The stock market under bearish conditions is losing value or holding steady at depressed prices.
  • Low interest rates typically accompany bull markets, while high interest rates are associated with bear markets. Low interest rates make it more affordable for businesses to borrow money and grow, while high interest rates tend to slow companies’ expansions.
  1. MSFT
  2. AAPL
  3. NDAQ
  4. Barchart Opinion
  5. Portfolio Allocation
  6. Portfolio Optimization
  7. Summary
  8. Conclusions
  9. Explore More
The Motley Fool Bear vs. Bull
Source: The Motley Fool

MSFT

  • The bears believe Microsoft’s business is still exposed to macroeconomic headwinds. 
  • Wall Street remains overwhelmingly bullish on Microsoft. Analysts expect its revenue and adjusted earnings per share (EPS) to rise 19% and 16%, respectively, in fiscal 2022.
  • In fiscal 2023, they expect its revenue to increase 14% as its adjusted EPS grows another 16%. 

AAPL

  • Bull case: Innovation spanning decades.
  • While Apple has done an excellent job creating sought-after consumer electronics like the iPod, iPad, AirPods, Apple Watch, etc., it’s still largely dependent on the iPhone. 
  • In its most recent quarter, the iPhone comprised 52% of the company’s overall sales. That’s not even including all the attachments that go along with it. The risk is that if Apple doesn’t continue its iPhone success, revenue growth could stall or even reverse. 

NDAQ

  • All three of the major U.S. stock indexes are now entrenched in a bear market
  • While there’s no denying that bear markets can test the resolve of both tenured and new investors, history also shows that they’re the ideal opportunity to go hunting for bargains. No matter how bad things may appear for the leading indexes, a bull market rally eventually recoups all that was lost.

Barchart Opinion

Price Information

CompanyMicrosoft CorpApple IncNasdaq Inc
Change+8.57+3.37+0.47
% Change+3.80%+2.44%+0.82%
Weighted Alpha-27.20-9.20-10.80
A positive weighted alpha shows that the security produced a return greater than the benchmark; a negative measure indicates the converse.
Today’s Opinion100% Sell72% Sell24% Buy

Performance 1-Month

%Chg-7.51% since 09/13/22-8.48% since 09/13/22-6.11% since 09/13/22

Ratios

Price/Earnings ttm24.5022.9322.49
The market average P/E ratio currently ranges from 20-25, so a higher PE above that could be considered bad, while a lower PE ratio could be considered better. A higher P/E ratio means you are paying more to purchase a share of the company’s earnings.
Comparison of MSFT (black), AAPL (blue), and NDAQ (orange) charts
MSFT (black), AAPL (blue), and NDAQ (orange) charts

Portfolio Allocation

Let’s set the working directory YOURPATH and import the key libraries

import os
os.chdir(‘C:/Users/adrou/OneDrive/Documents/PORTFOLIOJAYA’) # Set working directory
os. getcwd()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import pandas_datareader.data as web
import datetime

Let’s define the time interval

start = datetime.datetime(2020,1,1)
end = datetime.datetime(2022,10,10)

and the 3 stock datasets

data1 = web.DataReader(‘MSFT’,’yahoo’,start,end)
data2 = web.DataReader(‘AAPL’,’yahoo’,start,end)
data3 = web.DataReader(‘NDAQ’,’yahoo’,start,end)

data1.tail()

Input MSFT stock data table

Let’s estimate normalized returns
for s_df in (data1,data2,data3):
s_df[‘Normalized Return’] = s_df[‘Adj Close’]/s_df.iloc[0][‘Adj Close’]

data1.tail()

Input MSFT stock data table with normalized returns

Allocations are as follows:
for s_df, allocation in zip((data1,data2,data3),[0.5,0.3,0.2]):
s_df[‘Allocation’] = s_df[‘Normalized Return’]*allocation

data1.tail()

Input MSFT stock data table with normalized returns and allocations

Let’s set

investment = 100000

and get the position value based on allocations

for s_df in (data1,data2,data3):
s_df[‘Position value’] = s_df[‘Allocation’] * investment

data1.tail()

Input MSFT stock data table with normalized returns and allocations + position values

Now let’s look at the position values for all the stocks
position_values = pd.concat([data1[‘Position value’],
data2[‘Position value’],data3[‘Position value’]],axis=1)

position_values.columns = [‘MSFT position values’,’AAPL position values’,
‘NDAQ position values’]
position_values.tail()

 Position values for all the stocks

The total position can now be calculated by summing the values of all stocks position values
position_values[‘Total position value’] = position_values.sum(axis=1)
position_values.tail()

Total position value

position_values[‘Total position value’].plot(figsize=(10,8))
plt.title(‘Total portfolio value’)

Total portfolio value

position_values.drop(‘Total position value’,axis=1).plot(figsize=(10,8))

MSF, AAPL and NDAQ position values

Portfolio statistics:
position_values[‘Daily return’] = position_values[‘Total position value’].pct_change(1)
position_values.tail()

Daily return table

The mean daily return is given by

position_values[‘Daily return’].mean()

0.0009057958832049809

The Sharpe ratio is
sharpe_r = position_values[‘Daily return’].mean()/position_values[‘Daily return’].std()
sharpe_r

0.04543968353367672

and the annual Sharpe ratio is

annual_sharpe_r = sharpe_r * (252**0.5)
annual_sharpe_r

0.7213326137015117

Recall that A Sharpe ratio of less than one is considered unacceptable or bad. The risk your portfolio encounters isn’t being offset well enough by its return. The higher the Sharpe ratio, the better.

Portfolio Optimization

Let’s apply the revised portfolio management algorithm to our stocks. Firstly, the return will be plotted against volatility based on the Sharpe ratio. Secondly, the Python SciPy module will be used to create the return-volatility plot using the SLSQP optimization scheme.

Let’s continue the story by combining Adj Close columns of our 3 datasets

df1=data1[‘Adj Close’]
df2=data2[‘Adj Close’]
df3=data3[‘Adj Close’]
df = pd.concat([df1,df2,df3],axis=1)
df.columns = [‘MSFT’,’AAPL’,’NDAQ’]

df.tail()

Adj Close columns of MSFT AAPL and NDAQ

Let’s check statistics.

Mean daily return:

Using the percent change function, the mean daily return of the stocks is
df.pct_change(1).mean()

MSFT    0.000780
AAPL    0.001190
NDAQ    0.000929
dtype: float64

The mean daily return yields the degree of correlation between the stocks

df.pct_change(1).corr()

Correlation matrix

The percentage change method also gives the arithmetic return

Arithmetic return

or in log scale

log_returns = np.log(df/df.shift(1))
log_returns.head()

Log return

Hence, the covariance matrix is

log_returns.cov()

Covariance matrix

Optimization via Randomization – Allocation of random weights to the stocks. The weights are randomly allocated to the stocks and made sure that they all add up to 1. To make sure they add up to 1, the weights are rebalanced by dividing it by the sum of weights:

print(‘The stocks are: ‘,df.columns)
np.random.seed(200)
weights = np.array(np.random.random(3)) #random weights
weights = weights/np.sum(weights)
print(‘Random weights: ‘,weights)

The stocks are:  Index(['MSFT', 'AAPL', 'NDAQ'], dtype='object')
Random weights:  [0.53580931 0.12809422 0.33609646]

The annual expected return is

expected_return = np.sum((log_returns.mean()* weights) * 252)
expected_return

0.16570984130750205

The annual expected volatility is

expected_vol = np.sqrt(np.dot(weights.T,np.dot(log_returns.cov()*252,weights)))
expected_vol

0.3059477823851022

Hence, the Sharpe ratio is

sharpe_r = expected_return/expected_vol
sharpe_r

0.541627855628383

Let’s run the random optimization algorithm

np.random.seed(200)

Initalization of variables

portfolio_number = 7000
weights_total = np.zeros((portfolio_number,len(df.columns)))
returns = np.zeros(portfolio_number)
volatility = np.zeros(portfolio_number)
sharpe = np.zeros(portfolio_number)

for i in range(portfolio_number):

# Random weights
weights = np.array(np.random.random(3))
weights = weights/np.sum(weights)
# Append weight
weights_total[i,:] = weights

# Expected return
returns[i] = np.sum((log_returns.mean()* weights) * 252)

# Expected volume
volatility[i] = np.sqrt(np.dot(weights.T,np.dot(log_returns.cov()*252,weights)))

# Sharpe ratio
sharpe[i] = returns[i]/volatility[i]

Let’s calculate the max Sharpe ratio

max_sharpe = sharpe.max()
max_sharpe

0.6818731773912419

max_sharpe_index = sharpe.argmax()
max_sharpe_index

6708

max_sharpe_weights = weights_total[343,:]
max_sharpe_weights

array([6.71707148e-01, 2.83931652e-04, 3.28008920e-01])

Let’s calculate the corresponding max expected return and volatility

max_sharpe_return = returns[max_sharpe_index]
max_sharpe_return

0.21046030001983235

max_sharpe_vol = volatility[max_sharpe_index]
max_sharpe_vol

0.3086502109160916

Let’s plot the Return-Volatility map as the scatter plot

plt.figure(figsize=(12,8))
plt.scatter(volatility,returns,c=sharpe)
plt.colorbar(label=’Sharpe Ratio’)
plt.xlabel(‘Volatility’)
plt.ylabel(‘Return’)

plt.scatter(max_sharpe_vol,max_sharpe_return,c=’red’,s=50)

Random optimization return-volatility vs Sharpe ratio scatter plot

Let’s apply the SLSQP minimization algorithm from scipy.optimize (with constraints, bounds, and the initial guess as input parameters) to verify this result

def stats(weights):
weights = np.array(weights)
expected_return = np.sum((log_returns.mean()* weights) * 252)
expected_vol = np.sqrt(np.dot(weights.T,np.dot(log_returns.cov()*252,weights)))
sharpe_r = expected_return/expected_vol
return np.array([expected_return,expected_vol,sharpe_r])

from scipy.optimize import minimize

def sr_negate(weights):
neg_sr = stats(weights)[2] * -1
return neg_sr
def weight_check(weights):
weights_sum = np.sum(weights)
return weights_sum – 1

constraints = ({‘type’:’eq’,’fun’:weight_check})
bounds = ((0,1),(0,1),(0,1))
initial_guess = [0.3,0.3,0.4]

results = minimize(sr_negate,initial_guess,method=’SLSQP’,bounds=bounds,constraints=constraints)
results

fun: -0.6819487289981644
     jac: array([ 1.87360495e-01, -1.54420733e-04,  1.82241201e-04])
 message: 'Optimization terminated successfully'
    nfev: 20
     nit: 5
    njev: 5
  status: 0
 success: True
       x: array([1.48788148e-16, 5.41313619e-01, 4.58686381e-01])

wt = results.x
wt
stats(wt)

Let’s plot the Return-Volatility map with the volatility frontier envelope

frontier_return = np.linspace(-0.6,0.4,200)
def min_vol(weights):
vol = stats(weights)[1]
return vol
frontier_volatility = []

for exp_return in frontier_return:
constraints = ({‘type’:’eq’,’fun’:weight_check},
{‘type’:’eq’,’fun’:lambda x: stats(x)[0]-exp_return})
result = minimize(min_vol,initial_guess,method=’SLSQP’,bounds=bounds,constraints=constraints)
frontier_volatility.append(result[‘fun’])

plt.figure(figsize=(12,8))
plt.scatter(volatility,returns,c=sharpe)
plt.scatter(max_sharpe_vol,max_sharpe_return,c=’red’,s=200)
plt.scatter(expected_vol,expected_return,c=’black’,s=200)
plt.colorbar(label=’Sharpe Ratio’)
plt.xlabel(‘Volatility’)
plt.ylabel(‘Return’)

plt.plot(frontier_volatility,frontier_return,’r–‘,linewidth=3)
plt.legend([‘SLSQP Optimizer’, ‘Max Sharpe Ratio’,’Expected Sharpe Ratio’,’Frontier Line’])
plt.ylim([0.125, 0.25])

Summary

  • Portfolio & Timing

start = datetime.datetime(2020,1,1)
end = datetime.datetime(2022,10,10)

and the 3 stock datasets imported from Yahoo Finance

web.DataReader(‘MSFT’,’yahoo’,start,end)
web.DataReader(‘AAPL’,’yahoo’,start,end)
web.DataReader(‘NDAQ’,’yahoo’,start,end)

  • Volatility (Max Sharpe Ratio) ~ Volatility (Expected Sharpe Ratio) ~ 0.31
  • Return (Max Sharpe Ratio) ~ 0.21 > Return (Expected Sharpe Ratio) ~ 0.17
  • Expected Sharpe Ratio ~ 0.54 < 1.0
  • Volatility: var(MSFT) ~4.69e-4 > var(AAPL) ~4.1e-4 > var(NDAQ)~2.9e-4
  • Similarity index corr(MSFT,AAPL)~0.81 > corr(MSFT,NAQ)~0.67 ~ corr(AAPL,NAQ) ~0.62
  • the mean daily return of the stocks is (AAPL>MSFT>NDAQ)

MSFT 0.000780

AAPL 0.001190

NDAQ 0.000929

Conclusions

  • A Sharpe ratio of less than one is considered unacceptable or bad. The risk the portfolio P encounters isn’t being offset well enough by its return.
  • The expected return of the portfolio P is lower than the return associated with Max Sharpe Ratio.
  • Poor diversification: three stocks follow more or less the similar trend, as confirmed by the relatively high correlation coefficient above 0.5 and a similar value of variance
  • Stocks have a similar degree of volatility, as shown by their variance
  • The outcome is consistent with the stock-to-sell barchart.com opinion (see above).

Explore More

A TradeSanta’s Quick Guide to Best Swing Trading Indicators

Algorithmic Testing Stock Portfolios to Optimize the Risk/Reward Ratio

Algorithmic Trading using Monte Carlo Predictions and 62 AI-Assisted Trading Technical Indicators (TTI)

Stock Portfolio Risk/Return Optimization

Risk/Return QC via Portfolio Optimization – Current Positions of The Dividend Breeder

Risk/Return POA – Dr. Dividend’s Positions

All Eyes on ETFs Sep ’22

Invest in AI via Macroaxis Sep ’22 Update

Bear Market Similarity Analysis using Nasdaq 100 Index Data

Are Blue-Chips Perfect for This Bear Market?

Track All Markets with TradingView

Basic Stock Price Analysis in Python

S&P 500 Algorithmic Trading with FBProphet

The Qullamaggie’s TSLA Breakouts for Swing Traders

Predicting Trend Reversal in Algorithmic Trading using Stochastic Oscillator in Python

Stock Forecasting with FBProphet

The Zacks’s Steady Investor – A Quick Look

Zacks Insights into the Commodity Bull Market

Zacks Insights into this High Inflation/Rising Rate Market

SeekingAlpha Risk/Reward July Rundown

Inflation-Resistant Stocks to Buy

Macroaxis AI Investment Opportunity

Macroaxis Wealth Optimization

AAPL Stock Technical Analysis 2 June 2022

OXY Stock Analysis, Thursday, 23 June 2022

OXY Stock Update Wednesday, 25 May 2022

OXY Stock Technical Analysis 17 May 2022

Short-Term Stock Market Price Prediction using Deep Learning Models

ML/AI Regression for Stock Prediction – AAPL Use Case

Stocks on Watch Tomorrow

A Weekday Market Research Update

Upswing Resilient Investor Guide

Supervised ML/AI Stock Prediction using Keras LSTM Models

Investment Risk Management Study

RISK AWARE INVESTMENT: GUIDE FOR EVERYONE 

Investopedia

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

$5.00
$15.00
$100.00
$5.00
$15.00
$100.00
$5.00
$15.00
$100.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: