Backtesting of different trading strategies by applying different Modern Portfolio Theory (MPT) approaches on long-only ETFs portfolios in Python.
Backtesting trading strategies
Last Update February 25, 2021 ####
Matteo Bottacini, matteo.bottacini@usi.ch ####
Project description
This project is to backtest different trading strategies applying different approaches from the Modern Portfolio Tehory (MPT) in Python 3.
The strategies backtested are:
- The Optimal Markowitz Portfolio;
- The Global Minimum Variance Portfolio;
- The Risk-Parity Portfolio;
- The Equally Weighted Portfolio
The ETFs considered are:
- EEM: iShares MSCI Emerging Markets ETF
- EMLC: VanEck Vectors J.P. Morgan EM Local Currency Bond ETF
- IAU: iShares Gold Trust
- IEF: iShares 7-10 Year Treasury Bond ETF
- IWM: iShares Russell 2000 ETF
- SPY: SPDR S&P 500 ETF Trust
- TIP: iShares TIPS Bond ETF
- TLT: iShares 20+ Year Treasury Bond ETF
- VGK: Vanguard FTSE Europe Index Fund ETF Shares
The scripts do the following:
- Download and analyse financial data;
- Find the optimal Markowtiz portfolio (mean-variance);
- Find the Global Minimum Variance (GMV) portfolio (minimum variance);
- Find the risk-parity portfolio (risk parity);
- Find the equally weighted portfolio (equally weighted);
- Backtest a trading strategy with monthly rebalance with out-of-the-sample results;
- Compare the results.
Folder structure: ~~~~ trading-strategy-backtest/ deliverable/ run_backtest.py src/ equallyweightedportfolio.py meanvarianceportfolio.py minimumvarianceportfolio.py riskparityportoflio.py README.md ~~~~
Trading strategy configuration
Each model is setup in its specific scripts.Equally weighted portfolio ###
def equallyweightedportfolio(ret):
init_weights = [1 / len(ret.columns)] * len(ret.columns)
optweights = initweights
return opt_weights
Risk parity portfolio ###
def riskparityportfolio(ret):
init_guess = 1 / ret.std()
optweights = list(initguess / init_guess.sum())
return opt_weights
Minimum variance portfolio ###
import numpy as np
from scipy.optimize import minimize
def minimumvarianceportfolio(ret):
# define objective function to minimize: variance def getportfoliovariance(weights): weights = np.array(weights) # check cov_mat = ret.cov() portvariance = np.dot(weights.T, np.dot(covmat, weights)) return port_variance
# equality constraint: sum of the weights = 1 def weight_cons(weights): return np.sum(weights) - 1
# model set-up # - long only portfolio # - initial guess # - constraints bounds_lim = ((0, 1),) * len(ret.columns) init_weights = [1 / len(ret.columns)] * len(ret.columns) constraint = {'type': 'eq', 'fun': weight_cons}
# find optimal portfolio optport = minimize(fun=getportfolio_variance, x0=init_weights, bounds=bounds_lim, constraints=constraint, method='SLSQP')
# find optimal weights optweights = list(optport['x'])
return opt_weights
Mean-variance portfolio ###
# import modules
import numpy as np
from scipy.optimize import minimize
def meanvarianceportfolio(ret):
# define objective function to minimize: sharpe ratio def getportfoliosr(weights):
weights = np.array(weights) # check
# expected returns port_ret = np.dot(ret, weights) meanret = portret.mean()
# volatility cov_mat = ret.cov() portstd = np.sqrt(np.dot(weights.T, np.dot(covmat, weights)))
# sharpe ratio portsr = meanret / port_std return port_sr
def objective_fun(weights): negsr = getportfolio_sr(weights) * (-1) return neg_sr
# equality constraint: sum of the weights = 1 def weight_cons(weights): return np.sum(weights) - 1
# model set-up # - long only portfolio # - initial guess # - constraints bounds_lim = ((0, 1),) * len(ret.columns) init_weights = [1 / len(ret.columns)] * len(ret.columns) constraint = {'type': 'eq', 'fun': weight_cons}
# find optimal portfolio optport = minimize(fun=objectivefun, x0=init_weights, bounds=bounds_lim, constraints=constraint, method='SLSQP')
# find optimal weights optweights = list(optport['x'])
return opt_weights
Backtest configuration
In the script../deliverable/run_backtest.py you can change the main variables to spot your ideal asset allocation and strategy.
The parameters you can change are the following:
- Assets;
- Length of the training set
# feel free to change the following parameters:
tickers = []
monthstrainingset = 12 * 5
Results