Hey everyone!

Attached below is my code. It has worked well in the Quant book but when put into my algorithm and during backtesting, it keeps telling me the data coming from the ‘_black_litterman_’ function * ‘ must be 1 dimensional’*, when I'm assured it is. Can someone help me understand my problem and why it keeps failing during backtesting?

NOTE : The problematic line seems to be line 92 : starting *‘mu_bl = …’*

```
import pandas as pd
import numpy as np
from scipy.optimize import minimize
from sklearn.linear_model import ElasticNet
from sklearn.model_selection import GridSearchCV
from numpy.linalg import inv
from AlgorithmImports import *
from datetime import datetime
from datetime import timedelta
from io import StringIO
import matplotlib as mpl
from matplotlib import cm
import matplotlib.pyplot as plt
from arch import arch_model
class ghostyfund(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020,1,1)
self.SetEndDate(2021,1,1)
self.SetCash(10000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.UniverseSettings.Leverage = 3
self._changes = None
def implied_returns(self, delta, sigma, w):
"""
Inputs:
delta = Risk aversion coefficient (scalar) (excess return on the market / variance of the market)
w = portfolio weights (N x 1) as Series
sigma = N x N covariance matrix as df
Returns a (N x 1) vector of implied expected returns by reverse engineering the portfolio weights
"""
ir = delta * sigma.values.dot(w).squeeze()
return ir
def proportional_prior(self, sigma, tau, p, index):
"""
Inputs:
sigma = N x N covariance matrix as df
tau = a scalar
p = a K x N df linking Q & Assets
Returns a (P x P) df, a matrix representing prior uncertainties
"""
helit_omega = p.values.dot(tau * sigma.values).dot(p.values.T)
return pd.DataFrame(np.diag(np.diag(helit_omega)), index=index, columns=index)
def _black_litterman_(self, w_prior, sigma_prior, p, p_not_values, q, omega=None, delta=2.5, tau=0.2):
"""
Computes the black litterman expected returns
W.prior must be an N x 1 vector of weights, series
Sigma.prior is an N x N covariance matrix, a df
P must be an K x N linking Q and the assets, a df
Q must be a K x 1 vector of views, a series
Omega must be a K x K matrix, a df
if omega == None:
its proportional to the variance of the prior
Delta, tau are scalars
"""
index = sigma_prior.index
def as_column_vec(x):
if (x.ndim == 2):
return x
else:
return np.expand_dims(x, axis=1)
if omega is None:
omega = self.proportional_prior(sigma_prior, tau, p_not_values, index)
# no of assets
N = w_prior.shape[0]
# no of views
K = q.shape[0]
# 1) Reverse engineer weights to get pi
pi = self.implied_returns(delta, sigma_prior, w_prior)
# 2) Adjust (scale) sigma by the uncertainty scale factor
sigma_prior_scaled = sigma_prior.values * tau
sigma_prior = sigma_prior.values
# note; .dot == matrix multiplication
mu_bl = pi + sigma_prior_scaled.dot(p.T).dot(inv(p.dot(sigma_prior_scaled).dot(p.T) + omega).dot(q - p.dot(pi)))
# posterior estimate of uncertainty of mu_bl
sigma_bl = sigma_prior + sigma_prior_scaled - sigma_prior_scaled.dot(p.T).dot(inv(p.dot(sigma_prior_scaled).dot(p.T) + omega)).dot(p).dot(sigma_prior_scaled)
return (mu_bl, sigma_bl)
def black_litty_implied_rets(self, tickers):
q = pd.Series(index=tickers, data=[0.03]*int(len(tickers)))
p = pd.DataFrame(columns=tickers, index=range(0, len(tickers)))
w = pd.Series({}, index=tickers).fillna(float(1/len(tickers)))
delta = 2.5
tau = .02
for i in range(0, len(p.columns)):
p.iloc[i, i] = 1
p = p.fillna(0)
history = self.History(tickers, 60, Resolution.Daily)["close"]
sigma_prior = history.pct_change().unstack(level=0).dropna().cov()
index = sigma_prior.index
pi = self.implied_returns(delta, sigma_prior, w)
omega = self.proportional_prior(sigma_prior, tau, p, index)
bl = self._black_litterman_(w_prior=w, sigma_prior=sigma_prior, p=p.values, p_not_values=p, q=q)
df = pd.DataFrame(bl[0])
df["Ticker"] = tickers
df = df.pivot_table(columns="Ticker")
exp_rets = df.sort_values(by=0, ascending=False, axis=1)
#highest_exp_ret = exp_rets.iloc[0].index[0]
return exp_rets
def monte_carlo_gbm(self, n_years, n_scenarios, mu, sigma, steps_per_year, s_0):
"""
Evolution of Geometric Brownian Motion trajectories, such as for Stock Prices through Monte Carlo
:param n_years: The number of years to generate data for
:param n_paths: The number of scenarios/trajectories
:param mu: Annualized Drift, e.g. Market Return
:param sigma: Annualized Volatility
:param steps_per_year: granularity of the simulation
:param s_0: initial value
:return: a numpy array of n_paths columns and n_years*steps_per_year rows
"""
dt = 1/steps_per_year
n_steps = int(n_years*steps_per_year) + 1
rets_plus_1 = np.random.normal(loc=mu*dt+1, scale=sigma*np.sqrt(dt), size=(n_steps, n_scenarios))
rets_plus_1[0] = 1
prices = s_0*pd.DataFrame(rets_plus_1).cumprod()
return prices
def get_max_drawdown(self, array):
drawdowns = []
for i in range(len(array)):
max_array = max(array[:i+1])
drawdown = max_array - array[i]
drawdowns.append(drawdown)
return max(drawdowns)
def CoarseSelectionFunction(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in sortedByDollarVolume]
def FineSelectionFunction(self, fine):
sortedByPeRatio = sorted(fine, key=lambda x: x.ValuationRatios.PERatio)
tickers = [x.Symbol for x in sortedByPeRatio][:5]
return tickers
def OnData(self, data):
if self._changes is None:
return
tickers = [x.Symbol for x in self._changes.AddedSecurities]
mu = self.black_litty_implied_rets(tickers)
iteration = 0
avg_drawdowns = []
for ticker in tickers:
symbol = self.AddEquity(ticker).Symbol
symbol_hist = self.History(symbol, 252, Resolution.Daily)["close"]
returns = symbol_hist.pct_change().dropna().unstack(level=0)
vol = returns.rolling(window=30, center=False).std().dropna()
am = arch_model(vol, vol='Garch', p=1, o=0, q=1, dist='Normal')
res = am.fit(disp='off')
garch_implied_vol = 0.1 * np.sqrt(res.params['omega'] + res.params['alpha[1]'] * res.resid**2 +
res.conditional_volatility**2 * res.params['beta[1]'])
mc = self.monte_carlo_gbm(n_years=1, n_scenarios=2500,
mu=mu.iloc[:, iteration], sigma=garch_implied_vol.max(), steps_per_year=252, s_0=100)
drawdowns = []
for i in range(0, len(mc.columns)):
drawdown = self.get_max_drawdown(mc[i])
drawdowns.append(drawdown)
avg_drawd = np.mean(drawdowns)
avg_drawdowns.append(avg_drawd)
iteration += 1
df = pd.DataFrame(avg_drawdowns)
df["Ticker"] = tickers
df = df.pivot_table(columns="Ticker")
expected_drawdowns = df.sort_values(by=0, ascending=True, axis=1)
ticker_symbol = expected_drawdowns.columns[0]
self.SetHoldings(ticker_symbol, 1)
self._changes = None
def OnSecuritiesChanged(self, changes):
self._changes = changes
```

Louis Szeto

Hi N@tty

In line 86, you're doing matrix arithmetic operations with vectors/matrices in unmatched shape. The variable `pi` and `q` were 1-d data with shape (5,), while the others were 2-d data with shape (5, 5). So, they can't do dot product. You could solve it by (1) convert all vectors/matrices in pandas format into np.array, then (2) reshape all 1-d vectors by

.reshape(-1, 1)Best

Louis

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.

N@tty

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.

To unlock posting to the community forums please complete at least 30% of Boot Camp.

You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!